Compare commits

..
Author SHA1 Message Date
39f21a5a19 chore(deps): update dependency gradle to v9
Some checks failed
renovate/stability-days Updates have met minimum release age requirement
Dependency builds / Build web (pull_request) Successful in 17m54s
Dependency builds / Build desktop (pull_request) Successful in 21m36s
Dependency builds / Build mobile (pull_request) Failing after 22m24s
2026-08-11 16:00:50 +02:00
eaf3f61ebd
feat(android): add zoom slider
All checks were successful
/ build-web (push) Successful in 8m21s
/ build-desktop (linux) (push) Successful in 18m41s
/ build-mobile (push) Successful in 44m57s
/ release (push) Successful in 9m27s
fix(android): gray screen bug
2026-08-11 15:50:52 +02:00
24 changed files with 456 additions and 372 deletions

View file

@ -14,7 +14,7 @@
} }
}, },
"scripts": { "scripts": {
"dev:mobile:raw": "tauri android dev --host ${TAURI_DEV_HOST:-127.0.0.1}", "dev:mobile:raw": "adb reverse tcp:3000 tcp:3000 && tauri android dev --host ${TAURI_DEV_HOST:-127.0.0.1}",
"start-adb:mobile:raw": "adb devices", "start-adb:mobile:raw": "adb devices",
"build:mobile:raw": "tauri android build", "build:mobile:raw": "tauri android build",
"build:mobile:ci": "node render-version.ts && trap 'node render-version.ts --unrender' EXIT && tauri android build --debug", "build:mobile:ci": "node render-version.ts && trap 'node render-version.ts --unrender' EXIT && tauri android build --debug",

View file

@ -560,17 +560,6 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "cfb"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3"
dependencies = [
"fnv",
"uuid",
"web-time",
]
[[package]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.15.8" version = "0.15.8"
@ -843,20 +832,7 @@ version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2"
dependencies = [ dependencies = [
"cssparser-macros 0.6.1", "cssparser-macros",
"dtoa-short",
"itoa",
"phf",
"smallvec",
]
[[package]]
name = "cssparser"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98"
dependencies = [
"cssparser-macros 0.7.0",
"dtoa-short", "dtoa-short",
"itoa", "itoa",
"phf", "phf",
@ -873,16 +849,6 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "cssparser-macros"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a2a99df6e410a8ff4245aa2006499ea662245f967cc7c0a38c83ef8eb44dbf"
dependencies = [
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.8.0" version = "0.8.0"
@ -1179,26 +1145,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89"
dependencies = [ dependencies = [
"bit-set", "bit-set",
"cssparser 0.36.0", "cssparser",
"foldhash", "foldhash",
"html5ever 0.38.0", "html5ever",
"precomputed-hash", "precomputed-hash",
"selectors 0.36.1", "selectors",
"tendril",
]
[[package]]
name = "dom_query"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc"
dependencies = [
"bit-set",
"cssparser 0.37.0",
"foldhash",
"html5ever 0.39.0",
"precomputed-hash",
"selectors 0.38.0",
"tendril", "tendril",
] ]
@ -1383,7 +1334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.2", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@ -2073,17 +2024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2"
dependencies = [ dependencies = [
"log", "log",
"markup5ever 0.38.0", "markup5ever",
]
[[package]]
name = "html5ever"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
dependencies = [
"log",
"markup5ever 0.39.0",
] ]
[[package]] [[package]]
@ -2182,7 +2123,6 @@ dependencies = [
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tower-service", "tower-service",
"webpki-roots",
] ]
[[package]] [[package]]
@ -2220,7 +2160,7 @@ dependencies = [
"js-sys", "js-sys",
"log", "log",
"wasm-bindgen", "wasm-bindgen",
"windows-core 0.62.2", "windows-core",
] ]
[[package]] [[package]]
@ -2380,16 +2320,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7"
dependencies = [ dependencies = [
"cfb 0.7.3", "cfb",
]
[[package]]
name = "infer"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4"
dependencies = [
"cfb 0.14.0",
] ]
[[package]] [[package]]
@ -2752,17 +2683,6 @@ dependencies = [
"web_atoms", "web_atoms",
] ]
[[package]]
name = "markup5ever"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de"
dependencies = [
"log",
"tendril",
"web_atoms",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.3" version = "2.8.3"
@ -3920,7 +3840,7 @@ dependencies = [
"once_cell", "once_cell",
"socket2", "socket2",
"tracing", "tracing",
"windows-sys 0.61.2", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@ -4100,13 +4020,14 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.28" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util",
"http", "http",
"http-body", "http-body",
"http-body-util", "http-body-util",
@ -4120,45 +4041,12 @@ dependencies = [
"quinn", "quinn",
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"rustls-platform-verifier",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
]
[[package]]
name = "reqwest"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
dependencies = [
"base64 0.22.1",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-util", "tokio-util",
"tower", "tower",
"tower-http", "tower-http",
@ -4228,7 +4116,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.61.2", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@ -4287,7 +4175,7 @@ dependencies = [
"security-framework", "security-framework",
"security-framework-sys", "security-framework-sys",
"webpki-root-certs", "webpki-root-certs",
"windows-sys 0.61.2", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@ -4440,26 +4328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.1",
"cssparser 0.36.0", "cssparser",
"derive_more",
"log",
"new_debug_unreachable",
"phf",
"phf_codegen",
"precomputed-hash",
"rustc-hash",
"servo_arc",
"smallvec",
]
[[package]]
name = "selectors"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d"
dependencies = [
"bitflags 2.13.1",
"cssparser 0.37.0",
"derive_more", "derive_more",
"log", "log",
"new_debug_unreachable", "new_debug_unreachable",
@ -4588,18 +4457,6 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "serde_with" name = "serde_with"
version = "3.21.0" version = "3.21.0"
@ -5031,7 +4888,7 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
"url", "url",
"windows", "windows",
"windows-core 0.61.2", "windows-core",
"windows-version", "windows-version",
"x11-dl", "x11-dl",
] ]
@ -5082,13 +4939,13 @@ dependencies = [
"percent-encoding", "percent-encoding",
"plist", "plist",
"raw-window-handle", "raw-window-handle",
"reqwest 0.13.4", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"serialize-to-javascript", "serialize-to-javascript",
"swift-rs", "swift-rs",
"tauri-build 2.6.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", "tauri-build",
"tauri-macros", "tauri-macros",
"tauri-runtime", "tauri-runtime",
"tauri-runtime-wry", "tauri-runtime-wry",
@ -5123,26 +4980,6 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "tauri-build"
version = "2.6.3"
source = "git+https://github.com/tauri-apps/tauri?rev=e2e585ad1196c9572f86ef39aae01ef4c3b1a762#e2e585ad1196c9572f86ef39aae01ef4c3b1a762"
dependencies = [
"anyhow",
"cargo_toml",
"dirs",
"glob",
"heck 0.5.0",
"json-patch 4.2.0",
"schemars 0.8.22",
"semver",
"serde",
"serde_json",
"tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=e2e585ad1196c9572f86ef39aae01ef4c3b1a762)",
"tauri-winres",
"walkdir",
]
[[package]] [[package]]
name = "tauri-codegen" name = "tauri-codegen"
version = "2.6.3" version = "2.6.3"
@ -5230,7 +5067,7 @@ dependencies = [
"tracing", "tracing",
"url", "url",
"windows-registry", "windows-registry",
"windows-result 0.3.4", "windows-result",
] ]
[[package]] [[package]]
@ -5351,11 +5188,11 @@ dependencies = [
"anyhow", "anyhow",
"cargo_metadata", "cargo_metadata",
"ctor 0.8.0", "ctor 0.8.0",
"dom_query 0.27.0", "dom_query",
"dunce", "dunce",
"glob", "glob",
"http", "http",
"infer 0.19.0", "infer",
"json-patch 3.0.1", "json-patch 3.0.1",
"log", "log",
"memchr", "memchr",
@ -5387,11 +5224,11 @@ dependencies = [
"brotli", "brotli",
"cargo_metadata", "cargo_metadata",
"ctor 1.0.12", "ctor 1.0.12",
"dom_query 0.27.0", "dom_query",
"dunce", "dunce",
"glob", "glob",
"http", "http",
"infer 0.19.0", "infer",
"json-patch 4.2.0", "json-patch 4.2.0",
"log", "log",
"memchr", "memchr",
@ -5415,42 +5252,6 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "tauri-utils"
version = "2.9.3"
source = "git+https://github.com/tauri-apps/tauri?rev=e2e585ad1196c9572f86ef39aae01ef4c3b1a762#e2e585ad1196c9572f86ef39aae01ef4c3b1a762"
dependencies = [
"anyhow",
"cargo_metadata",
"ctor 1.0.12",
"dom_query 0.28.0",
"dunce",
"glob",
"http",
"infer 0.22.0",
"json-patch 4.2.0",
"log",
"memchr",
"phf",
"plist",
"proc-macro2",
"quote",
"regex",
"schemars 0.8.22",
"semver",
"serde",
"serde-untagged",
"serde_json",
"serde_with",
"swift-rs",
"thiserror 2.0.19",
"toml 1.1.4+spec-1.1.0",
"url",
"urlpattern 0.6.0",
"uuid",
"walkdir",
]
[[package]] [[package]]
name = "tauri-winres" name = "tauri-winres"
version = "0.3.6" version = "0.3.6"
@ -5480,10 +5281,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"getrandom 0.4.3", "getrandom 0.3.4",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.61.2", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@ -5497,17 +5298,17 @@ dependencies = [
[[package]] [[package]]
name = "tensamin" name = "tensamin"
version = "0.0.0" version = "0.0.11"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"jni 0.22.4", "jni 0.22.4",
"mtp", "mtp",
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)", "mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
"reqwest 0.12.28", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",
"tauri-build 2.6.3 (git+https://github.com/tauri-apps/tauri?rev=e2e585ad1196c9572f86ef39aae01ef4c3b1a762)", "tauri-build",
"tauri-plugin-barcode-scanner", "tauri-plugin-barcode-scanner",
"tauri-plugin-deep-link", "tauri-plugin-deep-link",
"tauri-plugin-log", "tauri-plugin-log",
@ -6265,15 +6066,6 @@ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
[[package]]
name = "webpki-roots"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "webview2-com" name = "webview2-com"
version = "0.38.2" version = "0.38.2"
@ -6283,7 +6075,7 @@ dependencies = [
"webview2-com-macros", "webview2-com-macros",
"webview2-com-sys", "webview2-com-sys",
"windows", "windows",
"windows-core 0.61.2", "windows-core",
"windows-implement", "windows-implement",
"windows-interface", "windows-interface",
] ]
@ -6307,7 +6099,7 @@ checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
dependencies = [ dependencies = [
"thiserror 2.0.19", "thiserror 2.0.19",
"windows", "windows",
"windows-core 0.61.2", "windows-core",
] ]
[[package]] [[package]]
@ -6332,7 +6124,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@ -6363,7 +6155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [ dependencies = [
"windows-collections", "windows-collections",
"windows-core 0.61.2", "windows-core",
"windows-future", "windows-future",
"windows-link 0.1.3", "windows-link 0.1.3",
"windows-numerics", "windows-numerics",
@ -6375,7 +6167,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [ dependencies = [
"windows-core 0.61.2", "windows-core",
] ]
[[package]] [[package]]
@ -6387,21 +6179,8 @@ dependencies = [
"windows-implement", "windows-implement",
"windows-interface", "windows-interface",
"windows-link 0.1.3", "windows-link 0.1.3",
"windows-result 0.3.4", "windows-result",
"windows-strings 0.4.2", "windows-strings",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
] ]
[[package]] [[package]]
@ -6410,7 +6189,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [ dependencies = [
"windows-core 0.61.2", "windows-core",
"windows-link 0.1.3", "windows-link 0.1.3",
"windows-threading", "windows-threading",
] ]
@ -6455,7 +6234,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [ dependencies = [
"windows-core 0.61.2", "windows-core",
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
@ -6466,8 +6245,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
dependencies = [ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
"windows-result 0.3.4", "windows-result",
"windows-strings 0.4.2", "windows-strings",
] ]
[[package]] [[package]]
@ -6479,15 +6258,6 @@ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "windows-strings" name = "windows-strings"
version = "0.4.2" version = "0.4.2"
@ -6497,15 +6267,6 @@ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.45.0" version = "0.45.0"
@ -6732,7 +6493,7 @@ dependencies = [
"cookie", "cookie",
"crossbeam-channel", "crossbeam-channel",
"dirs", "dirs",
"dom_query 0.27.0", "dom_query",
"dpi", "dpi",
"dunce", "dunce",
"gdkx11", "gdkx11",
@ -6760,7 +6521,7 @@ dependencies = [
"webkit2gtk-sys", "webkit2gtk-sys",
"webview2-com", "webview2-com",
"windows", "windows",
"windows-core 0.61.2", "windows-core",
"windows-version", "windows-version",
"x11-dl", "x11-dl",
] ]

View file

@ -15,14 +15,14 @@ name = "mobile_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies] [build-dependencies]
tauri-build = { git = "https://github.com/tauri-apps/tauri", rev = "e2e585ad1196c9572f86ef39aae01ef4c3b1a762", features = [] } tauri-build = { git = "https://github.com/tauri-apps/tauri", rev = "4af26a3f7f8b692d62cca549bbacd93f5ce90b41", features = [] }
[dependencies] [dependencies]
tauri-plugin-opener = "2" tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
base64 = "0.22" base64 = "0.22"
reqwest = { version = "0.13", default-features = false, features = ["json", "rustls-tls"] } reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] }
tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] } tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] }
mtp = { git = "https://git.methanium.net/methanium/mtp.git", rev = "7182272e3edb0079d7b74b45b2f62a298d2fee22", features = ["client", "crypto"] } mtp = { git = "https://git.methanium.net/methanium/mtp.git", rev = "7182272e3edb0079d7b74b45b2f62a298d2fee22", features = ["client", "crypto"] }
mtp-transport = { git = "https://git.methanium.net/methanium/mtp.git", rev = "b067614a684eb1856bc5db7b3fd82148c036ce6b" } mtp-transport = { git = "https://git.methanium.net/methanium/mtp.git", rev = "b067614a684eb1856bc5db7b3fd82148c036ce6b" }

