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
This commit is contained in:
Alois 2026-08-11 15:50:52 +02:00
commit eaf3f61ebd
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
24 changed files with 456 additions and 372 deletions

View file

@ -777,40 +777,49 @@ function TauriProvider(props: {
let disposed = false;
let unlisten: UnlistenFn | undefined;
void (async () => {
unlisten = await listen<
| { kind: "state"; snapshot: NativeSnapshot }
| { kind: "message"; generation: number; message: unknown }
| {
kind: "log";
level: number;
message: string;
details?: unknown;
try {
const nextUnlisten = await listen<
| { kind: "state"; snapshot: NativeSnapshot }
| { kind: "message"; generation: number; message: unknown }
| {
kind: "log";
level: number;
message: string;
details?: unknown;
}
>("mtp://event", ({ payload }) => {
if (disposed) return;
if (payload.kind === "state") {
applySnapshot(payload.snapshot);
return;
}
>("mtp://event", ({ payload }) => {
if (disposed) return;
if (payload.kind === "state") {
applySnapshot(payload.snapshot);
return;
}
if (payload.kind === "message") {
if (payload.generation === generationRef.current) {
dispatchMessage(payload.message);
if (payload.kind === "message") {
if (payload.generation === generationRef.current) {
dispatchMessage(payload.message);
}
return;
}
return;
}
log(
payload.level,
"android",
"orange",
payload.message,
payload.details,
);
});
const current = await invoke<NativeSnapshot>("mtp_status");
if (!disposed) applySnapshot(current);
})().catch((error) => {
log(0, "mtp", "red", "Failed to initialize native MTP bridge", error);
});
log(
payload.level,
"android",
"orange",
payload.message,
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");
if (!disposed) applySnapshot(current);
} catch (error) {
log(0, "mtp", "red", "Failed to load native MTP status", error);
}
})();
return () => {
disposed = true;
unlisten?.();

View file

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

View file

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