import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../../core/theme/app_layout.dart'; import '../../../core/widgets/primary_button.dart'; import '../../sync/providers/sync_controller.dart'; import '../providers/navigation_provider.dart'; import '../widgets/category_chips.dart'; import '../widgets/customer_bar.dart'; import '../widgets/product_grid.dart'; import '../widgets/scan_toast.dart'; import '../widgets/search_field.dart'; /// Catalogue half of the terminal. /// /// Gated on the catalogue import: with no products loaded there is nothing to /// sell, so the cashier is sent to the import step instead of a broken grid. class PosView extends ConsumerWidget { const PosView({super.key, required this.layout, required this.searchFocus}); final PosLayout layout; final FocusNode searchFocus; @override Widget build(BuildContext context, WidgetRef ref) { if (!ref.watch(catalogueReadyProvider)) { return const _CatalogueRequired(); } final pad = layout.contentPadding; // Keep the last grid row clear of the floating bill button. final bottomInset = layout.billingIsSheet ? AppSizes.buttonHeightLarge + AppSpacing.xxxl : AppSpacing.xxl; return Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: EdgeInsets.fromLTRB(pad, AppSpacing.lg, pad, AppSpacing.md), child: PosSearchField(focusNode: searchFocus), ), CategoryChips(horizontalPadding: pad), const SizedBox(height: AppSpacing.md), Expanded( child: ProductGrid( horizontalPadding: pad, tileExtent: layout.gridTileExtent, bottomPadding: bottomInset, ), ), ], ), Positioned( left: 0, right: 0, bottom: bottomInset, child: const Align( alignment: Alignment.bottomCenter, child: ScanToast(), ), ), ], ); } } /// Shown until the catalogue has been pulled onto this terminal. class _CatalogueRequired extends ConsumerWidget { const _CatalogueRequired(); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(catalogueImportProvider); final running = state is ImportRunning; return Center( child: SingleChildScrollView( padding: const EdgeInsets.all(AppSpacing.xxl), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 460), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 96, height: 96, decoration: const BoxDecoration( color: AppColors.primarySurface, shape: BoxShape.circle, ), child: const Icon(Icons.cloud_download_outlined, size: 42, color: AppColors.primary,), ), const SizedBox(height: AppSpacing.xxl), Text( 'Import products to start billing', textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: AppSpacing.sm), const Text( 'This terminal has no catalogue yet. Pull the current products ' 'once at the start of your shift — after that everything runs ' 'offline.', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.6, ), ), const SizedBox(height: AppSpacing.xxl), if (running) ...[ Text( state.stage, style: const TextStyle( fontSize: 13, color: AppColors.textSecondary, ), ), const SizedBox(height: AppSpacing.sm), ClipRRect( borderRadius: AppRadius.brPill, child: LinearProgressIndicator( value: state.progress, minHeight: 8, backgroundColor: AppColors.divider, valueColor: const AlwaysStoppedAnimation( AppColors.primary,), ), ), const SizedBox(height: AppSpacing.lg), ], if (state is ImportFailed) ...[ Container( padding: const EdgeInsets.all(AppSpacing.md), margin: const EdgeInsets.only(bottom: AppSpacing.lg), decoration: const BoxDecoration( color: AppColors.dangerSurface, borderRadius: AppRadius.brSm, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon(Icons.wifi_off_rounded, color: AppColors.danger, size: 18,), const SizedBox(width: AppSpacing.sm), Expanded( child: Text( state.message, style: const TextStyle( color: AppColors.danger, fontSize: 13, height: 1.45, ), ), ), ], ), ), ], PrimaryButton( label: 'Import catalogue now', icon: Icons.cloud_download_rounded, large: true, busy: running, onPressed: running ? null : () => ref.read(catalogueImportProvider.notifier).run(), ), const SizedBox(height: AppSpacing.md), TextButton.icon( onPressed: () => ref .read(activeModuleProvider.notifier) .state = PosModule.productImport, icon: const Icon(Icons.open_in_new_rounded, size: 16), label: const Text('Open Product Import'), ), ], ), ), ), ); } }