View file

@ -1,13 +1,9 @@
{ {
"identifier": "mobile-capability", "identifier": "mobile-capability",
"platforms": [ "platforms": ["android", "iOS"],
"android", "windows": ["main"],
"iOS"
],
"windows": [
"main"
],
"permissions": [ "permissions": [
"core:event:default",
"deep-link:default", "deep-link:default",
"barcode-scanner:default", "barcode-scanner:default",
"barcode-scanner:allow-scan", "barcode-scanner:allow-scan",

View file

@ -1,3 +1,4 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.util.Properties import java.util.Properties
import java.io.FileInputStream import java.io.FileInputStream
@ -61,14 +62,17 @@ android {
) )
} }
} }
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures { buildFeatures {
buildConfig = true buildConfig = true
} }
} }
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_1_8
}
}
rust { rust {
rootDirRel = "../../../" rootDirRel = "../../../"
} }

View file

@ -3,10 +3,8 @@ package net.tensamin.client
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Rect
import android.media.projection.MediaProjectionManager import android.media.projection.MediaProjectionManager
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.view.WindowManager import android.view.WindowManager
import android.webkit.JavascriptInterface import android.webkit.JavascriptInterface
@ -56,7 +54,7 @@ class MainActivity : TauriActivity() {
} }
override fun onWebViewCreate(webView: WebView) { override fun onWebViewCreate(webView: WebView) {
webView.setInitialScale(290) NativeAccessibilityBridge.attach(webView)
mediaWebView = webView mediaWebView = webView
MobileMediaEvents.attach(webView) MobileMediaEvents.attach(webView)
webView.addJavascriptInterface(MobileMediaJavascriptInterface(), "tensaminMobileMedia") webView.addJavascriptInterface(MobileMediaJavascriptInterface(), "tensaminMobileMedia")
@ -65,11 +63,12 @@ class MainActivity : TauriActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, true) WindowCompat.setDecorFitsSystemWindows(window, true)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
super.onCreate(savedInstanceState)
NativeMtpBridge.nativeAttach(applicationContext)
if (MtpSecureStore.isEnabled(this) && MtpSecureStore.hasConfig(this)) { if (MtpSecureStore.isEnabled(this) && MtpSecureStore.hasConfig(this)) {
NativeMtpBridge.startService(this) NativeMtpBridge.startService(this)
} }
super.onCreate(savedInstanceState) NativeAccessibilityBridge.nativeAttach(applicationContext)
NativeMtpBridge.nativeAttach(applicationContext)
installKeyboardResizeWorkaround() installKeyboardResizeWorkaround()
} }
@ -90,7 +89,10 @@ class MainActivity : TauriActivity() {
attachLayoutListener = null attachLayoutListener = null
contentRoot = null contentRoot = null
contentChild = null contentChild = null
mediaWebView?.removeJavascriptInterface("tensaminMobileMedia") mediaWebView?.let {
NativeAccessibilityBridge.detach(it)
it.removeJavascriptInterface("tensaminMobileMedia")
}
mediaWebView = null mediaWebView = null
MobileMediaEvents.detach() MobileMediaEvents.detach()
super.onDestroy() super.onDestroy()
@ -203,16 +205,20 @@ class MainActivity : TauriActivity() {
child: android.view.View, child: android.view.View,
insets: WindowInsetsCompat? = ViewCompat.getRootWindowInsets(child), insets: WindowInsetsCompat? = ViewCompat.getRootWindowInsets(child),
) { ) {
val visibleFrame = Rect()
child.getWindowVisibleDisplayFrame(visibleFrame)
val rootHeight = child.rootView.height val rootHeight = child.rootView.height
if (rootHeight <= 0) return if (rootHeight <= 0) return
val imeHeight = insets?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0 val imeVisible = insets?.isVisible(WindowInsetsCompat.Type.ime()) == true
val keyboardHeight = maxOf(imeHeight, rootHeight - visibleFrame.bottom) val imeHeight = if (imeVisible) {
val keyboardVisible = keyboardHeight > rootHeight * 0.15 insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
val usableHeight = if (keyboardVisible) rootHeight - keyboardHeight else ViewGroup.LayoutParams.MATCH_PARENT } else {
0
}
val usableHeight = if (imeHeight in 1 until rootHeight) {
rootHeight - imeHeight
} else {
WindowManager.LayoutParams.MATCH_PARENT
}
if (previousUsableHeight == usableHeight) return if (previousUsableHeight == usableHeight) return

View file

@ -26,6 +26,7 @@ import android.os.HandlerThread
import android.os.IBinder import android.os.IBinder
import android.util.Base64 import android.util.Base64
import android.util.DisplayMetrics import android.util.DisplayMetrics
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -107,16 +108,12 @@ class MediaProjectionService : Service() {
stopIntent, stopIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
) )
val notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
android.app.Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
} else {
android.app.Notification.Builder(this)
}
.setSmallIcon(android.R.drawable.ic_menu_share) .setSmallIcon(android.R.drawable.ic_menu_share)
.setContentTitle("Tensamin is sharing your screen") .setContentTitle("Tensamin is sharing your screen")
.setContentText("Tap Stop to end screen sharing") .setContentText("Tap Stop to end screen sharing")
.setOngoing(true) .setOngoing(true)
.setCategory(android.app.Notification.CATEGORY_SERVICE) .setCategory(NotificationCompat.CATEGORY_SERVICE)
.addAction(android.R.drawable.ic_media_pause, "Stop", stopPendingIntent) .addAction(android.R.drawable.ic_media_pause, "Stop", stopPendingIntent)
.build() .build()

