From 35042ff04ffc50e55ff59a385849442be4429d6b Mon Sep 17 00:00:00 2001 From: cirakg Date: Mon, 19 Oct 2026 22:28:09 +0200 Subject: feat(ui): add the list, OTP detail, PIN entry, menu, info screens and glance List with live codes and countdowns, adaptive rows and lazy headers; OTP detail with ring or bar, next code and HOTP next; PIN keypad or button selector with create, unlock, change, lockout display and pending promotion; native menu; glance with the entry count; self-test after the first frame; option properties with defaults. Codes are computed one per timer event, apart from drawing, for the watchdog. A UI-test build (uitest.jungle) starts with fake entries, PIN 123456 and a frozen clock. Co-Authored-By: Claude Opus 5.5 --- source/app/Fixtures.mc | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 source/app/Fixtures.mc (limited to 'source/app/Fixtures.mc') diff --git a/source/app/Fixtures.mc b/source/app/Fixtures.mc new file mode 100644 index 0000000..0b641ee --- /dev/null +++ b/source/app/Fixtures.mc @@ -0,0 +1,83 @@ +// Author: cirakg & claude ai +// Summary: Debug-only fake entries for the simulator and screenshots; release builds get empty stubs. +// +// All data is fabricated: the secrets are the RFC 4226/6238 test keys and the +// names are made up. The UI-test build (source/util/TestMode.mc) wipes the +// store and loads these at every start; debug builds offer "Load demo +// entries" in the menu. FIXTURE_PIN is the PIN of the UI-test store. +// +// FIXTURE_WRAP is that PIN's wrap of FIXTURE_MASTER_KEY, precomputed by +// tools/gen_fixture_wrap.py (fake salt, IV and key): deriving the PIN key on +// the watch in one event trips the watchdog, so the fixture stores the +// result. Unlocking it with the app's own code also cross-checks the wrap +// format against the Python implementation (test/storage/PinTest.mc). + +import Toybox.Application; +import Toybox.Lang; + +(:debug) +module Fixtures { + + const FIXTURE_PIN = "123456"; + const FIXTURE_WRAP = "101112131415161718191a1b1c1d1e1f000007d001202122232425262728292a2b2c2d2e2f857b961d4854e06740f602627bfb952efaac5383e800768221bf3a7b292ee881e2ce2f76b9dd744b2ac32322473df351929bc6dfcbfc2941527ae8b90614a8a6a6bd0f72d33161bafd16b1039caa670c"; // lint: allow-literal (fake fixture) + const FIXTURE_MASTER_KEY = "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"; // lint: allow-literal (fake fixture) + + // Erase all app storage (UI-test build only). + function resetStore() as Void { + Application.Storage.clearValues(); + } + + // Add the demo entries. In the UI-test build this also sets FIXTURE_PIN + // so one entry can be locked; otherwise locked demo entries go to the + // pending queue until a PIN exists. + function load(vault as Vault) as Void { + if (TestMode.ON && !vault.hasPin()) { + Application.Storage.setValue(KeyManager.WRAP_KEY, Bytes.fromHex(FIXTURE_WRAP)); + vault.session.unlock(Bytes.fromHex(FIXTURE_MASTER_KEY)); + } + var rfcKey = "12345678901234567890"; // lint: allow-literal (RFC 4226 test key) + vault.add(otp("GitHub", "alice@example.com", "Work", Entry.OTP_TOTP, Hashes.SHA1, 6, 30, rfcKey, false)); + vault.add(otp("Google", "bob@example.com", "Personal", Entry.OTP_TOTP, Hashes.SHA256, 8, 30, + "12345678901234567890123456789012", false)); // lint: allow-literal (RFC 6238 test key) + vault.add(otp("Bank", "carol", "Finance", Entry.OTP_TOTP, Hashes.SHA512, 6, 30, + "1234567890123456789012345678901234567890123456789012345678901234", true)); // lint: allow-literal (RFC 6238 test key) + vault.add(otp("VPN", "dave", "Work", Entry.OTP_HOTP, Hashes.SHA1, 6, 30, rfcKey, false)); + vault.add(otp("Steam", "erin", "Games", Entry.OTP_STEAM, Hashes.SHA1, 5, 30, rfcKey, false)); + vault.add(otp("Library", "frank", "Personal", Entry.OTP_TOTP, Hashes.SHA1, 7, 60, rfcKey, false)); + var card = new Entry(); + card.kind = Entry.KIND_CODE; + card.type = 0; + card.name = "Gym card"; + card.group = "Personal"; + card.payload = Bytes.fromUtf8("0000-1111-2222"); + vault.add(card); + vault.lock(); + } + + function otp(name as String, account as String, group as String, type as Number, algorithm as Number, + digits as Number, period as Number, secret as String, locked as Boolean) as Entry { + var e = new Entry(); + e.name = name; + e.account = account; + e.group = group; + e.type = type; + e.algorithm = algorithm; + e.digits = digits < Otp.MIN_DIGITS ? Otp.MIN_DIGITS : digits; + e.period = period; + e.secret = Bytes.fromUtf8(secret); + e.locked = locked; + e.counter = type == Entry.OTP_HOTP ? 5l : 0l; + e.created = 1700000000l; + return e; + } +} + +(:release) +module Fixtures { + + function resetStore() as Void { + } + + function load(vault as Vault) as Void { + } +} -- cgit v1.2.3