summaryrefslogtreecommitdiff
path: root/source/settings/SettingsProcessor.mc
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-20 01:51:05 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-20 01:51:05 +0200
commitd537f4e154a40075405b0ab2fed14fbe2eeaac81 (patch)
treef1fb9b148871901dda96fca11401d6f4d0ee1d59 /source/settings/SettingsProcessor.mc
parent40d9a1d5c3e85486aea964c1446402a3be6ec05a (diff)
feat(settings): apply the Import form from the phone and show results
Import group in the app settings; the processor runs after the first frame and on settings changes, clears the fields, writes the status, remembers form hashes and opens the results screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source/settings/SettingsProcessor.mc')
-rw-r--r--source/settings/SettingsProcessor.mc150
1 files changed, 150 insertions, 0 deletions
diff --git a/source/settings/SettingsProcessor.mc b/source/settings/SettingsProcessor.mc
new file mode 100644
index 0000000..4c0a429
--- /dev/null
+++ b/source/settings/SettingsProcessor.mc
@@ -0,0 +1,150 @@
+// Author: cirakg & claude ai
+// Summary: Applies the phone's settings forms on the watch (README 10.3); in M5 the Import form.
+//
+// Rules (README 10.3):
+// * Runs from onSettingsChanged and at every start, after the first frame.
+// * A form is applied only when its switch ("Import now") is on. Afterwards
+// the watch turns the switch off and clears the text fields (they hold
+// secrets) and writes a short result to the status field.
+// * Idempotency: a hash of every applied form is remembered (the last 32),
+// so the same form sent again does nothing.
+// * The import runs as a job on a timer; when it ends, the results are
+// stored and the results screen opens.
+// Only the full app runs this, never the glance.
+
+import Toybox.Application;
+import Toybox.Lang;
+import Toybox.System;
+import Toybox.WatchUi;
+
+class SettingsProcessor {
+
+ static const TEXT_FIELDS = 8;
+ static const FORM_LOG_KEY = "fh";
+ static const FORM_LOG_SIZE = 32;
+ static const STATUS_MAX_CHARS = 200;
+
+ private var _job as Importer? = null;
+ // The job's timer driver (kept so it isn't collected while it runs).
+ var runner as Jobs.JobRunner? = null;
+ private var _formHash as String = "";
+ private var _startedAt as Number = 0;
+
+ function initialize() {
+ }
+
+ function isBusy() as Boolean {
+ return _job != null;
+ }
+
+ // "Importing… 40%" while an import runs, else null.
+ function busyText() as String? {
+ var job = _job;
+ return job != null ? "Importing… " + job.percent() + "%" : null;
+ }
+
+ // Look at the forms and start whatever they ask for.
+ function start() as Void {
+ if (_job != null || AppState.vault == null) {
+ return;
+ }
+ if (!isTrue(prop("impApply"))) {
+ return;
+ }
+ var mode = prop("impMode");
+ var locked = isTrue(prop("impLocked"));
+ var importMode = mode instanceof Number && mode >= Importer.MODE_APPEND && mode <= Importer.MODE_CANCEL ? mode : Importer.MODE_APPEND;
+ var vault = AppState.theVault();
+ if (importMode == Importer.MODE_CANCEL) {
+ ImportBuffer.clear(vault.storage());
+ finish(["Waiting import cancelled."], "Waiting import cancelled", false);
+ return;
+ }
+ var text = "";
+ for (var i = 1; i <= TEXT_FIELDS; i++) {
+ var part = prop("impText" + i);
+ if (part instanceof String) {
+ text += part;
+ }
+ }
+ _formHash = formHash(importMode + "|" + locked + "|" + text);
+ if (alreadyApplied(_formHash)) {
+ finish(["This import was already applied. Change the text to import again."], "Already imported", false);
+ return;
+ }
+ _startedAt = System.getTimer();
+ var job = new Importer(vault, text, importMode, locked, Clock.nowUnix(0));
+ _job = job;
+ var r = new Jobs.JobRunner(job, 1, method(:onImportDone));
+ runner = r;
+ r.start();
+ WatchUi.requestUpdate();
+ }
+
+ function onImportDone() as Void {
+ var job = _job as Importer;
+ _job = null;
+ runner = null;
+ var report = job.report;
+ Diag.importDone(report.imported, System.getTimer() - _startedAt);
+ if (report.failure == null && report.waitingChars < 0) {
+ rememberApplied(_formHash);
+ }
+ AppState.theCodes().clear();
+ finish(report.lines(), report.summary(), true);
+ }
+
+ // Clear the form, write the status, store and show the results.
+ private function finish(lines as Array<String>, status as String, clearText as Boolean) as Void {
+ if (clearText) {
+ for (var i = 1; i <= TEXT_FIELDS; i++) {
+ setProp("impText" + i, "");
+ }
+ }
+ setProp("impApply", false);
+ setProp("impStatus", Text.clip(status, STATUS_MAX_CHARS));
+ Results.save(lines);
+ WatchUi.pushView(new InfoView("Results", lines, "results"), new InfoDelegate(), WatchUi.SLIDE_UP);
+ Results.markSeen();
+ }
+
+ private function isTrue(value as Application.Properties.ValueType or Null) as Boolean {
+ return value instanceof Boolean && value;
+ }
+
+ private function prop(key as String) as Application.Properties.ValueType or Null {
+ try {
+ return Application.Properties.getValue(key);
+ } catch (e instanceof Lang.Exception) {
+ return null;
+ }
+ }
+
+ private function setProp(key as String, value as Application.Properties.ValueType) as Void {
+ try {
+ Application.Properties.setValue(key, value);
+ } catch (e instanceof Lang.Exception) {
+ // A property the phone hasn't got yet: nothing to clear.
+ }
+ }
+
+ // Short hash of a form's content (SHA-256, first 8 bytes, hex).
+ static function formHash(content as String) as String {
+ return Bytes.toHex(Hashes.digest(Hashes.SHA256, Bytes.fromUtf8(content)).slice(0, 8));
+ }
+
+ private function alreadyApplied(hash as String) as Boolean {
+ var log = Application.Storage.getValue(FORM_LOG_KEY);
+ return log instanceof Array && log.indexOf(hash) >= 0;
+ }
+
+ private function rememberApplied(hash as String) as Void {
+ var stored = Application.Storage.getValue(FORM_LOG_KEY);
+ var log = stored instanceof Array ? stored as Array<Application.Storage.ValueType> : [] as Array<Application.Storage.ValueType>;
+ log.add(hash);
+ if (log.size() > FORM_LOG_SIZE) {
+ log = log.slice(log.size() - FORM_LOG_SIZE, null) as Array<Application.Storage.ValueType>;
+ }
+ Application.Storage.setValue(FORM_LOG_KEY, log);
+ }
+}