import 'dart:async'; import 'dart:io'; import 'dart:ui'; 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 'package:intl/date_symbol_data_local.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'core/sacred_tokens.dart'; import 'data/local/models.dart'; import 'features/home/home_view.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); FlutterError.onError = (details) { FlutterError.presentError(details); debugPrint('[Fatal][FlutterError] ${details.exceptionAsString()}'); }; PlatformDispatcher.instance.onError = (error, stack) { debugPrint('[Fatal][PlatformDispatcher] $error'); debugPrintStack(stackTrace: stack); return true; }; ErrorWidget.builder = (details) { return const Material( color: SacredColors.background, child: Center( child: Padding( padding: EdgeInsets.all(32), child: Text( 'Terjadi gangguan tampilan.\nAplikasi tetap berjalan dalam mode aman.', textAlign: TextAlign.center, style: TextStyle( color: SacredColors.onSurface, fontSize: 24, fontWeight: FontWeight.w700, ), ), ), ), ); }; await runZonedGuarded(() async { await _bootstrapAndRun(); }, (error, stack) { debugPrint('[Fatal][Zone] $error'); debugPrintStack(stackTrace: stack); }); } Future _bootstrapAndRun() async { // Landscape-only for TV await SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); // Hide system overlays for full-screen kiosk mode await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); // Initialize Hive await Hive.initFlutter(); Hive.registerAdapter(AppSettingsAdapter()); Hive.registerAdapter(DailyPrayerScheduleAdapter()); await Hive.openBox(HiveBoxes.settings); await Hive.openBox(HiveBoxes.prayerSchedule); // Seed defaults if first launch final settingsBox = Hive.box(HiveBoxes.settings); if (settingsBox.get('default') == null) { await settingsBox.put('default', AppSettings()); } await _sanitizeMediaSettings(settingsBox); // Initialize date formatting for Indonesian locale await initializeDateFormatting('id_ID'); // Keep screen awake — CRITICAL for 24/7 TV operation await WakelockPlus.enable(); runApp( const ProviderScope( child: JamShalatApp(), ), ); } Future _sanitizeMediaSettings(Box settingsBox) async { final settings = settingsBox.get('default'); if (settings == null) return; final validSlides = settings.slideshowImages .where((path) => path.trim().isNotEmpty && File(path).existsSync()) .toList(); final brandedBg = (settings.brandedBgImage != null && settings.brandedBgImage!.trim().isNotEmpty && File(settings.brandedBgImage!).existsSync()) ? settings.brandedBgImage : null; final needsSave = validSlides.length != settings.slideshowImages.length || brandedBg != settings.brandedBgImage; if (!needsSave) return; await settingsBox.put( 'default', settings.copyWith( slideshowImages: validSlides, brandedBgImage: brandedBg, ), ); } class JamShalatApp extends ConsumerWidget { const JamShalatApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // textScaleProvider will be used selectively in child components. return MaterialApp( title: 'Jam Shalat Digital', debugShowCheckedModeBanner: false, theme: ThemeData( useMaterial3: true, fontFamily: 'Manrope', scaffoldBackgroundColor: SacredColors.background, colorScheme: const ColorScheme.dark( primary: SacredColors.primary, secondary: SacredColors.secondary, surface: SacredColors.surface, error: SacredColors.error, onPrimary: SacredColors.onPrimary, onSecondary: SacredColors.onSecondary, onSurface: SacredColors.onSurface, onError: Color(0xFF690005), ), ), home: const HomeView(), ); } }