diff options
Diffstat (limited to 'source/ui')
| -rw-r--r-- | source/ui/CodeView.mc | 19 | ||||
| -rw-r--r-- | source/ui/DebugMenu.mc | 9 | ||||
| -rw-r--r-- | source/ui/GlanceView.mc | 23 | ||||
| -rw-r--r-- | source/ui/InfoView.mc | 4 | ||||
| -rw-r--r-- | source/ui/ListView.mc | 18 | ||||
| -rw-r--r-- | source/ui/MainMenu.mc | 113 | ||||
| -rw-r--r-- | source/ui/OtpView.mc | 4 | ||||
| -rw-r--r-- | source/ui/PinView.mc | 33 | ||||
| -rw-r--r-- | source/ui/Ui.mc | 8 |
9 files changed, 180 insertions, 51 deletions
diff --git a/source/ui/CodeView.mc b/source/ui/CodeView.mc index efa23d2..03f1fa7 100644 --- a/source/ui/CodeView.mc +++ b/source/ui/CodeView.mc @@ -27,6 +27,9 @@ // ticker timer does the renewing (and redraws the progress while // encoding), so a code screen never needs more timers. // * Too big: a message with the size needed. +// * A locked code closes back to the list when auto-lock fires while it +// is open (README 7.5), like a locked OTP entry: the ticker keeps +// running for it and checks the session. // The payload is decrypted when the screen opens and wiped once encoded. import Toybox.Attention; @@ -70,6 +73,9 @@ class CodeView extends WatchUi.View { _shownLogged = false; encode(); boostBrightness(); + if (header.locked) { + startTicker(); + } } function onHide() as Void { @@ -143,8 +149,13 @@ class CodeView extends WatchUi.View { } // Progress redraws while a job runs; brightness renewals during the - // minute. The ticker stops when neither is needed. + // minute; auto-lock for a locked code. The ticker stops when none of + // them is needed. function onTick() as Void { + if (header.locked && AppState.checkAutoLock()) { + WatchUi.popView(WatchUi.SLIDE_RIGHT); + return; + } if (_job != null) { WatchUi.requestUpdate(); } @@ -157,7 +168,7 @@ class CodeView extends WatchUi.View { light(); } } - if (_job == null && _boostStart < 0) { + if (_job == null && _boostStart < 0 && !header.locked) { stopTicker(); } } @@ -266,8 +277,10 @@ class CodeView extends WatchUi.View { } else if (code instanceof RunsPainter) { drawMatrix(dc, code, smallFont, smallH); } else if (job != null) { + var percent = job.percent(); dc.drawText(w / 2, h / 2 - smallH / 2, smallFont, (job instanceof RunsPainter ? "Drawing… " : "Encoding… ") - + job.percent() + "%", Graphics.TEXT_JUSTIFY_CENTER); + + percent + "%", Graphics.TEXT_JUSTIFY_CENTER); + Diag.data("code_progress", "percent=" + percent); // for UI scripts } else { drawMessage(dc, code instanceof Codes.CodeError ? code.message : "…", smallFont, smallH); } diff --git a/source/ui/DebugMenu.mc b/source/ui/DebugMenu.mc index f1ab407..452cb1d 100644 --- a/source/ui/DebugMenu.mc +++ b/source/ui/DebugMenu.mc @@ -40,6 +40,8 @@ module DebugMenu { } } + const BENCH_HEADERS = 20; + // The running benchmark (kept so its timer isn't collected). var bench as BenchRunner? = null; @@ -74,12 +76,15 @@ module DebugMenu { } else if (_step == 2) { _lines.add("PIN derivation: " + Kdf.calibrate(1000, Kdf.ITERATIONS_PER_STEP) + " iterations per second"); } else if (_step == 3) { + // At most BENCH_HEADERS: all 110 of the UI test's entries in + // one event tripped the watchdog. var vault = AppState.theVault(); var ids = vault.ids(); - for (var i = 0; i < ids.size(); i++) { + var n = ids.size() < BENCH_HEADERS ? ids.size() : BENCH_HEADERS; + for (var i = 0; i < n; i++) { vault.header(ids[i]); } - _lines.add("Headers: " + ids.size() + " in " + (System.getTimer() - t) + " ms"); + _lines.add("Headers: " + n + " in " + (System.getTimer() - t) + " ms"); } else if (_step == 4) { SelfTest.run(); _lines.add("Self-test: " + (System.getTimer() - t) + " ms"); diff --git a/source/ui/GlanceView.mc b/source/ui/GlanceView.mc index 4540f2a..6be18f3 100644 --- a/source/ui/GlanceView.mc +++ b/source/ui/GlanceView.mc @@ -89,8 +89,15 @@ class OtpGlanceView extends WatchUi.GlanceView { var h = dc.getHeight(); var top = h / 2 - dc.getFontHeight(Graphics.FONT_GLANCE); var x = inset(dc, top); + // In capitals the name reaches the round screen's edge on the + // Forerunner 70 and 965, which cuts the last letter; the narrower + // mixed case fits there. + var title = "OTP WALLET"; + if (x + dc.getTextWidthInPixels(title, Graphics.FONT_GLANCE) > dc.getWidth() - edgeCut(top)) { + title = "OTP Wallet"; + } dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT); - dc.drawText(x, top, Graphics.FONT_GLANCE, "OTP WALLET", Graphics.TEXT_JUSTIFY_LEFT); + dc.drawText(x, top, Graphics.FONT_GLANCE, title, Graphics.TEXT_JUSTIFY_LEFT); dc.setColor(Graphics.COLOR_LT_GRAY, Graphics.COLOR_TRANSPARENT); dc.drawText(x, h / 2, Graphics.FONT_GLANCE, count == 1 ? "1 entry" : count + " entries", Graphics.TEXT_JUSTIFY_LEFT); @@ -103,15 +110,23 @@ class OtpGlanceView extends WatchUi.GlanceView { // screen's top-left curve; on most round watches this is 0 or a few // pixels). private function inset(dc as Graphics.Dc, y as Number) as Number { + var left = (System.getDeviceSettings().screenWidth - dc.getWidth()) / 2; + var edge = edgeCut(y); + return edge > left ? edge - left : 0; + } + + // How far a round screen's edge reaches in from each side at height y + // (0 on other shapes), with the same assumption: the glance starts at + // the top of the screen. The right end of a glance reaches the screen's + // edge, so this is also what the curve cuts off there. + private function edgeCut(y as Number) as Number { var screen = System.getDeviceSettings(); if (screen.screenShape != System.SCREEN_SHAPE_ROUND) { return 0; } var r = screen.screenWidth / 2; var dy = r - y; - var edge = r - Math.sqrt(r * r - dy * dy).toNumber(); - var left = (screen.screenWidth - dc.getWidth()) / 2; - return edge > left ? edge - left : 0; + return r - Math.sqrt(r * r - dy * dy).toNumber(); } // The label, the code and a bar for the seconds left in the period. diff --git a/source/ui/InfoView.mc b/source/ui/InfoView.mc index a965713..877ca51 100644 --- a/source/ui/InfoView.mc +++ b/source/ui/InfoView.mc @@ -65,7 +65,9 @@ class InfoView extends WatchUi.View { 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; + // The same margin on rectangles: the Venu X1's rounded corners cut + // off a last line drawn at the very bottom, and the scroll arrow. + var bottom = h - h / 10; var lines = _lines; if (lines == null) { // Wrap to the narrowest width the text area has. diff --git a/source/ui/ListView.mc b/source/ui/ListView.mc index de73cce..36df798 100644 --- a/source/ui/ListView.mc +++ b/source/ui/ListView.mc @@ -3,7 +3,8 @@ // // Layout (README 13.1, 13.3): a title, then rows of two lines each (name; // group and account) with the code and a countdown on the right for unlocked -// OTP entries, a padlock for locked entries and a barcode icon for codes. +// OTP entries, a padlock for locked entries (codes too: opening them asks +// for the PIN) and a barcode icon for other codes. // The focused row is outlined; the list scrolls to keep it visible and // centres short lists. On round screens each row is as wide as the circle // allows at its height. @@ -186,7 +187,8 @@ class ListView extends WatchUi.View { // "Importing… 40%" while an import runs, "Results ready" when settings // results haven't been looked at, "Confirm in menu" while phone changes - // wait for the PIN, else the app name. + // wait to be confirmed with the PIN (a waiting "Require PIN" applies at + // the next PIN by itself), else the app name. private function title() as String { var p = AppState.processor; var busy = p != null ? p.busyText() : null; @@ -197,7 +199,7 @@ class ListView extends WatchUi.View { return "Results ready (menu)"; } var v = AppState.vault; - return v != null && ChangeQueue.count(v) > 0 ? "Confirm in menu" : "OTP Wallet"; + return v != null && ChangeQueue.countToConfirm(v) > 0 ? "Confirm in menu" : "OTP Wallet"; } private function drawEmpty(dc as Graphics.Dc, top as Number, bottom as Number) as Void { @@ -283,7 +285,7 @@ class ListView extends WatchUi.View { var right = x1 - 8; if (header == null) { - dc.setColor(Ui.WARN_COLOR, Graphics.COLOR_TRANSPARENT); + dc.setColor(Ui.warn(), Graphics.COLOR_TRANSPARENT); dc.drawText(left, lineY, nameFont, Ui.fit(dc, "Damaged " + id, nameFont, right - left), Graphics.TEXT_JUSTIFY_LEFT); return true; } @@ -297,12 +299,12 @@ class ListView extends WatchUi.View { var secondRight = right; var complete = true; var iconSize = nameH * 2 / 3; - if (header.kind == Entry.KIND_CODE) { - Ui.drawBarcode(dc, right - iconSize, lineY + (nameH - iconSize) / 2, iconSize, Ui.dim()); - nameRight -= iconSize + 6; - } else if (header.locked) { + if (header.locked) { Ui.drawLock(dc, right - iconSize, lineY + (nameH - iconSize) / 2, iconSize, Ui.dim()); nameRight -= iconSize + 6; + } else if (header.kind == Entry.KIND_CODE) { + Ui.drawBarcode(dc, right - iconSize, lineY + (nameH - iconSize) / 2, iconSize, Ui.dim()); + nameRight -= iconSize + 6; } else { var now = AppState.now(); var text = AppState.cryptoReady ? AppState.theCodes().cached(header.id, now) : null; diff --git a/source/ui/MainMenu.mc b/source/ui/MainMenu.mc index 8863c6d..db59f4a 100644 --- a/source/ui/MainMenu.mc +++ b/source/ui/MainMenu.mc @@ -18,7 +18,10 @@ module MainMenu { var menu = new WatchUi.Menu2({:title => "OTP Wallet"}); var waiting = ChangeQueue.count(vault); if (waiting > 0) { - menu.addItem(new WatchUi.MenuItem("Phone changes", waiting + " to confirm", :queue, null)); + // Only "Require PIN" waiting: it applies at the next PIN anyway. + var toConfirm = ChangeQueue.countToConfirm(vault); + menu.addItem(new WatchUi.MenuItem("Phone changes", toConfirm > 0 ? toConfirm + " to confirm" : "Apply at next PIN", + :queue, null)); } if (vault.session.isUnlocked()) { menu.addItem(new WatchUi.MenuItem("Lock now", null, :lock, null)); @@ -32,28 +35,29 @@ module MainMenu { menu.addItem(new WatchUi.MenuItem("Clock status", null, :clock, null)); menu.addItem(new WatchUi.MenuItem("About", null, :about, null)); DebugMenu.addItems(menu); + logItems(menu); return menu; } - function entryIdLines() as Array<String> { - var vault = AppState.theVault(); - var ids = vault.ids(); - var lines = [] as Array<String>; - for (var i = 0; i < ids.size(); i++) { - var header = vault.header(ids[i]); - var name = header == null ? "damaged" : (header.name.length() > 0 ? header.name : "Locked entry"); - lines.add(ids[i] + " " + name); - } - if (lines.size() == 0) { - lines.add("No entries yet."); - } - var pending = vault.pendingIds().size(); - if (pending > 0) { - lines.add(pending + " locked entries wait for your PIN."); + // Debug builds: the item labels in order, so UI scripts can find an + // item on touch-only watches, whose menus have no focus to move. + (:debug) + function logItems(menu as WatchUi.Menu2) as Void { + var labels = ""; + for (var i = 0; menu.getItem(i) != null; i++) { + labels += (i > 0 ? "|" : "") + (menu.getItem(i) as WatchUi.MenuItem).getLabel(); } - return lines; + Diag.data("menu", "items=" + labels); + } + + (:release) + function logItems(menu as WatchUi.Menu2) as Void { } + // The Entry IDs screen: "Reading…" while EntryIdsJob reads the + // headers on a timer, then the list (kept so the runner isn't collected). + var idsScreen as EntryIdsScreen? = null; + function clockLines() as Array<String> { var lines = [] as Array<String>; var watch = Clock.nowUnix(0); @@ -118,7 +122,9 @@ class MainMenuDelegate extends WatchUi.Menu2InputDelegate { WatchUi.switchToView(new PinView(PinView.MODE_UNLOCK, then.method(:run)), new PinDelegate(), WatchUi.SLIDE_LEFT); } } else if (id == :reset) { - WatchUi.pushView(new WatchUi.Confirmation("Erase locked entries and set a new PIN?"), new ResetPinConfirmation(), + // Two short lines (the item said "Reset PIN"): the native dialog shows + // two lines and cuts longer text mid-word. + WatchUi.pushView(new WatchUi.Confirmation("Erase locked\nentries?"), new ResetPinConfirmation(), WatchUi.SLIDE_LEFT); } else if (id == :results) { WatchUi.switchToView(new InfoView("Results", Results.lines(), "results"), new InfoDelegate(), @@ -127,7 +133,9 @@ class MainMenuDelegate extends WatchUi.Menu2InputDelegate { } else if (id == :queue) { WatchUi.switchToView(QueueMenu.build(), new QueueMenuDelegate(), WatchUi.SLIDE_LEFT); } else if (id == :ids) { - WatchUi.switchToView(new InfoView("Entry IDs", MainMenu.entryIdLines(), "ids"), new InfoDelegate(), WatchUi.SLIDE_LEFT); + var screen = new EntryIdsScreen(); + MainMenu.idsScreen = screen; + screen.start(); } else if (id == :clock) { WatchUi.switchToView(new InfoView("Clock status", MainMenu.clockLines(), "clock"), new InfoDelegate(), WatchUi.SLIDE_LEFT); } else if (id == :about) { @@ -138,6 +146,73 @@ class MainMenuDelegate extends WatchUi.Menu2InputDelegate { } } +// Reads one header per entry, a few per timer step: reading 110 in one +// event tripped the watchdog. Ends with the lines of the Entry IDs screen. +class EntryIdsJob extends Jobs.Job { + + static const HEADERS_PER_STEP = 10; + + var lines as Array<String> = [] as Array<String>; + private var _ids as Array<String>; + private var _at as Number = 0; + + function initialize() { + Job.initialize(); + _ids = AppState.theVault().ids(); + } + + function step(budget as Number) as Boolean { + var vault = AppState.theVault(); + var stop = _at + HEADERS_PER_STEP * budget; + stop = stop < _ids.size() ? stop : _ids.size(); + for (; _at < stop; _at++) { + var id = _ids[_at]; + var header = vault.header(id); + var name = header == null ? "damaged" : (header.name.length() > 0 ? header.name : "Locked entry"); + lines.add(id + " " + name); + } + if (_at < _ids.size()) { + return false; + } + if (lines.size() == 0) { + lines.add("No entries yet."); + } + var pending = vault.pendingIds().size(); + if (pending > 0) { + lines.add(pending + " locked entries wait for your PIN."); + } + return true; + } +} + +// Shows "Reading…" (replacing the menu), runs EntryIdsJob, then replaces +// that screen with the IDs, unless the user left it meanwhile. +class EntryIdsScreen { + + private var _job as EntryIdsJob = new EntryIdsJob(); + // The timer driver (kept so it isn't collected). + var runner as Jobs.JobRunner? = null; + private var _reading as InfoView = new InfoView("Entry IDs", ["Reading…"] as Array<String>, "ids_reading"); + + function initialize() { + } + + function start() as Void { + WatchUi.switchToView(_reading, new InfoDelegate(), WatchUi.SLIDE_LEFT); + var r = new Jobs.JobRunner(_job, 1, method(:done)); + runner = r; + r.start(); + } + + function done() as Void { + runner = null; + MainMenu.idsScreen = null; + if (WatchUi.getCurrentView()[0] == _reading) { + WatchUi.switchToView(new InfoView("Entry IDs", _job.lines, "ids"), new InfoDelegate(), WatchUi.SLIDE_IMMEDIATE); + } + } +} + // After unlocking from the menu's "Change PIN": ask for the new PIN. class ChangePinAfterUnlock { diff --git a/source/ui/OtpView.mc b/source/ui/OtpView.mc index 1c0c0bd..d35e103 100644 --- a/source/ui/OtpView.mc +++ b/source/ui/OtpView.mc @@ -131,7 +131,7 @@ class OtpView extends WatchUi.View { dc.drawText(cx, y, smallFont, Ui.fit(dc, hint, smallFont, 2 * Ui.halfWidthAt(dc, y)), Graphics.TEXT_JUSTIFY_CENTER); y += smallH; } else { - dc.setColor(remaining <= NEXT_CODE_SECONDS ? Ui.WARN_COLOR : Ui.dim(), Graphics.COLOR_TRANSPARENT); + dc.setColor(remaining <= NEXT_CODE_SECONDS ? Ui.warn() : Ui.dim(), Graphics.COLOR_TRANSPARENT); dc.drawText(cx, y, infoFont, remaining + " s", Graphics.TEXT_JUSTIFY_CENTER); y += dc.getFontHeight(infoFont); if (remaining <= NEXT_CODE_SECONDS && cached != null) { @@ -232,7 +232,7 @@ class OtpView extends WatchUi.View { private function drawTimeLeft(dc as Graphics.Dc, remaining as Number, period as Number) as Void { var w = dc.getWidth(); var h = dc.getHeight(); - var color = remaining <= NEXT_CODE_SECONDS ? Ui.WARN_COLOR : Ui.accent(); + var color = remaining <= NEXT_CODE_SECONDS ? Ui.warn() : Ui.accent(); if (Ui.isRound()) { var r = w / 2 - RING_WIDTH / 2 - 1; dc.setPenWidth(RING_WIDTH); diff --git a/source/ui/PinView.mc b/source/ui/PinView.mc index fa58bfe..47b12fd 100644 --- a/source/ui/PinView.mc +++ b/source/ui/PinView.mc @@ -4,7 +4,8 @@ // README 13.1 and 13.4: // * A PIN has exactly 4 digits (KeyManager.PIN_DIGITS) and is submitted // as soon as the 4th is entered; there is no OK key. -// * Touch screens get a keypad (1-9, delete, 0). The START button does +// * Touch screens get a keypad (1-9, then 0 and delete in the bottom row, +// delete on the right as on phone keypads). The START button does // nothing there: a tap can also arrive as START, which used to submit a // PIN early. Watches without a touch screen get a digit selector: // UP/DOWN change the digit, START adds it, BACK deletes. @@ -32,9 +33,11 @@ class PinView extends WatchUi.View { // Selector positions 0-9 are digits; this one is "OK" (legacy PINs only). static const SELECT_OK = 10; - // Keypad keys besides the digits (OK for legacy PINs only). + // Keypad keys besides the digits (OK for legacy PINs only), and an + // empty slot. static const KEY_DELETE = -1; static const KEY_OK = -2; + static const KEY_NONE = -3; // Calibrate the PIN derivation to about this long on this watch. static const DERIVATION_TARGET_MS = 1000; static const CALIBRATION_SAMPLE = 200; @@ -358,7 +361,7 @@ class PinView extends WatchUi.View { var message = wait > 0 ? "Wrong PINs. Wait " + Ui.minutesSeconds(wait) : _message; if (message != null) { - dc.setColor(_messageIsError || wait > 0 ? Ui.WARN_COLOR : Ui.accent(), Graphics.COLOR_TRANSPARENT); + dc.setColor(_messageIsError || wait > 0 ? Ui.warn() : Ui.accent(), Graphics.COLOR_TRANSPARENT); dc.drawText(cx, y, Graphics.FONT_XTINY, Ui.fit(dc, message, Graphics.FONT_XTINY, 2 * Ui.halfWidthAt(dc, y)), Graphics.TEXT_JUSTIFY_CENTER); } @@ -372,6 +375,9 @@ class PinView extends WatchUi.View { if (!_shownLogged) { _shownLogged = true; + // For UI scripts: the state the dots and the title show (never digits). + Diag.data("pin_state", "mode=" + mode + " digits=" + _digits.size() + " repeat=" + (_first != null ? 1 : 0) + + " busy=" + (busy ? 1 : 0)); Diag.shown(wait > 0 ? "pin_wait" : (_message != null && _messageIsError ? "pin_error" : "pin")); } } @@ -394,19 +400,21 @@ class PinView extends WatchUi.View { keyW = keyH * 2; } var left = w / 2 - 3 * keyW / 2; - var labels = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "OK"]; - var values = [1, 2, 3, 4, 5, 6, 7, 8, 9, KEY_DELETE, 0, KEY_OK]; + // A legacy PIN needs OK: delete, 0, OK. Otherwise the 4th digit + // submits, and the bottom row is an empty slot, 0 and delete. + var values = legacy ? [1, 2, 3, 4, 5, 6, 7, 8, 9, KEY_DELETE, 0, KEY_OK] + : [1, 2, 3, 4, 5, 6, 7, 8, 9, KEY_NONE, 0, KEY_DELETE]; var font = Graphics.FONT_MEDIUM; _keys = [] as Array<Array<Number>>; var log = ""; for (var i = 0; i < 12; i++) { - if (values[i] == KEY_OK && !legacy) { - continue; // no OK key: the 4th digit submits; the slot stays empty + if (values[i] == KEY_NONE) { + continue; } var kx = left + (i % 3) * keyW; var ky = top + (i / 3) * keyH; _keys.add([kx, ky, keyW, keyH, values[i] as Number]); - var label = labels[i] as String; + var label = values[i] == KEY_OK ? "OK" : (values[i] as Number).toString(); var isAction = (values[i] as Number) < 0; dc.setColor(disabled ? (Ui.dark() ? 0x444444 : Graphics.COLOR_LT_GRAY) : (isAction ? Ui.accent() : Ui.fg()), Graphics.COLOR_TRANSPARENT); @@ -416,7 +424,7 @@ class PinView extends WatchUi.View { var f = isAction ? Graphics.FONT_SMALL : font; dc.drawText(kx + keyW / 2, ky + (keyH - dc.getFontHeight(f)) / 2, f, label, Graphics.TEXT_JUSTIFY_CENTER); } - log += (i > 0 ? ";" : "") + (values[i] as Number) + "=" + (kx + keyW / 2) + "," + (ky + keyH / 2); + log += (log.length() > 0 ? ";" : "") + (values[i] as Number) + "=" + (kx + keyW / 2) + "," + (ky + keyH / 2); } if (!_shownLogged) { Diag.data("pin_keys", log); @@ -444,7 +452,10 @@ class PinView extends WatchUi.View { private function drawSelector(dc as Graphics.Dc, top as Number, bottom as Number, disabled as Boolean) as Void { var cx = dc.getWidth() / 2; - var mid = (top + bottom) / 2; + // Centred above the hint, so the lower arrow never touches it (it + // did on the Forerunner 255S). + var hintTop = bottom - dc.getFontHeight(Graphics.FONT_XTINY); + var mid = (top + hintTop) / 2; var font = Graphics.FONT_NUMBER_MEDIUM; var label = _selector == SELECT_OK ? "OK" : _selector.toString(); if (_selector == SELECT_OK) { @@ -458,7 +469,7 @@ class PinView extends WatchUi.View { dc.fillPolygon([[cx - 8, mid + a], [cx + 8, mid + a], [cx, mid + a + 8]] as Array<Graphics.Point2D>); dc.setColor(Ui.dim(), Graphics.COLOR_TRANSPARENT); var hint = "START: add"; - dc.drawText(cx, bottom - dc.getFontHeight(Graphics.FONT_XTINY), Graphics.FONT_XTINY, + dc.drawText(cx, hintTop, Graphics.FONT_XTINY, Ui.fit(dc, hint, Graphics.FONT_XTINY, 2 * Ui.halfWidthAt(dc, bottom)), Graphics.TEXT_JUSTIFY_CENTER); } } diff --git a/source/ui/Ui.mc b/source/ui/Ui.mc index a8d1ec7..bf52520 100644 --- a/source/ui/Ui.mc +++ b/source/ui/Ui.mc @@ -15,6 +15,7 @@ module Ui { const ELLIPSIS = "…"; const WARN_COLOR = 0xFF5555; + const LIGHT_WARN_COLOR = 0xCC0000; // 0xFF5555 is too faint on white for small text const ACCENT_COLOR = 0x00AAAA; // the teal of the launcher icon const LIGHT_ACCENT_COLOR = 0x007777; @@ -71,6 +72,11 @@ module Ui { return dark() ? ACCENT_COLOR : LIGHT_ACCENT_COLOR; } + // Warnings (wrong PIN, the last seconds of a code) in the theme. + function warn() as Number { + return dark() ? WARN_COLOR : LIGHT_WARN_COLOR; + } + // Clear the screen in the theme's background. function clear(dc as Graphics.Dc) as Void { dc.setColor(fg(), bg()); @@ -142,7 +148,7 @@ module Ui { if (period <= 0 || remaining <= 0) { return; } - dc.setColor(remaining <= 5 ? WARN_COLOR : accent(), Graphics.COLOR_TRANSPARENT); + dc.setColor(remaining <= 5 ? warn() : accent(), Graphics.COLOR_TRANSPARENT); var pen = r / 2 > 1 ? r / 2 : 1; dc.setPenWidth(pen); var sweep = 360 * remaining / period; |
