Add a back-office setup dialog, and clear the last lints

Settings > Connectivity > Configure now points a terminal at a back office.
Until this existed a store was wired up by editing syncConfigProvider and
rebuilding, which made every terminal in a fleet its own build.

- Transport picker (offline demo / HTTP / MQTT) with only the relevant fields
  shown, validated: an MQTT route with no host is refused rather than silently
  saved, because a terminal pointed at nothing looks exactly like one that is
  merely offline.
- Terminal name and store id are editable and persist to the database. The
  device id and terminal code are shown but not editable, with a copy button —
  they are what a support call needs, and re-coding a till must not orphan the
  bills already written under the old code.
- TLS defaults on, with a note that bills carry customer names and numbers.
- About card now shows the real terminal, device id and store instead of the
  literal TERM-01, and the dead "Check for updates" button is now the entry
  point to this dialog.

Lints cleared, analyzer now reports zero issues:
- SoundService wrapped a plain bool in a getter and setter that did nothing.
- Two post-await guards used context.mounted inside a State, which the
  analyzer cannot relate to the State's own lifetime. Both are now `mounted`.

Tests: 140 -> 141. The new widget test drives the dialog end to end and asserts
that saving an MQTT route with no host keeps the dialog open with the error
visible. Suite run three times clean.

Known gap, documented in docs/sync-contract.md: broker credentials live in
memory and must be re-entered after a restart. Persisting them means
encrypting at rest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Suriya
2026-08-01 11:47:11 +05:30
parent 30d6d1080f
commit 33467e6963
6 changed files with 440 additions and 17 deletions

View File

@@ -132,4 +132,40 @@ void main() {
expect(tester.takeException(), isNull, reason: 'opening "$label" threw');
}
});
testWidgets('the back office connection dialog opens and validates',
(tester) async {
// The only way a shop can point a till at a broker. Until it existed a
// store was wired up by editing a provider and rebuilding.
tester.view.physicalSize = const Size(1800, 1200);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await bootApp(tester);
await signIn(tester);
await tester.tap(find.text('Settings').first);
await settle(tester);
await tester.tap(find.text('Configure').first);
await settle(tester);
// "Back office connection" is both the dialog title and the About card's
// button, so match the dialog itself.
expect(find.byType(AlertDialog), findsOneWidget);
expect(find.text('MQTT'), findsOneWidget);
// Switching to MQTT and saving with no host must be refused, not silently
// accepted — a terminal pointed at nothing looks identical to one that is
// simply offline.
await tester.tap(find.text('MQTT'));
await settle(tester);
await tester.tap(find.text('Save'));
await settle(tester);
expect(find.text('A broker host is required'), findsOneWidget);
expect(find.byType(AlertDialog), findsOneWidget,
reason: 'the dialog must stay open on a validation failure',);
expect(tester.takeException(), isNull);
});
}