Add pwa & stuff #75
24 changed files with 456 additions and 372 deletions
feat(android): add zoom slider
fix(android): gray screen bug
commit
eaf3f61ebd
|
|
@ -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",
|
||||||
|
|
|
||||||
323
apps/tauri/src-tauri/Cargo.lock
generated
323
apps/tauri/src-tauri/Cargo.lock
generated
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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 = "../../../"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -65,4 +69,4 @@ open class BuildTask : DefaultTask() {
|
||||||
args(listOf("--target", target))
|
args(listOf("--target", target))
|
||||||
}.assertNormalExitValue()
|
}.assertNormalExitValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
135
apps/tauri/src-tauri/src/accessibility_backend.rs
Normal file
135
apps/tauri/src-tauri/src/accessibility_backend.rs
Normal 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,
|
||||||
|
};
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -372,9 +372,13 @@ 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(
|
||||||
.await
|
Duration::from_secs(30),
|
||||||
.map_err(|error| format!("transport authentication failed: {error}"))?;
|
MTPClient::auth_connect(client_config, &keyring, &host_key),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| "transport authentication timed out".to_string())?
|
||||||
|
.map_err(|error| format!("transport authentication failed: {error}"))?;
|
||||||
manager().log(2, "Native MTP authentication completed", None);
|
manager().log(2, "Native MTP authentication completed", None);
|
||||||
|
|
||||||
let connected = CommunicationValue::new(CommunicationType::ClientConnected)
|
let connected = CommunicationValue::new(CommunicationType::ClientConnected)
|
||||||
|
|
@ -385,10 +389,13 @@ 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),
|
||||||
.await
|
connection.request(&connected, None),
|
||||||
.map_err(|error| format!("initial state synchronization failed: {error}"))?;
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| "initial state synchronization timed out".to_string())?
|
||||||
|
.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!(
|
||||||
"expected ClientStateSync, received {}",
|
"expected ClientStateSync, received {}",
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 .."
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -777,40 +777,49 @@ 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 {
|
||||||
| { kind: "state"; snapshot: NativeSnapshot }
|
const nextUnlisten = await listen<
|
||||||
| { kind: "message"; generation: number; message: unknown }
|
| { kind: "state"; snapshot: NativeSnapshot }
|
||||||
| {
|
| { kind: "message"; generation: number; message: unknown }
|
||||||
kind: "log";
|
| {
|
||||||
level: number;
|
kind: "log";
|
||||||
message: string;
|
level: number;
|
||||||
details?: unknown;
|
message: string;
|
||||||
|
details?: unknown;
|
||||||
|
}
|
||||||
|
>("mtp://event", ({ payload }) => {
|
||||||
|
if (disposed) return;
|
||||||
|
if (payload.kind === "state") {
|
||||||
|
applySnapshot(payload.snapshot);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
>("mtp://event", ({ payload }) => {
|
if (payload.kind === "message") {
|
||||||
if (disposed) return;
|
if (payload.generation === generationRef.current) {
|
||||||
if (payload.kind === "state") {
|
dispatchMessage(payload.message);
|
||||||
applySnapshot(payload.snapshot);
|
}
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
if (payload.kind === "message") {
|
|
||||||
if (payload.generation === generationRef.current) {
|
|
||||||
dispatchMessage(payload.message);
|
|
||||||
}
|
}
|
||||||
return;
|
log(
|
||||||
}
|
payload.level,
|
||||||
log(
|
"android",
|
||||||
payload.level,
|
"orange",
|
||||||
"android",
|
payload.message,
|
||||||
"orange",
|
payload.details,
|
||||||
payload.message,
|
);
|
||||||
payload.details,
|
});
|
||||||
);
|
if (disposed) nextUnlisten();
|
||||||
});
|
else unlisten = nextUnlisten;
|
||||||
const current = await invoke<NativeSnapshot>("mtp_status");
|
} catch (error) {
|
||||||
if (!disposed) applySnapshot(current);
|
log(0, "mtp", "red", "Failed to subscribe to native MTP events", error);
|
||||||
})().catch((error) => {
|
}
|
||||||
log(0, "mtp", "red", "Failed to initialize native MTP bridge", error);
|
|
||||||
});
|
try {
|
||||||
|
const current = await invoke<NativeSnapshot>("mtp_status");
|
||||||
|
if (!disposed) applySnapshot(current);
|
||||||
|
} catch (error) {
|
||||||
|
log(0, "mtp", "red", "Failed to load native MTP status", error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
return () => {
|
return () => {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
unlisten?.();
|
unlisten?.();
|
||||||
|
|
|
||||||
|
|
@ -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:*",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
80
packages/settings/src/pages/accessibility.tsx
Normal file
80
packages/settings/src/pages/accessibility.tsx
Normal 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
3
pnpm-lock.yaml
generated
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue