diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-19 22:28:09 +0200 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-19 22:28:09 +0200 |
| commit | 35042ff04ffc50e55ff59a385849442be4429d6b (patch) | |
| tree | 3c142dd9bfb995b7aacdf39128e591dd3f9370de /source/ui/InfoView.mc | |
| parent | 871aba901501302586895d266fe4569665d9fe70 (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'source/ui/InfoView.mc')
| -rw-r--r-- | source/ui/InfoView.mc | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/source/ui/InfoView.mc b/source/ui/InfoView.mc new file mode 100644 index 0000000..3b75764 --- /dev/null +++ b/source/ui/InfoView.mc @@ -0,0 +1,122 @@ +// Author: cirakg & claude ai +// Summary: A scrollable text screen with a title: settings results, entry IDs, clock status, about, errors. + +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class InfoView extends WatchUi.View { + + private var _title as String; + private var _text as Array<String>; + private var _screen as String; + private var _lines as Array<String>? = null; // wrapped, on first draw + private var _first as Number = 0; + private var _visible as Number = 1; + private var _shownLogged as Boolean = false; + + // screen names the view in "DIAG shown" lines for UI scripts. + function initialize(title as String, text as Array<String>, screen as String) { + View.initialize(); + _title = title; + _text = text; + _screen = screen; + } + + function onShow() as Void { + _shownLogged = false; + } + + // Scroll by delta lines; returns false when already at that end. + function scroll(delta as Number) as Boolean { + var lines = _lines; + if (lines == null) { + return false; + } + var max = lines.size() - _visible > 0 ? lines.size() - _visible : 0; + var next = _first + delta; + next = next < 0 ? 0 : (next > max ? max : next); + if (next == _first) { + return false; + } + _first = next; + WatchUi.requestUpdate(); + return true; + } + + function onUpdate(dc as Graphics.Dc) as Void { + Ui.clear(dc); + var w = dc.getWidth(); + var h = dc.getHeight(); + var font = Graphics.FONT_XTINY; + var lineH = dc.getFontHeight(font); + var y = Ui.isRound() ? h / 8 : 6; + dc.setColor(Ui.accent(), Graphics.COLOR_TRANSPARENT); + dc.drawText(w / 2, y, Graphics.FONT_SMALL, Ui.fit(dc, _title, Graphics.FONT_SMALL, 2 * Ui.halfWidthAt(dc, y)), + Graphics.TEXT_JUSTIFY_CENTER); + y += dc.getFontHeight(Graphics.FONT_SMALL) + 4; + var bottom = Ui.isRound() ? h - h / 10 : h - 4; + var lines = _lines; + if (lines == null) { + // Wrap to the narrowest width the text area has. + var width = 2 * Ui.halfWidthBetween(dc, y, bottom) - 8; + lines = [] as Array<String>; + for (var i = 0; i < _text.size(); i++) { + lines.addAll(Ui.wrap(dc, _text[i], font, width)); + } + _lines = lines; + } + _visible = (bottom - y) / lineH; + dc.setColor(Ui.fg(), Graphics.COLOR_TRANSPARENT); + for (var i = _first; i < lines.size() && i < _first + _visible; i++) { + dc.drawText(w / 2, y, font, lines[i], Graphics.TEXT_JUSTIFY_CENTER); + y += lineH; + } + if (_first + _visible < lines.size()) { + var cx = w / 2; + dc.setColor(Ui.dim(), Graphics.COLOR_TRANSPARENT); + dc.fillPolygon([[cx - 6, bottom], [cx + 6, bottom], [cx, bottom + 6]] as Array<Graphics.Point2D>); + } + if (!_shownLogged) { + _shownLogged = true; + Diag.shown(_screen); + } + } +} + +// Input on a text screen: scroll, BACK closes (and leaves the app when this +// is the only screen, as for start-up errors). +class InfoDelegate extends WatchUi.BehaviorDelegate { + + function initialize() { + BehaviorDelegate.initialize(); + } + + private function view() as InfoView? { + var current = WatchUi.getCurrentView()[0]; + return current instanceof InfoView ? current : null; + } + + function onNextPage() as Boolean { + AppState.activity(); + var v = view(); + if (v != null) { + v.scroll(3); + } + return true; + } + + function onPreviousPage() as Boolean { + AppState.activity(); + var v = view(); + if (v != null) { + v.scroll(-3); + } + return true; + } + + function onBack() as Boolean { + AppState.activity(); + return false; + } +} |
