import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'app/app.dart'; import 'core/services/sound_service.dart'; import 'core/theme/app_colors.dart'; import 'core/theme/app_dimens.dart'; import 'data/datasources/local_store.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await _configureChrome(); // Startup work must never be able to prevent runApp from being reached. // Awaiting it unguarded means one thrown exception leaves a blank window // with nothing on screen to explain why. try { // Opens SQLite and loads the catalogue into memory. Bills written on a // previous run are still on disk and still counted as unsynced. await LocalStore.instance.init(); await SoundService.instance.preload(); } catch (error, stack) { debugPrint('Startup failed: $error\n$stack'); runApp(_StartupFailureApp(error: error)); return; } runApp(const ProviderScope(child: NearlePosApp())); } /// Locks the terminal into landscape and hides the system bars. /// /// Both calls are Android/iOS only. On desktop the window manager owns sizing /// and chrome, so we skip them — invoking them there is at best a no-op and can /// raise a MissingPluginException on some engine builds. Future _configureChrome() async { final isMobile = defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS; if (kIsWeb || !isMobile) return; await SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); } /// Shown when the terminal cannot start. /// /// A cashier staring at a white window has nothing to report to support, so /// the failure is put on screen verbatim. class _StartupFailureApp extends StatelessWidget { const _StartupFailureApp({required this.error}); final Object error; @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( backgroundColor: AppColors.background, body: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(AppSpacing.xxl), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 560), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 56, height: 56, decoration: const BoxDecoration( color: AppColors.dangerSurface, borderRadius: AppRadius.brMd, ), child: const Icon(Icons.error_outline_rounded, color: AppColors.danger, size: 28,), ), const SizedBox(height: AppSpacing.xl), const Text( 'Nearle POS could not start', style: TextStyle( fontSize: 22, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), const SizedBox(height: AppSpacing.sm), const Text( 'The terminal failed while opening its local database.', style: TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.5, ), ), const SizedBox(height: AppSpacing.xl), Container( width: double.infinity, padding: const EdgeInsets.all(AppSpacing.lg), decoration: BoxDecoration( color: AppColors.surface, borderRadius: AppRadius.brMd, border: Border.all(color: AppColors.border), ), child: SelectableText( '$error', style: const TextStyle( fontSize: 12.5, height: 1.6, fontFamily: 'monospace', color: AppColors.textPrimary, ), ), ), ], ), ), ), ), ), ); } }