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:
@@ -11,6 +11,7 @@ import '../../../data/local/order_dao.dart';
|
||||
import '../../../domain/entities/store_account.dart';
|
||||
import '../../auth/providers/auth_controller.dart';
|
||||
import '../providers/printer_settings.dart';
|
||||
import '../widgets/back_office_dialog.dart';
|
||||
import '../../sync/providers/sync_controller.dart';
|
||||
import '../widgets/module_widgets.dart';
|
||||
|
||||
@@ -238,7 +239,7 @@ class _SettingsViewState extends ConsumerState<SettingsView> {
|
||||
.read(receiptServiceProvider)
|
||||
.printTestPage(
|
||||
printerUrl: settings.printerUrl,);
|
||||
if (!context.mounted) return;
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(SnackBar(
|
||||
@@ -321,6 +322,7 @@ class _SettingsViewState extends ConsumerState<SettingsView> {
|
||||
final lastImport = ref.watch(lastImportAtProvider);
|
||||
final outstanding = ref.watch(unsyncedCountProvider).value ?? 0;
|
||||
final config = ref.watch(syncConfigProvider);
|
||||
final terminal = ref.watch(terminalIdentityProvider);
|
||||
final sync = ref.watch(syncEngineStateProvider).value ??
|
||||
ref.watch(syncEngineProvider).state;
|
||||
|
||||
@@ -328,6 +330,10 @@ class _SettingsViewState extends ConsumerState<SettingsView> {
|
||||
title: 'Connectivity & sync',
|
||||
subtitle: 'Bills are written to this terminal first and uploaded in the '
|
||||
'background. Nothing is ever held up waiting for the network.',
|
||||
action: TextButton(
|
||||
onPressed: () => showBackOfficeDialog(context),
|
||||
child: const Text('Configure'),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -336,6 +342,7 @@ class _SettingsViewState extends ConsumerState<SettingsView> {
|
||||
'Last import',
|
||||
lastImport == null ? 'Never' : Formatters.dateTime(lastImport),
|
||||
),
|
||||
_row('Terminal', '${terminal.name} · ${terminal.code}'),
|
||||
_row('Route', _transportLabel(config)),
|
||||
_row('Waiting to upload', '$outstanding bill(s)'),
|
||||
_row(
|
||||
@@ -386,26 +393,35 @@ class _SettingsViewState extends ConsumerState<SettingsView> {
|
||||
'${config.useTls ? ' (TLS)' : ''}',
|
||||
};
|
||||
|
||||
Widget _aboutCard() => PanelCard(
|
||||
Widget _aboutCard() {
|
||||
final terminal = ref.watch(terminalIdentityProvider);
|
||||
|
||||
return PanelCard(
|
||||
title: 'About',
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_row('Application', '${AppConstants.appName} 1.0.0'),
|
||||
_row('Terminal', 'TERM-01'),
|
||||
_row('Data store', 'SQLite (on device)'),
|
||||
_row('Application',
|
||||
'${AppConstants.appName} ${AppConstants.appVersion}',),
|
||||
_row('Terminal', '${terminal.name} (${terminal.code})'),
|
||||
// The identifier support asks for. Stable for the life of the
|
||||
// device, and the only thing that ties this till to its history.
|
||||
_row('Device ID', terminal.deviceId, mono: true),
|
||||
_row('Store', terminal.storeId),
|
||||
_row('Data store', 'SQLite (on device, WAL)'),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.sync_rounded, size: 17),
|
||||
label: const Text('Check for updates'),
|
||||
onPressed: () => showBackOfficeDialog(context),
|
||||
icon: const Icon(Icons.settings_ethernet_rounded, size: 17),
|
||||
label: const Text('Back office connection'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _row(String label, String value, {bool mono = false}) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: AppSpacing.sm),
|
||||
|
||||
Reference in New Issue
Block a user