Improve Android app UI and controller flows

This commit is contained in:
2026-10-09 16:43:58 +08:00
parent c0e26b97fe
commit 608229a497
90 changed files with 8103 additions and 1133 deletions
@@ -1,3 +1,4 @@
import * as v1 from "../app/static/scene-model-v1.js";
import test from "node:test";
import assert from "node:assert/strict";
import {
@@ -78,23 +79,23 @@ test("serializes pixelRgb as base64 and round trips an independent scene", () =>
color: "#12abef",
});
const stored = sceneToStorageValue(original);
assert.equal(typeof stored.pixelRgb, "string");
assert.equal(typeof stored.layers[0].pixelPng, "string");
assert.equal("pixels" in stored, false);
assert.equal("pixelsRgb" in stored, false);
assert.equal("revision" in stored.elements[0], false);
assert.equal("revision" in stored.layers[1].elements[0], false);
const restored = deserializeScene(serializeScene(original));
assert.ok(restored);
assert.deepEqual(Array.from(restored.pixels), Array.from(original.pixels));
assert.deepEqual(restored.elements, original.elements);
assert.notStrictEqual(restored.pixels, original.pixels);
assert.notStrictEqual(restored.elements[0], original.elements[0]);
assert.equal(restored.elements[0].text, "你好 · مرحبا · नमस्ते · ไทย");
assert.notStrictEqual(restored.layers[1].elements[0], original.elements[0]);
assert.equal(restored.layers[1].elements[0].text, "你好 · مرحبا · नमस्ते · ไทย");
});
test("moves a valid v1 draft to the stable key before removing exact legacy keys", () => {
const original = addTextElement(createScene({ pixels: patternedPixels(23) }), { text: "keep me" });
const storage = memoryStorage({
[LEGACY_STORAGE_KEYS.sceneV1]: serializeScene(original),
[LEGACY_STORAGE_KEYS.sceneV1]: v1.serializeScene(v1.createScene({pixels:original.pixels,elements:original.elements})),
[LEGACY_STORAGE_KEYS.canvasV3]: JSON.stringify({ pixels: Array.from(patternedPixels(2)) }),
[LEGACY_STORAGE_KEYS.textV1]: JSON.stringify({ "text-value": "older text" }),
"unrelated:key": "keep",
@@ -117,7 +118,7 @@ test("moves a valid v1 draft to the stable key before removing exact legacy keys
});
test("keeps the v1 draft and all legacy keys when stable-key migration cannot write", () => {
const originalRaw = serializeScene(addTextElement(createScene(), { text: "do not delete" }));
const originalRaw = v1.serializeScene(v1.addTextElement(v1.createScene(), { text: "do not delete" }));
const storage = memoryStorage({
[LEGACY_STORAGE_KEYS.sceneV1]: originalRaw,
[LEGACY_STORAGE_KEYS.canvasV2]: JSON.stringify({ pixels: Array.from(patternedPixels(4)) }),
@@ -184,23 +185,14 @@ test("does not discard a damaged higher-priority legacy key during fallback migr
test("rejects damaged scene roots while skipping only damaged elements", () => {
const valid = sceneToStorageValue(addTextElement(createScene(), { text: "keep" }));
assert.equal(deserializeScene({ ...valid, version: 2 }), null);
assert.equal(deserializeScene({ ...valid, version: 3 }), null);
assert.equal(deserializeScene({ ...valid, width: 32 }), null);
assert.equal(deserializeScene({ ...valid, pixelRgb: "not base64" }), null);
assert.equal(deserializeScene("{broken"), null);
const recovered = deserializeScene({
...valid,
elements: [
valid.elements[0],
{ id: "bad-type", type: "image", text: "skip" },
{ id: "bad-text", type: "text", text: 123 },
{ ...valid.elements[0], text: "duplicate id" },
],
});
assert.ok(recovered);
assert.equal(recovered.elements.length, 1);
assert.equal(recovered.elements[0].text, "keep");
const invalid = {...valid,layers:valid.layers.map((l,i)=>i===1?{...l,elements:[...l.elements,{...l.elements[0]}]}:l)};
assert.equal(deserializeScene(invalid), null, "duplicate ids must not be silently dropped");
});
test("normalizes text fields and creates a centered lowercase ok element", () => {
@@ -235,6 +227,7 @@ test("normalizes text fields and creates a centered lowercase ok element", () =>
align: DEFAULT_TEXT_ELEMENT.align,
color: "#12ABEF",
revision: 0,
clip: null,
});
assert.equal("background" in normalized, false);
});