import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/router/app_router.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../auth/providers/auth_controller.dart'; import '../../pos/providers/cart_controller.dart'; import '../../pos/providers/catalog_providers.dart'; import '../../pos/providers/navigation_provider.dart'; import '../../sync/providers/sync_controller.dart'; import '../../sync/widgets/sign_out_dialog.dart'; /// Asks what "signing out" means before doing it. /// /// Both cashier paths clear the products — the till never keeps a catalogue /// across a sign-out, whatever the reason. What they differ on is the drawer: /// a temporary logout locks the screen and leaves the money alone, while /// ending the shift counts it and reconciles it against what was rung. /// Treating both as one button meant either the drawer was never settled, or a /// cashier stepping away for ten minutes had to count it first. /// /// Admins see the plain sign-out dialog: they have no drawer to settle, and /// their sign-out deliberately leaves the catalogue in place for whoever picks /// the terminal up. Future showSessionEndSheet(BuildContext context, WidgetRef ref) async { if (!ref.read(isCashierModeProvider)) { return showSignOutDialog(context, ref); } return showDialog( context: context, barrierDismissible: true, builder: (_) => const _SessionEndDialog(), ); } class _SessionEndDialog extends ConsumerWidget { const _SessionEndDialog(); /// Locks the screen and returns to the login screen. /// /// Explicitly *not* a shift end — the drawer is left alone, unsynced bills /// stay queued, and today's totals keep accumulating against the same day. /// The catalogue still goes: a terminal sitting unattended at a login screen /// must not be holding a shop's prices and stock, and an admin re-imports in /// seconds. Future _temporaryLogout(BuildContext context, WidgetRef ref) async { ref.read(cartControllerProvider.notifier).reset(); await ref.read(authControllerProvider.notifier).signOut(); ref.read(catalogueVersionProvider.notifier).state++; ref.invalidate(allProductsProvider); ref.invalidate(visibleProductsProvider); ref.invalidate(categoryCountsProvider); ref.invalidate(lowStockProductsProvider); ref.read(activeModuleProvider.notifier).state = PosModule.pos; ref.read(searchQueryProvider.notifier).state = ''; ref.read(selectedCategoryProvider.notifier).state = null; if (!context.mounted) return; Navigator.of(context).pop(); context.go(AppRoutes.login); } @override Widget build(BuildContext context, WidgetRef ref) { final pending = ref.watch(unsyncedCountProvider).value ?? 0; final cart = ref.watch(cartControllerProvider); return AlertDialog( title: const Text('Leaving the till'), contentPadding: const EdgeInsets.fromLTRB( AppSpacing.xxl, AppSpacing.lg, AppSpacing.xxl, AppSpacing.sm, ), content: SizedBox( width: (MediaQuery.sizeOf(context).width - 96).clamp(280.0, 460.0), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (cart.isNotEmpty) Container( margin: const EdgeInsets.only(bottom: AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md), decoration: const BoxDecoration( color: AppColors.warningSurface, borderRadius: AppRadius.brSm, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon(Icons.warning_amber_rounded, size: 18, color: AppColors.warning,), const SizedBox(width: AppSpacing.sm), Expanded( child: Text( 'The current bill has ${cart.lineCount} item(s), and ' 'the imported products are cleared either way.', style: const TextStyle( fontSize: 12.5, color: AppColors.warning, height: 1.45, ), ), ), ], ), ), _Choice( icon: Icons.lock_outline_rounded, tone: AppColors.info, title: 'Temporary logout', body: 'Locks the screen. The drawer is left as it is and the ' 'day keeps running — an admin re-imports the products when ' 'you come back.', onTap: () => _temporaryLogout(context, ref), ), const SizedBox(height: AppSpacing.md), _Choice( icon: Icons.point_of_sale_rounded, tone: AppColors.primary, title: 'End shift', body: pending == 0 ? 'Count the drawer. It has to match what was rung before ' 'the till can be handed over.' : 'Count the drawer and upload the $pending bill(s) still ' 'held here. The count has to match before you can sign ' 'out.', onTap: () { Navigator.of(context).pop(); context.push(AppRoutes.endShift); }, emphasised: true, ), ], ), ), ), actionsPadding: const EdgeInsets.fromLTRB( AppSpacing.xxl, 0, AppSpacing.xxl, AppSpacing.lg, ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Stay signed in'), ), ], ); } } class _Choice extends StatelessWidget { const _Choice({ required this.icon, required this.tone, required this.title, required this.body, required this.onTap, this.emphasised = false, }); final IconData icon; final Color tone; final String title; final String body; final VoidCallback onTap; final bool emphasised; @override Widget build(BuildContext context) { return Material( color: emphasised ? AppColors.primarySurface : AppColors.surfaceAlt, borderRadius: AppRadius.brLg, child: InkWell( onTap: onTap, borderRadius: AppRadius.brLg, child: Container( padding: const EdgeInsets.all(AppSpacing.lg), decoration: BoxDecoration( borderRadius: AppRadius.brLg, border: Border.all( color: emphasised ? AppColors.primaryBorder : AppColors.border, ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 22, color: tone), const SizedBox(width: AppSpacing.md), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Text( body, style: const TextStyle( fontSize: 12.5, color: AppColors.textSecondary, height: 1.5, ), ), ], ), ), const SizedBox(width: AppSpacing.sm), const Icon(Icons.chevron_right_rounded, size: 20, color: AppColors.textTertiary,), ], ), ), ), ); } }