import 'dart:async'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:logging/logging.dart'; @RoutePage() class SplashScreenPage extends StatefulHookConsumerWidget { const SplashScreenPage({super.key}); @override SplashScreenPageState createState() => SplashScreenPageState(); } class SplashScreenPageState extends ConsumerState { final log = Logger("SplashScreenPage"); @override void initState() { super.initState(); ref .read(authProvider.notifier) .setOpenApiServiceEndpoint() .then(logConnectionInfo) .whenComplete(() => resumeSession()); } void logConnectionInfo(String? endpoint) { if (endpoint == null) { return; } log.info("Resuming session at $endpoint"); } void resumeSession() async { final serverUrl = Store.tryGet(StoreKey.serverUrl); final endpoint = Store.tryGet(StoreKey.serverEndpoint); final accessToken = Store.tryGet(StoreKey.accessToken); if (accessToken != null && serverUrl != null && endpoint != null) { final infoProvider = ref.read(serverInfoProvider.notifier); final wsProvider = ref.read(websocketProvider.notifier); final backgroundManager = ref.read(backgroundSyncProvider); final backupProvider = ref.read(driftBackupProvider.notifier); unawaited( ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( (_) async { try { wsProvider.connect(); unawaited(infoProvider.getServerInfo()); if (Store.isBetaTimelineEnabled) { bool syncSuccess = false; await Future.wait([ backgroundManager.syncLocal(full: true), backgroundManager.syncRemote().then((success) => syncSuccess = success), ]); if (syncSuccess) { await Future.wait([ backgroundManager.hashAssets().then((_) { _resumeBackup(backupProvider); }), _resumeBackup(backupProvider), // TODO: Bring back when the soft freeze issue is addressed // backgroundManager.syncCloudIds(), ]); } else { await backgroundManager.hashAssets(); } if (Store.get(StoreKey.syncAlbums, false)) { await backgroundManager.syncLinkedAlbum(); } } } catch (e) { log.severe('Failed establishing connection to the server: $e'); } }, onError: (exception) => { log.severe('Failed to update auth info with access token: $accessToken'), ref.read(authProvider.notifier).logout(), context.replaceRoute(const LoginRoute()), }, ), ); } else { log.severe('Missing crucial offline login info - Logging out completely'); unawaited(ref.read(authProvider.notifier).logout()); unawaited(context.replaceRoute(const LoginRoute())); return; } // clean install - change the default of the flag // current install not using beta timeline if (context.router.current.name == SplashScreenRoute.name) { final needBetaMigration = Store.get(StoreKey.needBetaMigration, false); if (needBetaMigration) { bool migrate = (await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text("New Timeline Experience"), content: const Text( "The old timeline has been deprecated and will be removed in an upcoming release. Would you like to switch to the new timeline now?", ), actions: [ TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text("No")), ElevatedButton(onPressed: () => Navigator.of(ctx).pop(true), child: const Text("Yes")), ], ), )) ?? false; if (migrate != true) { migrate = (await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text("Are you sure?"), content: const Text( "If you choose to remain on the old timeline, you will be automatically migrated to the new timeline in an upcoming release. Would you like to switch now?", ), actions: [ TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text("No")), ElevatedButton(onPressed: () => Navigator.of(ctx).pop(true), child: const Text("Yes")), ], ), )) ?? false; } await Store.put(StoreKey.needBetaMigration, false); if (migrate) { unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)])); return; } } unawaited(context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute())); } if (Store.isBetaTimelineEnabled) { return; } final hasPermission = await ref.read(galleryPermissionNotifier.notifier).hasPermission; if (hasPermission) { // Resume backup (if enable) then navigate await ref.watch(backupProvider.notifier).resumeBackup(); } } Future _resumeBackup(DriftBackupNotifier notifier) async { final isEnableBackup = Store.get(StoreKey.enableBackup, false); if (isEnableBackup) { final currentUser = Store.tryGet(StoreKey.currentUser); if (currentUser != null) { unawaited(notifier.startForegroundBackup(currentUser.id)); } } } @override Widget build(BuildContext context) { return const Scaffold( body: Center( child: Image(image: AssetImage('assets/immich-logo.png'), width: 80, filterQuality: FilterQuality.high), ), ); } }