import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'config.dart'; import 'models/sale.dart'; import 'state/app_state.dart'; import 'services/printer_service.dart'; import 'screens/login_screen.dart'; import 'screens/home_page.dart'; import 'utils/colors.dart'; import 'utils/format.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Supabase.initialize(url: supabaseUrl, publishableKey: supabaseAnonKey); await printerService.init(); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State with WidgetsBindingObserver { late final AppState _appState; final GlobalKey _scaffoldKey = GlobalKey(); @override void initState() { super.initState(); _appState = AppState(); WidgetsBinding.instance.addObserver(this); // Dengarkan stream notifikasi transaksi baru (dari HP lain / checkout). _appState.newSaleNotifications.stream.listen(_showSaleNotification); } void _showSaleNotification(Sale sale) { final messenger = _scaffoldKey.currentState; if (messenger == null) return; // Getar + bunyi notifikasi. HapticFeedback.heavyImpact(); SystemSound.play(SystemSoundType.click); // Banner notifikasi di atas layar. messenger.showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle, color: kSuccess, size: 20), const SizedBox(width: 10), Expanded( child: Text( 'Transaksi baru: ${sale.items.fold(0, (s, i) => s + i.qty)} item • ${formatRupiah(sale.total)}', style: const TextStyle(color: kText, fontWeight: FontWeight.w600), ), ), ], ), backgroundColor: kSurface, behavior: SnackBarBehavior.floating, duration: const Duration(seconds: 4), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), side: const BorderSide(color: kSuccess, width: 1), ), ), ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _appState.stopPolling(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { _appState.startPolling(); } else { _appState.stopPolling(); } } @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: _appState, child: MaterialApp( scaffoldMessengerKey: _scaffoldKey, title: 'Dutra Store', debugShowCheckedModeBanner: false, theme: ThemeData( useMaterial3: true, brightness: Brightness.dark, scaffoldBackgroundColor: kBackground, colorScheme: ColorScheme.dark( primary: kAccent, secondary: kSuccess, surface: kSurface, onSurface: kText, error: kDanger, ), appBarTheme: const AppBarTheme( backgroundColor: kBackground, foregroundColor: kText, elevation: 0, centerTitle: true, titleTextStyle: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: kText, ), ), cardTheme: CardThemeData( color: kSurface2, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(kRadius), side: const BorderSide(color: kBorder), ), ), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: kSurface2, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: kBorder), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: kAccent, width: 1.5), ), labelStyle: const TextStyle(color: kMuted), hintStyle: const TextStyle(color: kMuted), ), filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( backgroundColor: kAccent, foregroundColor: const Color(0xFF1A1206), padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), textStyle: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, ), ), ), dialogTheme: DialogThemeData( backgroundColor: kSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), titleTextStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: kText, ), ), bottomNavigationBarTheme: const BottomNavigationBarThemeData( backgroundColor: kSurface, selectedItemColor: kAccent, unselectedItemColor: kMuted, selectedLabelStyle: TextStyle(fontWeight: FontWeight.w600), type: BottomNavigationBarType.fixed, elevation: 0, ), ), home: const AuthGate(), ), ); } } class AuthGate extends StatelessWidget { const AuthGate({super.key}); @override Widget build(BuildContext context) { return Selector( selector: (context, state) => ( loading: state.loading, loggedIn: state.isLoggedIn, expired: state.sessionExpired, ), builder: (context, a, child) { // Loading global hanya untuk init aplikasi. Proses login dihandle // tombol LoginScreen, status data produk dihandle spinner khusus // Kasir agar layar tidak melepas LoginScreen saat gagal login. if (a.loading) { return const Scaffold( body: Center(child: CircularProgressIndicator(color: kAccent)), ); } if (a.loggedIn && !a.expired) { return const HomePage(); } return const LoginScreen(); }, ); } }