import 'dart:async'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:timezone/data/latest.dart'; import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; import 'package:immich_mobile/modules/backup/models/backup_album.model.dart'; import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/tab_navigation_observer.dart'; import 'package:immich_mobile/shared/cache/widgets_binding.dart'; import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/android_device_asset.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/etag.dart'; import 'package:immich_mobile/shared/models/exif_info.dart'; import 'package:immich_mobile/shared/models/ios_device_asset.dart'; import 'package:immich_mobile/shared/models/logger_message.model.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/providers/app_state.provider.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/services/immich_logger.service.dart'; import 'package:immich_mobile/shared/services/local_notification.service.dart'; import 'package:immich_mobile/utils/http_ssl_cert_override.dart'; import 'package:immich_mobile/utils/immich_app_theme.dart'; import 'package:immich_mobile/utils/migration.dart'; import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; void main() async { ImmichWidgetsBinding(); final db = await loadDb(); await initApp(); await migrateDatabaseIfNeeded(db); HttpOverrides.global = HttpSSLCertOverride(); runApp( ProviderScope( overrides: [dbProvider.overrideWithValue(db)], child: const MainWidget(), ), ); } Future initApp() async { await EasyLocalization.ensureInitialized(); if (kReleaseMode && Platform.isAndroid) { try { await FlutterDisplayMode.setHighRefreshRate(); debugPrint("Enabled high refresh mode"); } catch (e) { debugPrint("Error setting high refresh rate: $e"); } } // Initialize Immich Logger Service ImmichLogger(); var log = Logger("ImmichErrorLogger"); FlutterError.onError = (details) { FlutterError.presentError(details); log.severe( 'FlutterError - Catch all error: ${details.toString()} - ${details.exception} - ${details.library} - ${details.context} - ${details.stack}', details, details.stack, ); }; PlatformDispatcher.instance.onError = (error, stack) { log.severe('PlatformDispatcher - Catch all error: $error', error, stack); debugPrint("PlatformDispatcher - Catch all error: $error $stack"); return true; }; initializeTimeZones(); } Future loadDb() async { final dir = await getApplicationDocumentsDirectory(); Isar db = await Isar.open( [ StoreValueSchema, ExifInfoSchema, AssetSchema, AlbumSchema, UserSchema, BackupAlbumSchema, DuplicatedAssetSchema, LoggerMessageSchema, ETagSchema, if (Platform.isAndroid) AndroidDeviceAssetSchema, if (Platform.isIOS) IOSDeviceAssetSchema, ], directory: dir.path, maxSizeMiB: 256, ); Store.init(db); return db; } class ImmichApp extends ConsumerStatefulWidget { const ImmichApp({super.key}); @override ImmichAppState createState() => ImmichAppState(); } class ImmichAppState extends ConsumerState with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { case AppLifecycleState.resumed: debugPrint("[APP STATE] resumed"); ref.read(appStateProvider.notifier).handleAppResume(); break; case AppLifecycleState.inactive: debugPrint("[APP STATE] inactive"); ref.read(appStateProvider.notifier).handleAppInactivity(); break; case AppLifecycleState.paused: debugPrint("[APP STATE] paused"); ref.read(appStateProvider.notifier).handleAppPause(); break; case AppLifecycleState.detached: debugPrint("[APP STATE] detached"); ref.read(appStateProvider.notifier).handleAppDetached(); break; case AppLifecycleState.hidden: debugPrint("[APP STATE] hidden"); ref.read(appStateProvider.notifier).handleAppHidden(); break; } } Future initApp() async { WidgetsBinding.instance.addObserver(this); // Draw the app from edge to edge SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); // Sets the navigation bar color SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle( systemNavigationBarColor: Colors.transparent, ); if (Platform.isAndroid) { // Android 8 does not support transparent app bars final info = await DeviceInfoPlugin().androidInfo; if (info.version.sdkInt <= 26) { overlayStyle = context.isDarkTheme ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light; } } SystemChrome.setSystemUIOverlayStyle(overlayStyle); await ref.read(localNotificationService).setup(); } @override initState() { super.initState(); initApp().then((_) => debugPrint("App Init Completed")); WidgetsBinding.instance.addPostFrameCallback((_) { // needs to be delayed so that EasyLocalization is working ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); }); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override Widget build(BuildContext context) { var router = ref.watch(appRouterProvider); return MaterialApp( localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, debugShowCheckedModeBanner: false, home: MaterialApp.router( title: 'Immich', debugShowCheckedModeBanner: false, themeMode: ref.watch(immichThemeProvider), darkTheme: immichDarkTheme, theme: immichLightTheme, routeInformationParser: router.defaultRouteParser(), routerDelegate: router.delegate( navigatorObservers: () => [TabNavigationObserver(ref: ref)], ), ), ); } } // ignore: prefer-single-widget-per-file class MainWidget extends StatelessWidget { const MainWidget({super.key}); @override Widget build(BuildContext context) { return EasyLocalization( supportedLocales: locales, path: translationsPath, useFallbackTranslations: true, fallbackLocale: locales.first, child: const ImmichApp(), ); } }