import 'package:flutter/material.dart'; import 'dart:async'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/foundation.dart'; import 'package:provider/provider.dart'; import 'firebase_options.dart'; import 'providers/auth_provider.dart'; import 'providers/kandang_provider.dart'; import 'providers/panen_provider.dart'; import 'providers/penjadwalan_provider.dart'; import 'providers/riwayat_provider.dart'; import 'services/notification_service.dart'; import 'services/panen_runtime_scheduler_service.dart'; // import 'services/panen_scheduler.dart'; // TODO: Enable after scheduler fixed import 'screens/landing_page.dart'; import 'screens/login_page.dart'; import 'screens/signup_page.dart'; import 'screens/home_page.dart'; import 'screens/panen_debug_screen.dart'; import 'screens/notification_debug_screen.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); // Realtime DB persistence tidak didukung di semua platform (terutama web). if (!kIsWeb && (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS)) { FirebaseDatabase.instance.setPersistenceEnabled(true); } if (!kIsWeb) { await NotificationService.instance.initialize(); } // Initialize background scheduler untuk panen otomatis // TODO: Enable setelah scheduler stabil // await PanenScheduler.initialize(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => AuthProvider()), ChangeNotifierProvider(create: (_) => KandangProvider()), ChangeNotifierProvider(create: (_) => PanenProvider()), ChangeNotifierProvider(create: (_) => PenjadwalanProvider()), ChangeNotifierProvider(create: (_) => TelurProvider()), ], child: MaterialApp( title: 'TEFA UNGGAS POLIJE', debugShowCheckedModeBanner: false, theme: ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFFF4D03F), brightness: Brightness.light, ), ), home: const AuthWrapper(), routes: { '/landing': (context) => const LandingPage(), '/login': (context) => const LoginPage(), '/signup': (context) => const SignupPage(), '/home': (context) => const AuthWrapper(), '/debug/panen': (context) => const PanenDebugScreen(), '/debug/notifikasi': (context) => const NotificationDebugScreen(), }, ), ); } } class AuthWrapper extends StatelessWidget { const AuthWrapper({super.key}); @override Widget build(BuildContext context) { return Consumer( builder: (context, authProvider, _) { if (authProvider.isLoading) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } if (authProvider.isLoggedIn) { final userId = authProvider.user?.uid; if (userId == null) { return const LandingPage(); } return HomeBootstrap(userId: userId); } return const LandingPage(); }, ); } } class HomeBootstrap extends StatefulWidget { final String userId; const HomeBootstrap({super.key, required this.userId}); @override State createState() => _HomeBootstrapState(); } class _HomeBootstrapState extends State { late Future _initFuture; PenjadwalanProvider? _penjadwalanProvider; final Set _scheduledReminderKeys = {}; Timer? _scheduleSyncDebounce; @override void initState() { super.initState(); _initFuture = _initializeData(); } Future _initializeData() async { final penjadwalanProvider = context.read(); final kandangProvider = context.read(); final panenProvider = context.read(); await penjadwalanProvider.initializeWithUser(widget.userId); await kandangProvider.initializeWithUser(widget.userId); await panenProvider.loadTodaySnapshots(); await panenProvider.restorePanenHistoryFromFirebase(); await panenProvider.startRealtimePanenNotifications(); _attachPenjadwalanListener(penjadwalanProvider); await _syncDailyHarvestReminders(penjadwalanProvider); await PanenRuntimeSchedulerService.start( panenProvider: panenProvider, kandangProvider: kandangProvider, penjadwalanProvider: penjadwalanProvider, ); } void _attachPenjadwalanListener(PenjadwalanProvider provider) { if (identical(_penjadwalanProvider, provider)) return; _penjadwalanProvider?.removeListener(_onPenjadwalanChanged); _penjadwalanProvider = provider; _penjadwalanProvider?.addListener(_onPenjadwalanChanged); } void _onPenjadwalanChanged() { _scheduleSyncDebounce?.cancel(); _scheduleSyncDebounce = Timer(const Duration(seconds: 1), () async { final provider = _penjadwalanProvider; if (!mounted || provider == null) return; await _syncDailyHarvestReminders(provider); }); } (int, int)? _tryParseHourMinute(String rawJam) { final match = RegExp(r'(\d{1,2})[:.](\d{2})').firstMatch(rawJam.trim()); if (match == null) return null; final hour = int.tryParse(match.group(1)!); final minute = int.tryParse(match.group(2)!); if (hour == null || minute == null) return null; if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null; return (hour, minute); } Future _syncDailyHarvestReminders(PenjadwalanProvider provider) async { if (kDebugMode) { print('📱 [BOOTSTRAP] Syncing harvest reminders...'); } final activeSchedules = provider.penjadwalans.where((s) => s.aktif).toList(); final nextKeys = {}; for (final schedule in activeSchedules) { final scheduleKey = 'panen:${schedule.id}:${schedule.kandangId}'; nextKeys.add(scheduleKey); } final removedKeys = _scheduledReminderKeys.difference(nextKeys).toList(); for (final removedKey in removedKeys) { await NotificationService.instance .cancelScheduledNotification(removedKey); _scheduledReminderKeys.remove(removedKey); } for (final schedule in activeSchedules) { final parsed = _tryParseHourMinute(schedule.jam); if (parsed == null) { if (kDebugMode) { print('⚠️ [BOOTSTRAP] Invalid time format: ${schedule.jam}'); } continue; } final hour = parsed.$1; final minute = parsed.$2; final jenisPanen = hour < 12 ? 'pagi' : 'sore'; final scheduleKey = 'panen:${schedule.id}:${schedule.kandangId}'; try { await NotificationService.instance.scheduleDailyNotificationAtTime( scheduleKey: scheduleKey, title: 'Panen ${jenisPanen[0].toUpperCase()}${jenisPanen.substring(1)}', body: 'Jadwal panen ${schedule.kandangNama} dimulai pukul ${schedule.jam}. Buka aplikasi untuk detail.', hour: hour, minute: minute, payload: 'panen_schedule:${schedule.id}', ); _scheduledReminderKeys.add(scheduleKey); if (kDebugMode) { print( '✅ [BOOTSTRAP] Scheduled reminder: $jenisPanen ${schedule.kandangNama} @ ${schedule.jam}'); } } catch (e) { if (kDebugMode) { print('❌ [BOOTSTRAP] Failed schedule ${schedule.id}: $e'); } } } final pending = await NotificationService.instance.pendingNotifications(); if (kDebugMode) { print( '📱 [BOOTSTRAP] Active schedules: ${activeSchedules.length}, pending notifications: ${pending.length}'); print('✅ [BOOTSTRAP] Sync reminders done'); } } @override void dispose() { _scheduleSyncDebounce?.cancel(); _penjadwalanProvider?.removeListener(_onPenjadwalanChanged); PanenRuntimeSchedulerService.stop(); context.read().stopRealtimePanenNotifications(); super.dispose(); } @override Widget build(BuildContext context) { return FutureBuilder( future: _initFuture, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } if (snapshot.hasError) { return Scaffold( body: Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Gagal memuat data awal. Coba buka ulang aplikasi.', textAlign: TextAlign.center, ), const SizedBox(height: 12), ElevatedButton( onPressed: () { setState(() { _initFuture = _initializeData(); }); }, child: const Text('Coba Lagi'), ), ], ), ), ), ); } return const HomePage(); }, ); } }