import 'dart:async'; import 'dart:ui' show ViewFocusEvent, ViewFocusState; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; import '../core/providers/theme_provider.dart'; import '../data/local/hive_boxes.dart'; import '../data/local/models/app_settings.dart'; import '../data/services/notification_event_producer_service.dart'; import '../data/services/notification_service.dart'; import '../features/dashboard/data/prayer_times_provider.dart'; import 'router.dart'; import 'theme/app_theme.dart'; /// Root MaterialApp.router wired to GoRouter + ThemeMode from Riverpod. class App extends ConsumerStatefulWidget { const App({super.key}); @override ConsumerState createState() => _AppState(); } class _AppState extends ConsumerState with WidgetsBindingObserver { Timer? _midnightResyncTimer; DateTime? _lastPermissionCheckAt; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { HardwareKeyboard.instance.syncKeyboardState(); }); _scheduleMidnightResync(); NotificationService.instance.consumePendingLaunchRoute(); } @override void dispose() { _midnightResyncTimer?.cancel(); WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed || state == AppLifecycleState.inactive) { // Resync stale pressed-key state to avoid repeated KeyDown assertions. HardwareKeyboard.instance.syncKeyboardState(); } if (state == AppLifecycleState.resumed) { ref.invalidate(prayerTimesProvider); unawaited(ref.read(prayerTimesProvider.future)); _scheduleMidnightResync(); _checkNotificationPermissionOnResume(); } } @override void didChangeViewFocus(ViewFocusEvent event) { if (event.state == ViewFocusState.focused) { HardwareKeyboard.instance.syncKeyboardState(); } } void _scheduleMidnightResync() { _midnightResyncTimer?.cancel(); final now = DateTime.now(); final nextRun = DateTime(now.year, now.month, now.day, 0, 5).isAfter(now) ? DateTime(now.year, now.month, now.day, 0, 5) : DateTime(now.year, now.month, now.day + 1, 0, 5); final delay = nextRun.difference(now); _midnightResyncTimer = Timer(delay, () { ref.invalidate(prayerTimesProvider); unawaited(ref.read(prayerTimesProvider.future)); _scheduleMidnightResync(); }); } Future _checkNotificationPermissionOnResume() async { final now = DateTime.now(); if (_lastPermissionCheckAt != null && now.difference(_lastPermissionCheckAt!).inSeconds < 30) { return; // Throttle: max once per 30 seconds. } _lastPermissionCheckAt = now; try { final settings = Hive.box(HiveBoxes.settings) .get('default') ?? AppSettings(); if (!settings.adhanEnabled.values.any((v) => v)) return; final permissionStatus = await NotificationService.instance.getPermissionStatus(); await NotificationEventProducerService.instance .emitPermissionWarningsIfNeeded( settings: settings, permissionStatus: permissionStatus, ); } catch (_) { // Non-blocking: permission check on resume is best-effort. } } @override Widget build(BuildContext context) { final themeMode = ref.watch(themeProvider); return MaterialApp.router( title: 'JamShalat', debugShowCheckedModeBanner: false, theme: AppTheme.light, darkTheme: AppTheme.dark, themeMode: themeMode, routerConfig: appRouter, ); } }