View file

@ -13,6 +13,8 @@ import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.Process
import android.os.SystemClock
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
class MtpForegroundService : Service() { class MtpForegroundService : Service() {
@ -74,10 +76,19 @@ class MtpForegroundService : Service() {
} }
override fun onTaskRemoved(rootIntent: Intent?) { override fun onTaskRemoved(rootIntent: Intent?) {
val preferences = getSharedPreferences(SERVICE_PREFERENCES, Context.MODE_PRIVATE)
val now = SystemClock.elapsedRealtime()
if (now - preferences.getLong(LAST_TASK_RESTART, 0) < TASK_RESTART_COOLDOWN_MS) {
super.onTaskRemoved(rootIntent)
return
}
preferences.edit().putLong(LAST_TASK_RESTART, now).commit()
if (MtpSecureStore.isEnabled(this) && MtpSecureStore.hasConfig(this)) { if (MtpSecureStore.isEnabled(this) && MtpSecureStore.hasConfig(this)) {
startService(Intent(this, MtpForegroundService::class.java)) startService(Intent(this, MtpForegroundService::class.java))
} }
super.onTaskRemoved(rootIntent) super.onTaskRemoved(rootIntent)
// Tauri cannot recreate its WebView after the UI task is removed while this process survives.
Process.killProcess(Process.myPid())
} }
override fun onDestroy() { override fun onDestroy() {
@ -94,6 +105,9 @@ class MtpForegroundService : Service() {
private const val CHANNEL_ID = "tensamin-connection" private const val CHANNEL_ID = "tensamin-connection"
private const val NOTIFICATION_ID = 2201 private const val NOTIFICATION_ID = 2201
private const val ACTION_STOP = "net.tensamin.client.STOP_MTP" private const val ACTION_STOP = "net.tensamin.client.STOP_MTP"
private const val SERVICE_PREFERENCES = "tensamin-service"
private const val LAST_TASK_RESTART = "last-task-restart"
private const val TASK_RESTART_COOLDOWN_MS = 15_000L
@Volatile private var connectionStatus = "Connecting" @Volatile private var connectionStatus = "Connecting"
fun updateNotification(context: Context, status: String) { fun updateNotification(context: Context, status: String) {

View file

@ -0,0 +1,51 @@
package net.tensamin.client
import android.content.Context
import android.webkit.WebView
import androidx.annotation.Keep
import java.lang.ref.WeakReference
@Keep
object NativeAccessibilityBridge {
const val DEFAULT_INITIAL_SCALE = 290
private const val MIN_INITIAL_SCALE = 210
private const val MAX_INITIAL_SCALE = 500
private const val PREFERENCES = "tensamin-accessibility"
private const val INITIAL_SCALE = "initial-scale"
private var webView = WeakReference<WebView>(null)
init {
System.loadLibrary("mobile_lib")
}
@JvmStatic external fun nativeAttach(context: Context)
fun attach(webView: WebView) {
this.webView = WeakReference(webView)
webView.setInitialScale(getInitialScale(webView.context))
}
fun detach(webView: WebView) {
if (this.webView.get() === webView) this.webView.clear()
}
fun getInitialScale(context: Context): Int {
val preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE)
val storedScale = preferences.getInt(INITIAL_SCALE, DEFAULT_INITIAL_SCALE)
val scale = storedScale.coerceIn(MIN_INITIAL_SCALE, MAX_INITIAL_SCALE)
if (scale != storedScale) preferences.edit().putInt(INITIAL_SCALE, scale).apply()
return scale
}
fun setInitialScale(context: Context, initialScale: Int) {
val nextScale = initialScale.coerceIn(MIN_INITIAL_SCALE, MAX_INITIAL_SCALE)
context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE)
.edit()
.putInt(INITIAL_SCALE, nextScale)
.apply()
webView.get()?.let { currentWebView ->
currentWebView.post { currentWebView.setInitialScale(nextScale) }
}
}
}

View file

@ -1,4 +1,4 @@
import com.android.build.gradle.LibraryExtension import com.android.build.api.dsl.LibraryExtension
buildscript { buildscript {
repositories { repositories {
@ -6,8 +6,8 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:9.3.1") classpath("com.android.tools.build:gradle:8.11.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.4.10") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20")
} }
} }

View file

@ -18,6 +18,5 @@ repositories {
dependencies { dependencies {
compileOnly(gradleApi()) compileOnly(gradleApi())
implementation("com.android.tools.build:gradle:9.3.1") implementation("com.android.tools.build:gradle:8.11.0")
} }

View file

@ -5,8 +5,12 @@ import org.gradle.api.GradleException
import org.gradle.api.logging.LogLevel import org.gradle.api.logging.LogLevel
import org.gradle.api.tasks.Input import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import javax.inject.Inject
open class BuildTask : DefaultTask() { open class BuildTask @Inject constructor(
private val execOperations: ExecOperations,
) : DefaultTask() {
@Input @Input
var rootDirRel: String? = null var rootDirRel: String? = null
@Input @Input
@ -50,7 +54,7 @@ open class BuildTask : DefaultTask() {
val release = release ?: throw GradleException("release cannot be null") val release = release ?: throw GradleException("release cannot be null")
val args = listOf("tauri", "android", "android-studio-script"); val args = listOf("tauri", "android", "android-studio-script");
project.exec { execOperations.exec {
workingDir(File(project.projectDir, rootDirRel)) workingDir(File(project.projectDir, rootDirRel))
executable(executable) executable(executable)
args(args) args(args)

View file

@ -0,0 +1,135 @@
#[cfg(not(target_os = "android"))]
const DEFAULT_INITIAL_SCALE: i32 = 290;
const MIN_INITIAL_SCALE: i32 = 210;
const MAX_INITIAL_SCALE: i32 = 500;
#[tauri::command]
pub fn accessibility_get_initial_scale() -> Result<i32, String> {
android_get_initial_scale()
}
#[tauri::command]
pub fn accessibility_set_initial_scale(initial_scale: i32) -> Result<(), String> {
if !(MIN_INITIAL_SCALE..=MAX_INITIAL_SCALE).contains(&initial_scale) {
return Err(format!(
"Initial scale must be between {MIN_INITIAL_SCALE} and {MAX_INITIAL_SCALE}"
));
}
android_set_initial_scale(initial_scale)
}
#[cfg(not(target_os = "android"))]
fn android_get_initial_scale() -> Result<i32, String> {
Ok(DEFAULT_INITIAL_SCALE)
}
#[cfg(not(target_os = "android"))]
fn android_set_initial_scale(_: i32) -> Result<(), String> {
Ok(())
}
#[cfg(target_os = "android")]
mod android {
use std::sync::OnceLock;
use jni::{
jni_sig, jni_str,
objects::{Global, JClass, JObject, JValue},
Env, EnvUnowned, JavaVM,
};
struct Host {
vm: JavaVM,
context: Global<JObject<'static>>,
bridge: Global<JObject<'static>>,
}
static HOST: OnceLock<Host> = OnceLock::new();
fn attach(env: &mut Env, context: JObject) -> Result<(), String> {
if HOST.get().is_some() {
return Ok(());
}
let class = env
.find_class(jni_str!("net/tensamin/client/NativeAccessibilityBridge"))
.map_err(|error| error.to_string())?;
let bridge = env
.get_static_field(
class,
jni_str!("INSTANCE"),
jni_sig!("Lnet/tensamin/client/NativeAccessibilityBridge;"),
)
.and_then(|value| value.l())
.map_err(|error| error.to_string())?;
HOST.set(Host {
vm: env.get_java_vm().map_err(|error| error.to_string())?,
context: env
.new_global_ref(context)
.map_err(|error| error.to_string())?,
bridge: env
.new_global_ref(bridge)
.map_err(|error| error.to_string())?,
})
.map_err(|_| "Android accessibility host is already attached".to_string())
}
fn with_env<T>(call: impl FnOnce(&mut Env, &Host) -> Result<T, String>) -> Result<T, String> {
let host = HOST
.get()
.ok_or("Android accessibility host is not attached")?;
host.vm
.attach_current_thread(|env| Ok::<_, jni::errors::Error>(call(env, host)))
.map_err(|error| error.to_string())?
}
pub fn get_initial_scale() -> Result<i32, String> {
with_env(|env, host| {
env.call_method(
host.bridge.as_obj(),
jni_str!("getInitialScale"),
jni_sig!("(Landroid/content/Context;)I"),
&[JValue::Object(host.context.as_obj())],
)
.and_then(|value| value.i())
.map_err(|error| error.to_string())
})
}
pub fn set_initial_scale(initial_scale: i32) -> Result<(), String> {
with_env(|env, host| {
env.call_method(
host.bridge.as_obj(),
jni_str!("setInitialScale"),
jni_sig!("(Landroid/content/Context;I)V"),
&[
JValue::Object(host.context.as_obj()),
JValue::Int(initial_scale),
],
)
.map_err(|error| error.to_string())?;
Ok(())
})
}
#[no_mangle]
pub extern "system" fn Java_net_tensamin_client_NativeAccessibilityBridge_nativeAttach<
'caller,
>(
mut env: EnvUnowned<'caller>,
_class: JClass,
context: JObject<'caller>,
) {
let _ = env.with_env(|env| {
let _ = attach(env, context);
Ok::<_, jni::errors::Error>(())
});
}
}
#[cfg(target_os = "android")]
use android::{
get_initial_scale as android_get_initial_scale, set_initial_scale as android_set_initial_scale,
};

View file

@ -1,3 +1,4 @@
mod accessibility_backend;
mod mtp_backend; mod mtp_backend;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
@ -19,6 +20,8 @@ pub fn run() {
let app = builder let app = builder
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
accessibility_backend::accessibility_get_initial_scale,
accessibility_backend::accessibility_set_initial_scale,
mtp_backend::mtp_request, mtp_backend::mtp_request,
mtp_backend::mtp_status, mtp_backend::mtp_status,
mtp_backend::mtp_store_credentials, mtp_backend::mtp_store_credentials,

View file

@ -372,8 +372,12 @@ async fn connect(config: &MtpConfig) -> Result<(MTPConnection, Value), String> {
.with_max_missed_pings(3); .with_max_missed_pings(3);
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
let client_config = client_config.with_pinned_pem(android_root_certificates().clone()); let client_config = client_config.with_pinned_pem(android_root_certificates().clone());
let connection = MTPClient::auth_connect(client_config, &keyring, &host_key) let connection = tokio::time::timeout(
Duration::from_secs(30),
MTPClient::auth_connect(client_config, &keyring, &host_key),
)
.await .await
.map_err(|_| "transport authentication timed out".to_string())?
.map_err(|error| format!("transport authentication failed: {error}"))?; .map_err(|error| format!("transport authentication failed: {error}"))?;
manager().log(2, "Native MTP authentication completed", None); manager().log(2, "Native MTP authentication completed", None);
@ -385,9 +389,12 @@ async fn connect(config: &MtpConfig) -> Result<(MTPConnection, Value), String> {
.add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0)) .add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0))
.add_typed_default(DataType::CacheValid, DataValue::BoolFalse) .add_typed_default(DataType::CacheValid, DataValue::BoolFalse)
.add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0)); .add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0));
let state = connection let state = tokio::time::timeout(
.request(&connected, None) Duration::from_secs(30),
connection.request(&connected, None),
)
.await .await
.map_err(|_| "initial state synchronization timed out".to_string())?
.map_err(|error| format!("initial state synchronization failed: {error}"))?; .map_err(|error| format!("initial state synchronization failed: {error}"))?;
if !state.is_type(CommunicationType::ClientStateSync) { if !state.is_type(CommunicationType::ClientStateSync) {
return Err(format!( return Err(format!(
@ -406,9 +413,9 @@ async fn connect(config: &MtpConfig) -> Result<(MTPConnection, Value), String> {
let ack = CommunicationValue::new(CommunicationType::ClientStateAck) let ack = CommunicationValue::new(CommunicationType::ClientStateAck)
.add_typed_default(DataType::SessionId, number_to_data(session_id)) .add_typed_default(DataType::SessionId, number_to_data(session_id))
.add_typed_default(DataType::VersionNumber, number_to_data(version)); .add_typed_default(DataType::VersionNumber, number_to_data(version));
let response = connection let response = tokio::time::timeout(Duration::from_secs(30), connection.request(&ack, None))
.request(&ack, None)
.await .await
.map_err(|_| "state acknowledgement timed out".to_string())?
.map_err(|error| format!("state acknowledgement failed: {error}"))?; .map_err(|error| format!("state acknowledgement failed: {error}"))?;
if response if response
.get_type_name() .get_type_name()
@ -433,7 +440,19 @@ async fn resolve_endpoint(config: &MtpConfig) -> Result<(String, String), String
public_key: String, public_key: String,
} }
let root = config.omega_url.trim_end_matches('/'); let root = config.omega_url.trim_end_matches('/');
let response = reqwest::get(format!("{root}/api/get/omikron/{}", config.user_id)) let client = reqwest::Client::builder()
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(20));
#[cfg(target_os = "android")]
let client = client.tls_certs_only(
reqwest::Certificate::from_pem_bundle(android_root_certificates())
.map_err(|error| format!("invalid bundled root certificates: {error}"))?,
);
let response = client
.build()
.map_err(|error| error.to_string())?
.get(format!("{root}/api/get/omikron/{}", config.user_id))
.send()
.await .await
.map_err(|error| error.to_string())?; .map_err(|error| error.to_string())?;
if !response.status().is_success() { if !response.status().is_success() {

View file

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <link rel="icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tensamin</title> <title>Tensamin</title>
</head> </head>

View file

@ -6,7 +6,7 @@
"scripts": { "scripts": {
"format": "pnpm exec prettier --write .", "format": "pnpm exec prettier --write .",
"lint": "eslint src", "lint": "eslint src",
"dev": "vite --port 3000 --host 0.0.0.0", "dev": "vite",
"test": "vitest run --passWithNoTests", "test": "vitest run --passWithNoTests",
"build": "pnpm run test && tsc -b && vite build", "build": "pnpm run test && tsc -b && vite build",
"preview": "cd dist && nix-shell -p python3 --run 'python3 -m http.server 3000' && cd .." "preview": "cd dist && nix-shell -p python3 --run 'python3 -m http.server 3000' && cd .."

View file

@ -76,11 +76,6 @@ function parseTuFileContent(rawFileContent: string): {
throw new Error("Invalid file"); throw new Error("Invalid file");
} }
console.log({
domain,
userId,
});
return { userId, privateKey, domain }; return { userId, privateKey, domain };
} }

View file

@ -70,9 +70,9 @@ export default defineConfig({
], ],
}, },
server: { server: {
port: 5173, port: 3000,
strictPort: true, strictPort: true,
host: host || "0.0.0.0", host: "0.0.0.0",
hmr: host hmr: host
? { ? {
protocol: "ws", protocol: "ws",

View file

@ -777,7 +777,8 @@ function TauriProvider(props: {
let disposed = false; let disposed = false;
let unlisten: UnlistenFn | undefined; let unlisten: UnlistenFn | undefined;
void (async () => { void (async () => {
unlisten = await listen< try {
const nextUnlisten = await listen<
| { kind: "state"; snapshot: NativeSnapshot } | { kind: "state"; snapshot: NativeSnapshot }
| { kind: "message"; generation: number; message: unknown } | { kind: "message"; generation: number; message: unknown }
| { | {
@ -806,11 +807,19 @@ function TauriProvider(props: {
payload.details, payload.details,
); );
}); });
if (disposed) nextUnlisten();
else unlisten = nextUnlisten;
} catch (error) {
log(0, "mtp", "red", "Failed to subscribe to native MTP events", error);
}
try {
const current = await invoke<NativeSnapshot>("mtp_status"); const current = await invoke<NativeSnapshot>("mtp_status");
if (!disposed) applySnapshot(current); if (!disposed) applySnapshot(current);
})().catch((error) => { } catch (error) {
log(0, "mtp", "red", "Failed to initialize native MTP bridge", error); log(0, "mtp", "red", "Failed to load native MTP status", error);
}); }
})();
return () => { return () => {
disposed = true; disposed = true;
unlisten?.(); unlisten?.();

View file

@ -14,6 +14,7 @@
"dependencies": { "dependencies": {
"@methanium/ui": "*", "@methanium/ui": "*",
"@tanstack/react-router": "^1.170.21", "@tanstack/react-router": "^1.170.21",
"@tauri-apps/api": "^2.11.1",
"@tensamin/cache": "workspace:*", "@tensamin/cache": "workspace:*",
"@tensamin/hotkeys": "workspace:*", "@tensamin/hotkeys": "workspace:*",
"@tensamin/mtp": "workspace:*", "@tensamin/mtp": "workspace:*",

View file

@ -1,3 +1,4 @@
import Accessibility from "./pages/accessibility";
import Cache from "./pages/cache"; import Cache from "./pages/cache";
import Call from "./pages/call"; import Call from "./pages/call";
import Chat from "./pages/chat"; import Chat from "./pages/chat";
@ -26,6 +27,12 @@ export const settingsPages = [
{ category: "general", path: "call", label: "Call", component: Call }, { category: "general", path: "call", label: "Call", component: Call },
{ category: "application", path: "cache", label: "Cache", component: Cache }, { category: "application", path: "cache", label: "Cache", component: Cache },
{ category: "application", path: "theme", label: "Theme", component: Theme }, { category: "application", path: "theme", label: "Theme", component: Theme },
{
category: "application",
path: "accessibility",
label: "Accessibility",
component: Accessibility,
},
{ {
category: "application", category: "application",
path: "hotkeys", path: "hotkeys",

View file

@ -0,0 +1,80 @@
import { Button, Label, Slider } from "@methanium/ui";
import { invoke, isTauri } from "@tauri-apps/api/core";
import { RotateCcw } from "lucide-react";
import { useEffect, useState } from "react";
const DEFAULT_INITIAL_SCALE = 290;
const MIN_INITIAL_SCALE = 210;
const isAndroid = isTauri() && /Android/.test(navigator.userAgent);
export default function Page() {
const [initialScale, setInitialScale] = useState(DEFAULT_INITIAL_SCALE);
const [loading, setLoading] = useState(isAndroid);
useEffect(() => {
if (!isAndroid) return;
let active = true;
void invoke<number>("accessibility_get_initial_scale")
.then((scale) => {
if (active) setInitialScale(scale);
})
.catch((error: unknown) => {
console.error("Failed to load the Android initial scale", error);
})
.finally(() => {
if (active) setLoading(false);
});
return () => {
active = false;
};
}, []);
if (!isAndroid) return null;
function updateInitialScale(nextScale: number) {
setInitialScale(nextScale);
void invoke("accessibility_set_initial_scale", {
initialScale: nextScale,
}).catch((error: unknown) => {
console.error("Failed to update the Android initial scale", error);
});
}
return (
<div className="flex max-w-md flex-col gap-2">
<div className="flex items-center justify-between gap-2">
<Label htmlFor="initial-scale">Interface scale</Label>
<span className="text-muted-foreground text-sm tabular-nums">
{initialScale}%
</span>
</div>
<div className="flex items-center gap-3">
<Slider
id="initial-scale"
aria-label="Interface scale"
min={MIN_INITIAL_SCALE}
max={500}
step={1}
value={[initialScale]}
disabled={loading}
onValueChange={(value) => {
const nextScale = Array.isArray(value) ? value[0] : value;
if (nextScale !== undefined) updateInitialScale(nextScale);
}}
/>
<Button
className="size-8 shrink-0 p-0"
variant="outline"
aria-label="Reset interface scale"
title="Reset interface scale"
disabled={loading || initialScale === DEFAULT_INITIAL_SCALE}
onClick={() => updateInitialScale(DEFAULT_INITIAL_SCALE)}
>
<RotateCcw className="size-4" />
</Button>
</div>
</div>
);
}

3
pnpm-lock.yaml generated
View file

@ -509,6 +509,9 @@ importers:
'@tanstack/react-router': '@tanstack/react-router':
specifier: ^1.170.21 specifier: ^1.170.21
version: 1.170.23(react-dom@19.2.8(react@19.2.8))(react@19.2.8) version: 1.170.23(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@tauri-apps/api':
specifier: ^2.11.1
version: 2.11.1
'@tensamin/cache': '@tensamin/cache':
specifier: workspace:* specifier: workspace:*
version: link:../cache version: link:../cache