import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../app/providers.dart'; import '../../../core/constants/app_constants.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../../core/utils/formatters.dart'; import '../../../domain/entities/store_account.dart'; import '../../../domain/entities/sync_event.dart'; import '../../auth/providers/auth_controller.dart'; import '../../sync/providers/sync_controller.dart'; import '../widgets/module_widgets.dart'; /// Terminal and store configuration. class SettingsView extends ConsumerStatefulWidget { const SettingsView({super.key}); @override ConsumerState createState() => _SettingsViewState(); } class _SettingsViewState extends ConsumerState { bool _scannerSound = true; bool _autoPrint = true; bool _openDrawer = true; bool _roundOff = true; bool _autoLoyalty = true; bool _offline = false; @override Widget build(BuildContext context) { final store = ref.watch(currentStoreProvider); final user = ref.watch(currentUserProvider); return ModulePage( children: [ LayoutBuilder( builder: (context, constraints) { final wide = constraints.maxWidth >= 1040; final left = Column( children: [ _storeCard(store), const SizedBox(height: AppSpacing.lg), _taxCard(), const SizedBox(height: AppSpacing.lg), _loyaltyCard(), ], ); final right = Column( children: [ _hardwareCard(), const SizedBox(height: AppSpacing.lg), _connectivityCard(), const SizedBox(height: AppSpacing.lg), _staffCard(store, user), const SizedBox(height: AppSpacing.lg), _aboutCard(), ], ); if (!wide) { return Column( children: [left, const SizedBox(height: AppSpacing.lg), right], ); } return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: left), const SizedBox(width: AppSpacing.lg), Expanded(child: right), ], ); }, ), ], ); } Widget _storeCard(StoreAccount? store) => PanelCard( title: 'Store details', subtitle: 'Printed on every invoice', action: TextButton(onPressed: () {}, child: const Text('Edit')), child: Column( mainAxisSize: MainAxisSize.min, children: [ _row('Store name', store?.name ?? AppConstants.storeName), _row('Address', store?.address ?? AppConstants.storeAddress), _row('GSTIN', store?.gstin ?? AppConstants.storeGstin, mono: true), _row('Phone', store?.phone ?? AppConstants.storePhone, mono: true), _row('Plan', store?.plan ?? 'Business'), ], ), ); Widget _taxCard() => PanelCard( title: 'Tax & pricing', child: Column( mainAxisSize: MainAxisSize.min, children: [ _row( 'Default GST slab', Formatters.percent(AppConstants.defaultGstRate), ), _row('Prices include tax', 'Yes'), _toggle( 'Round bills to nearest rupee', 'Shows the adjustment as a Round Off line', _roundOff, (v) => setState(() => _roundOff = v), ), ], ), ); Widget _loyaltyCard() => PanelCard( title: 'Loyalty programme', child: Column( mainAxisSize: MainAxisSize.min, children: [ _row( 'Earn rate', '1 point per ' '${Formatters.money(AppConstants.loyaltyRupeesPerPoint)}', ), _row( 'Point value', Formatters.money(AppConstants.loyaltyPointValue), ), _toggle( 'Apply tier discount automatically', 'Silver 2%, Gold 5%, Platinum 8%', _autoLoyalty, (v) => setState(() => _autoLoyalty = v), ), ], ), ); Widget _hardwareCard() => PanelCard( title: 'Hardware & peripherals', child: Column( mainAxisSize: MainAxisSize.min, children: [ _toggle( 'Scanner beep', 'Audible confirmation on every scan', _scannerSound, (v) => setState(() => _scannerSound = v), ), _toggle( 'Print receipt automatically', 'Sends to the default roll printer with no dialog', _autoPrint, (v) => setState(() => _autoPrint = v), ), _toggle( 'Open cash drawer on cash sales', 'Sends the ESC/POS kick pulse', _openDrawer, (v) => setState(() => _openDrawer = v), ), const Divider(height: AppSpacing.xxl), _row('Receipt printer', 'EPSON TM-T82 (default)'), _row('Barcode scanner', 'Keyboard wedge · detected'), _row('Cash drawer', 'Connected via printer'), ], ), ); Widget _staffCard(StoreAccount? store, StaffUser? current) => PanelCard( title: 'Users & roles', action: TextButton(onPressed: () {}, child: const Text('Manage')), child: ResponsiveTable( stackBelow: 360, columns: const [ TableCol('Name', flex: 3), TableCol('Role', flex: 3), TableCol('', flex: 2, numeric: true), ], rows: (store?.staff ?? const []) .map((s) => [ Cell(s.name, bold: true), Cell(s.role.label, color: AppColors.textSecondary), s.id == current?.id ? const TagChip('Signed in', color: AppColors.success) : const SizedBox.shrink(), ]) .toList(), ), ); Widget _connectivityCard() { final ready = ref.watch(catalogueReadyProvider); final lastImport = ref.watch(lastImportAtProvider); final outstanding = ref .watch(syncEventsProvider) .where((e) => e.status != SyncStatus.synced) .length; return PanelCard( title: 'Connectivity & sync', subtitle: 'This terminal only needs a connection to import the ' 'catalogue and to push the shift report.', child: Column( mainAxisSize: MainAxisSize.min, children: [ _row('Catalogue', ready ? 'Loaded' : 'Not imported'), _row( 'Last import', lastImport == null ? 'Never' : Formatters.dateTime(lastImport), ), _row('Outstanding pushes', '$outstanding'), _toggle( 'Simulate offline', 'Forces import and push to fail, so you can confirm nothing is ' 'lost when the network drops', _offline, (v) { setState(() => _offline = v); ref.read(remoteCatalogueProvider).simulateOffline = v; ref.read(remoteReportSinkProvider).simulateOffline = v; }, ), ], ), ); } Widget _aboutCard() => PanelCard( title: 'About', child: Column( mainAxisSize: MainAxisSize.min, children: [ _row('Application', '${AppConstants.appName} 1.0.0'), _row('Terminal', 'TERM-01'), _row('Data store', 'Local — offline first'), 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'), ), ), ], ), ); Widget _row(String label, String value, {bool mono = false}) => Padding( padding: const EdgeInsets.symmetric(vertical: AppSpacing.sm), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 148, child: Text( label, style: const TextStyle( fontSize: 13, color: AppColors.textSecondary, ), ), ), Expanded( child: Text( value, textAlign: TextAlign.right, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textPrimary, fontFamily: mono ? 'monospace' : null, ), ), ), ], ), ); Widget _toggle( String title, String subtitle, bool value, ValueChanged onChanged, ) => Padding( padding: const EdgeInsets.symmetric(vertical: AppSpacing.xs), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 13.5, fontWeight: FontWeight.w500, ), ), Text( subtitle, style: const TextStyle( fontSize: 11.5, color: AppColors.textTertiary, height: 1.4, ), ), ], ), ), const SizedBox(width: AppSpacing.md), Switch(value: value, onChanged: onChanged), ], ), ); }