diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index 3f5a33b2b2..e828760789 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -65,7 +65,7 @@ linter: avoid_type_to_string: true # Flutter specific - use_build_context_synchronously: false + use_build_context_synchronously: true sized_box_for_whitespace: true use_colored_box: true use_decorated_box: true diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 58e93891a2..317733f1de 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -147,20 +147,9 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve Future initApp() async { WidgetsBinding.instance.addObserver(this); - // Draw the app from edge to edge unawaited(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 _setNavigationBarColor(); await FlutterLocalNotificationsPlugin().initialize( const InitializationSettings( @@ -170,6 +159,22 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve ); } + Future _setNavigationBarColor() async { + SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle(systemNavigationBarColor: Colors.transparent); + if (Platform.isAndroid) { + // Android 8 does not support transparent app bars + final info = await DeviceInfoPlugin().androidInfo; + if (!mounted) { + return; + } + + if (info.version.sdkInt <= 26) { + overlayStyle = context.isDarkTheme ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light; + } + } + SystemChrome.setSystemUIOverlayStyle(overlayStyle); + } + Future _deepLinkBuilder(PlatformDeepLink deepLink) async { final deepLinkHandler = ref.read(deepLinkServiceProvider); final currentRouteName = ref.read(currentRouteNameProvider.notifier).state; diff --git a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart index 396f4224a7..7667dbc3f1 100644 --- a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart @@ -132,6 +132,10 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState { return; } - if (mounted) { - setState(() => _cleared = true); + if (!mounted) { + return; } + + setState(() => _cleared = true); } @override @@ -312,46 +314,53 @@ class SplashScreenPageState extends ConsumerState { final viewIntentHandler = ref.read(viewIntentHandlerProvider); unawaited( - ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( - (_) async { - try { - wsProvider.connect(); - unawaited(infoProvider.getServerInfo()); + ref + .read(authProvider.notifier) + .saveAuthInfo(accessToken: accessToken) + .then( + (_) async { + try { + wsProvider.connect(); + unawaited(infoProvider.getServerInfo()); - bool syncSuccess = false; - await Future.wait([ - backgroundManager.syncLocal(full: true), - backgroundManager.syncRemote().then((success) => syncSuccess = success), - ]); + bool syncSuccess = false; + await Future.wait([ + backgroundManager.syncLocal(full: true), + backgroundManager.syncRemote().then((success) => syncSuccess = success), + ]); - await viewIntentHandler.flushDeferredViewIntent(); + await viewIntentHandler.flushDeferredViewIntent(); - if (syncSuccess) { - await Future.wait([ - backgroundManager.hashAssets().then((_) { - unawaited(_resumeBackup(backupProvider)); - }), - _resumeBackup(backupProvider), - // TODO: Bring back when the soft freeze issue is addressed - // backgroundManager.syncCloudIds(), - ]); - } else { - await backgroundManager.hashAssets(); - } + if (syncSuccess) { + await Future.wait([ + backgroundManager.hashAssets().then((_) { + unawaited(_resumeBackup(backupProvider)); + }), + _resumeBackup(backupProvider), + // TODO: Bring back when the soft freeze issue is addressed + // backgroundManager.syncCloudIds(), + ]); + } else { + await backgroundManager.hashAssets(); + } - if (SettingsRepository.instance.appConfig.backup.syncAlbums) { - 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.router.replaceAll([const LoginRoute()]), - }, - ), + if (SettingsRepository.instance.appConfig.backup.syncAlbums) { + 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'); + unawaited(ref.read(authProvider.notifier).logout()); + if (!mounted) { + return; + } + + unawaited(context.router.replaceAll([const LoginRoute()])); + }, + ), ); } else { log.severe('Missing crucial offline login info - Logging out completely'); diff --git a/mobile/lib/pages/library/locked/pin_auth.page.dart b/mobile/lib/pages/library/locked/pin_auth.page.dart index 2da9a8ddab..81e08d853b 100644 --- a/mobile/lib/pages/library/locked/pin_auth.page.dart +++ b/mobile/lib/pages/library/locked/pin_auth.page.dart @@ -25,17 +25,19 @@ class PinAuthPage extends HookConsumerWidget { Future registerBiometric(String pinCode) async { final isRegistered = await ref.read(localAuthProvider.notifier).registerBiometric(context, pinCode); - if (isRegistered) { - context.showSnackBar( - SnackBar( - content: Text('biometric_auth_enabled'.tr(), style: context.textTheme.labelLarge), - duration: const Duration(seconds: 3), - backgroundColor: context.colorScheme.primaryContainer, - ), - ); - - unawaited(context.replaceRoute(const DriftLockedFolderRoute())); + if (!isRegistered || !context.mounted) { + return; } + + context.showSnackBar( + SnackBar( + content: Text('biometric_auth_enabled'.tr(), style: context.textTheme.labelLarge), + duration: const Duration(seconds: 3), + backgroundColor: context.colorScheme.primaryContainer, + ), + ); + + unawaited(context.replaceRoute(const DriftLockedFolderRoute())); } Future enableBiometricAuth() { diff --git a/mobile/lib/presentation/actions/action.widget.dart b/mobile/lib/presentation/actions/action.widget.dart index eba5e3939c..f96dc2bc2f 100644 --- a/mobile/lib/presentation/actions/action.widget.dart +++ b/mobile/lib/presentation/actions/action.widget.dart @@ -23,6 +23,10 @@ class _ActionWidget extends ConsumerWidget { try { await action.onAction(scope); } catch (error, stackTrace) { + if (!scope.context.mounted) { + return; + } + handleError(scope.context, stack: stackTrace, description: 'Action failed: ${action.runtimeType}'); } } diff --git a/mobile/lib/presentation/pages/drift_album_options.page.dart b/mobile/lib/presentation/pages/drift_album_options.page.dart index 37c0273fae..9c5161fa8a 100644 --- a/mobile/lib/presentation/pages/drift_album_options.page.dart +++ b/mobile/lib/presentation/pages/drift_album_options.page.dart @@ -46,6 +46,10 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { Future leaveAlbum() async { try { await ref.read(remoteAlbumProvider.notifier).leaveAlbum(album.id, userId: userId); + if (!context.mounted) { + return; + } + unawaited(context.navigateTo(const DriftAlbumsRoute())); } catch (_) { showErrorMessage(); @@ -72,17 +76,21 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { try { await ref.read(remoteAlbumProvider.notifier).addUsers(album.id, newUsers); - - if (newUsers.isNotEmpty) { - ImmichToast.show( - context: context, - msg: "users_added_to_album_count".t(context: context, args: {'count': newUsers.length}), - toastType: ToastType.success, - ); + ref.invalidate(remoteAlbumSharedUsersProvider(album.id)); + if (!context.mounted) { + return; } - ref.invalidate(remoteAlbumSharedUsersProvider(album.id)); + ImmichToast.show( + context: context, + msg: "users_added_to_album_count".t(context: context, args: {'count': newUsers.length}), + toastType: ToastType.success, + ); } catch (e) { + if (!context.mounted) { + return; + } + ImmichToast.show(context: context, msg: "Failed to add users to album: $e", toastType: ToastType.error); } } diff --git a/mobile/lib/presentation/pages/drift_create_album.page.dart b/mobile/lib/presentation/pages/drift_create_album.page.dart index 0dfae062dc..93534d62a5 100644 --- a/mobile/lib/presentation/pages/drift_create_album.page.dart +++ b/mobile/lib/presentation/pages/drift_create_album.page.dart @@ -186,13 +186,17 @@ class _DriftCreateAlbumPageState extends ConsumerState { assets: selectedAssets, ); - if (album != null && context.mounted) { - unawaited(context.replaceRoute(RemoteAlbumRoute(album: album))); + if (!mounted || album == null) { + return; } + + unawaited(context.replaceRoute(RemoteAlbumRoute(album: album))); } catch (_) { - if (context.mounted) { - ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.t()); + if (!mounted) { + return; } + + ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.t()); } finally { if (mounted) { setState(() => isCreatingAlbum = false); diff --git a/mobile/lib/presentation/pages/drift_partner_detail.page.dart b/mobile/lib/presentation/pages/drift_partner_detail.page.dart index 53353ce689..70de434d9f 100644 --- a/mobile/lib/presentation/pages/drift_partner_detail.page.dart +++ b/mobile/lib/presentation/pages/drift_partner_detail.page.dart @@ -72,13 +72,16 @@ class _InfoBoxState extends ConsumerState<_InfoBox> { }); } catch (error, stack) { dPrint(() => "Failed to toggle in timeline: $error $stack"); + if (!mounted) { + return; + } + ImmichToast.show( context: context, toastType: ToastType.error, durationInSecond: 1, msg: "Failed to toggle the timeline setting", ); - return; } } diff --git a/mobile/lib/presentation/pages/drift_remote_album.page.dart b/mobile/lib/presentation/pages/drift_remote_album.page.dart index 5e4e525e06..5d018561cc 100644 --- a/mobile/lib/presentation/pages/drift_remote_album.page.dart +++ b/mobile/lib/presentation/pages/drift_remote_album.page.dart @@ -42,6 +42,9 @@ class _RemoteAlbumPageState extends ConsumerState { Future addAssets(BuildContext context) async { final notifier = ref.read(remoteAlbumProvider.notifier); final albumAssets = await notifier.getAssets(_album.id); + if (!context.mounted) { + return; + } final newAssets = await context.pushRoute>( DriftAssetSelectionTimelineRoute(lockedSelectionAssets: albumAssets.toSet()), @@ -52,8 +55,11 @@ class _RemoteAlbumPageState extends ConsumerState { } final added = await notifier.addAssetsToAlbum(_album.id, newAssets); + if (!context.mounted) { + return; + } - if (added > 0 && context.mounted) { + if (added > 0) { ImmichToast.show( context: context, msg: "assets_added_to_album_count".t(context: context, args: {'count': added.toString()}), @@ -71,17 +77,21 @@ class _RemoteAlbumPageState extends ConsumerState { try { await ref.read(remoteAlbumProvider.notifier).addUsers(_album.id, newUsers); - - if (newUsers.isNotEmpty) { - ImmichToast.show( - context: context, - msg: "users_added_to_album_count".t(context: context, args: {'count': newUsers.length}), - toastType: ToastType.success, - ); + ref.invalidate(remoteAlbumSharedUsersProvider(_album.id)); + if (!context.mounted) { + return; } - ref.invalidate(remoteAlbumSharedUsersProvider(_album.id)); + ImmichToast.show( + context: context, + msg: "users_added_to_album_count".t(context: context, args: {'count': newUsers.length}), + toastType: ToastType.success, + ); } catch (e) { + if (!context.mounted) { + return; + } + ImmichToast.show(context: context, msg: "Failed to add users to album: $e", toastType: ToastType.error); } } @@ -124,6 +134,9 @@ class _RemoteAlbumPageState extends ConsumerState { if (confirmed == true) { try { await ref.read(remoteAlbumProvider.notifier).deleteAlbum(_album.id); + if (!context.mounted) { + return; + } ImmichToast.show( context: context, @@ -133,6 +146,10 @@ class _RemoteAlbumPageState extends ConsumerState { unawaited(context.pushRoute(const DriftAlbumsRoute())); } catch (e) { + if (!context.mounted) { + return; + } + ImmichToast.show( context: context, msg: 'album_viewer_appbar_share_err_delete'.t(context: context), @@ -149,7 +166,11 @@ class _RemoteAlbumPageState extends ConsumerState { builder: (context) => _EditAlbumDialog(album: _album), ); - if (result != null && context.mounted) { + if (!context.mounted) { + return; + } + + if (result != null) { setState(() { _album = _album.copyWith(name: result.name, description: result.description ?? ''); }); @@ -247,20 +268,23 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { await ref .read(remoteAlbumProvider.notifier) .updateAlbum(widget.album.id, name: newTitle, description: newDescription); + if (!mounted) { + return; + } - if (mounted) { - Navigator.of( - context, - ).pop(_EditAlbumData(name: newTitle, description: newDescription.isEmpty ? null : newDescription)); - } + Navigator.of( + context, + ).pop(_EditAlbumData(name: newTitle, description: newDescription.isEmpty ? null : newDescription)); } catch (e) { - if (mounted) { - ImmichToast.show( - context: context, - msg: 'album_update_error'.t(context: context), - toastType: ToastType.error, - ); + if (!mounted) { + return; } + + ImmichToast.show( + context: context, + msg: 'album_update_error'.t(context: context), + toastType: ToastType.error, + ); } } diff --git a/mobile/lib/presentation/pages/edit/drift_edit.page.dart b/mobile/lib/presentation/pages/edit/drift_edit.page.dart index 0ce9985c19..ec7d8ac3d7 100644 --- a/mobile/lib/presentation/pages/edit/drift_edit.page.dart +++ b/mobile/lib/presentation/pages/edit/drift_edit.page.dart @@ -59,9 +59,17 @@ class _DriftEditImagePageState extends ConsumerState with Ti try { await widget.applyEdits(edits); + if (!mounted) { + return; + } + ImmichToast.show(context: context, msg: 'success'.tr(), toastType: ToastType.success); Navigator.of(context).pop(); } catch (e) { + if (!mounted) { + return; + } + ImmichToast.show(context: context, msg: 'error_title'.tr(), toastType: ToastType.error); } finally { ref.read(editorStateProvider.notifier).setIsEditing(false); @@ -99,7 +107,7 @@ class _DriftEditImagePageState extends ConsumerState with Ti return; } final shouldDiscard = await _showDiscardChangesDialog() ?? false; - if (shouldDiscard && mounted) { + if (shouldDiscard && context.mounted) { Navigator.of(context).pop(); } }, diff --git a/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart b/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart index e6ae6ad44f..a987c9ca29 100644 --- a/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart +++ b/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart @@ -74,7 +74,7 @@ class _ProfilePictureCropPageState extends ConsumerState .read(uploadProfileImageProvider.notifier) .upload(xFile, fileName: 'profile-picture.png'); - if (!context.mounted) { + if (!mounted) { return; } @@ -94,9 +94,7 @@ class _ProfilePictureCropPageState extends ConsumerState toastType: ToastType.success, ); - if (context.mounted) { - unawaited(context.maybePop()); - } + unawaited(context.maybePop()); } else { ImmichToast.show( context: context, @@ -106,7 +104,7 @@ class _ProfilePictureCropPageState extends ConsumerState ); } } catch (e) { - if (!context.mounted) { + if (!mounted) { return; } diff --git a/mobile/lib/presentation/pages/search/drift_search.page.dart b/mobile/lib/presentation/pages/search/drift_search.page.dart index 8d6122804a..39588cf051 100644 --- a/mobile/lib/presentation/pages/search/drift_search.page.dart +++ b/mobile/lib/presentation/pages/search/drift_search.page.dart @@ -107,6 +107,10 @@ class DriftSearchPage extends HookConsumerWidget { unawaited( Future.microtask(() { + if (!context.mounted) { + return; + } + textSearchController.clear(); peopleCurrentFilterWidget.value = null; dateRangeCurrentFilterWidget.value = null; diff --git a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart index bcd3b20df6..533bc72971 100644 --- a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart @@ -144,7 +144,7 @@ class _AddActionButtonState extends ConsumerState { final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.viewer, album); - if (!context.mounted) { + if (!mounted) { return; } @@ -175,7 +175,7 @@ class _AddActionButtonState extends ConsumerState { ); } - if (!context.mounted) { + if (!mounted) { return; } await Navigator.of(context).maybePop(); diff --git a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart index bb2cae21ad..3322dd3a85 100644 --- a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart @@ -23,16 +23,17 @@ Future performArchiveAction(BuildContext context, WidgetRef ref, {required final result = await ref.read(actionProvider.notifier).archive(source); ref.read(multiSelectProvider.notifier).reset(); - final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); + if (!context.mounted) { + return; } + + final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } class ArchiveActionButton extends ConsumerWidget { diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart index 45dc5ec699..a6ed4c4246 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart @@ -73,17 +73,17 @@ class DeleteActionButton extends ConsumerWidget { shouldRefreshStack ? ViewerStackAssetDeletedEvent(stackIndex: stackIndex) : const ViewerReloadAssetEvent(), ); } + if (!context.mounted) { + return; + } final successMessage = 'delete_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart index 5a94d9807e..09969d8b8a 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart @@ -42,16 +42,17 @@ class DeleteLocalActionButton extends ConsumerWidget { ref.invalidate(localAlbumProvider); - final successMessage = 'delete_local_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); + if (!context.mounted) { + return; } + + final successMessage = 'delete_local_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart index 922f8593fa..c02fcf8f79 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart @@ -50,20 +50,20 @@ class DeletePermanentActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'delete_permanently_action_prompt'.t( context: context, args: {'count': result.count.toString()}, ); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart index f3e048f06f..3312e4f2a3 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart @@ -37,20 +37,20 @@ class DeleteTrashActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'assets_permanently_deleted_count'.t( context: context, args: {'count': result.count.toString()}, ); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart index b2b5050a8e..e93720186b 100644 --- a/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart @@ -24,20 +24,20 @@ class EditDateTimeActionButton extends ConsumerWidget { } ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'edit_date_and_time_action_prompt'.t( context: context, args: {'count': result.count.toString()}, ); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart index cc8e15617c..b250e325ce 100644 --- a/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart @@ -24,17 +24,17 @@ class EditLocationActionButton extends ConsumerWidget { } ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'edit_location_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart index 0365335fd2..0f3ce47122 100644 --- a/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart @@ -35,17 +35,17 @@ class FavoriteActionButton extends ConsumerWidget { } ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'favorite_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart index 56191e9055..6d5f5b387a 100644 --- a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart @@ -22,20 +22,20 @@ Future performMoveToLockFolderAction(BuildContext context, WidgetRef ref, final result = await ref.read(actionProvider.notifier).moveToLockFolder(source); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'move_to_lock_folder_action_prompt'.t( context: context, args: {'count': result.count.toString()}, ); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } class MoveToLockFolderActionButton extends ConsumerWidget { diff --git a/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart index ebcfbaa1e5..7049da13f8 100644 --- a/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart @@ -35,20 +35,20 @@ class RemoveFromAlbumActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).removeFromAlbum(source, albumId); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'remove_from_album_action_prompt'.t( context: context, args: {'count': result.count.toString()}, ); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart index 75deef9ccb..ea0d9f384f 100644 --- a/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart @@ -27,20 +27,20 @@ class RemoveFromLockFolderActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).removeFromLockFolder(source); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'remove_from_lock_folder_action_prompt'.t( context: context, args: {'count': result.count.toString()}, ); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart index b752a77c89..9270ce8351 100644 --- a/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart @@ -29,16 +29,17 @@ class RestoreActionButton extends ConsumerWidget { EventStream.shared.emit(const ViewerReloadAssetEvent()); } - final successMessage = 'assets_restored_count'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); + if (!context.mounted) { + return; } + + final successMessage = 'assets_restored_count'.t(context: context, args: {'count': result.count.toString()}); + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart index 82a9d98549..f6cd26c189 100644 --- a/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart @@ -19,17 +19,17 @@ class RestoreTrashActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).restoreTrash(source); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'assets_restored_count'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/set_album_cover.widget.dart b/mobile/lib/presentation/widgets/action_buttons/set_album_cover.widget.dart index d080efc5b2..e6e572110e 100644 --- a/mobile/lib/presentation/widgets/action_buttons/set_album_cover.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/set_album_cover.widget.dart @@ -29,17 +29,17 @@ class SetAlbumCoverActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).setAlbumCover(source, albumId); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'album_cover_updated'.t(context: context); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart index b87d288a3e..026268fe52 100644 --- a/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart @@ -26,17 +26,17 @@ class StackActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).stack(user.id, source); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'stack_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart index a320d3b1b1..2be2049a07 100644 --- a/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart @@ -31,17 +31,17 @@ class TrashActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).trash(source); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'trash_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart index 78984f9ef1..552608f83f 100644 --- a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart @@ -25,16 +25,17 @@ Future performUnArchiveAction(BuildContext context, WidgetRef ref, {requir final result = await ref.read(actionProvider.notifier).unArchive(source); ref.read(multiSelectProvider.notifier).reset(); - final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); + if (!context.mounted) { + return; } + + final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } class UnArchiveActionButton extends ConsumerWidget { diff --git a/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart index 94d6588074..be6c3b0180 100644 --- a/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart @@ -35,17 +35,17 @@ class UnFavoriteActionButton extends ConsumerWidget { } ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'unfavorite_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart index c9a5102a9b..47cdfe9b5f 100644 --- a/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart @@ -22,17 +22,17 @@ class UnStackActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).unStack(source); ref.read(multiSelectProvider.notifier).reset(); + if (!context.mounted) { + return; + } final successMessage = 'unstack_action_prompt'.t(context: context, args: {'count': result.count.toString()}); - - if (context.mounted) { - ImmichToast.show( - context: context, - msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), - gravity: ToastGravity.BOTTOM, - toastType: result.success ? ToastType.success : ToastType.error, - ); - } + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); } @override diff --git a/mobile/lib/presentation/widgets/album/album_selector.widget.dart b/mobile/lib/presentation/widgets/album/album_selector.widget.dart index bf5de5611d..f30796e406 100644 --- a/mobile/lib/presentation/widgets/album/album_selector.widget.dart +++ b/mobile/lib/presentation/widgets/album/album_selector.widget.dart @@ -755,6 +755,10 @@ class AddToAlbumHeader extends ConsumerWidget { .read(remoteAlbumProvider.notifier) .createAlbumWithAssets(title: albumName, assets: selectedAssets); + if (!context.mounted) { + return; + } + if (newAlbum == null) { ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.tr()); return; @@ -798,7 +802,7 @@ class CreateAlbumButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { Future onCreateAlbum() async { final albumName = await showDialog(context: context, builder: (context) => const NewAlbumNameModal()); - if (albumName == null) { + if (albumName == null || !context.mounted) { return; } @@ -813,6 +817,10 @@ class CreateAlbumButton extends ConsumerWidget { .read(remoteAlbumProvider.notifier) .createAlbum(title: albumName, assetIds: [asset.remoteId!]); + if (!context.mounted) { + return; + } + if (album == null) { ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.tr()); return; diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/date_time_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/date_time_details.widget.dart index 27bac68310..2dc1c40456 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/date_time_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/date_time_details.widget.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -93,6 +94,9 @@ class _SheetAssetDescriptionState extends ConsumerState<_SheetAssetDescription> if (!editAction.success) { _controller.text = previousDescription ?? ''; + if (!mounted) { + return; + } ImmichToast.show( context: context, diff --git a/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart index bcb9fc6fe3..4382eeba5d 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart @@ -44,6 +44,9 @@ class FavoriteBottomSheet extends ConsumerWidget { final result = await ref .read(remoteAlbumProvider.notifier) .addAssets(album.id, remoteAssets.map((e) => e.id).toList()); + if (!context.mounted) { + return; + } if (selectedAssets.length != remoteAssets.length) { ImmichToast.show( diff --git a/mobile/lib/presentation/widgets/map/map_utils.dart b/mobile/lib/presentation/widgets/map/map_utils.dart index 3ce7b2e055..34116b144b 100644 --- a/mobile/lib/presentation/widgets/map/map_utils.dart +++ b/mobile/lib/presentation/widgets/map/map_utils.dart @@ -71,7 +71,11 @@ class MapUtils { bool silent = false, }) async { try { - final bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + final serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!context.mounted) { + return (null, LocationPermission.unableToDetermine); + } + if (!serviceEnabled && !silent) { unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context))); return (null, LocationPermission.deniedForever); @@ -81,6 +85,10 @@ class MapUtils { bool shouldRequestPermission = false; if (permission == LocationPermission.denied && !silent) { + if (!context.mounted) { + return (null, LocationPermission.unableToDetermine); + } + shouldRequestPermission = await showDialog( context: context, builder: (context) => _LocationPermissionDisabledDialog(context), diff --git a/mobile/lib/presentation/widgets/people/person_edit_birthday_modal.widget.dart b/mobile/lib/presentation/widgets/people/person_edit_birthday_modal.widget.dart index 6e66ff47ce..b5a9dd235c 100644 --- a/mobile/lib/presentation/widgets/people/person_edit_birthday_modal.widget.dart +++ b/mobile/lib/presentation/widgets/people/person_edit_birthday_modal.widget.dart @@ -34,12 +34,16 @@ class _DriftPersonNameEditFormState extends ConsumerState(_selectedDate); } } catch (error) { dPrint(() => 'Error updating birthday: $error'); - if (!context.mounted) { + if (!mounted) { return; } diff --git a/mobile/lib/presentation/widgets/people/person_edit_name_modal.widget.dart b/mobile/lib/presentation/widgets/people/person_edit_name_modal.widget.dart index 2eaac2ebf5..bb63de3695 100644 --- a/mobile/lib/presentation/widgets/people/person_edit_name_modal.widget.dart +++ b/mobile/lib/presentation/widgets/people/person_edit_name_modal.widget.dart @@ -32,12 +32,16 @@ class _DriftPersonNameEditFormState extends ConsumerState(newName); } } catch (error) { dPrint(() => 'Error updating name: $error'); - if (!context.mounted) { + if (!mounted) { return; } diff --git a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart index 7712983fde..16c947ca5e 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart @@ -211,6 +211,10 @@ class _AssetTileWidget extends ConsumerWidget { ref.read(multiSelectProvider.notifier).toggleAssetSelection(asset); } else { await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1); + if (!ctx.mounted) { + return; + } + ref.read(isPlayingMotionVideoProvider.notifier).playing = false; AssetViewer.setAsset(ref, asset); unawaited( diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index 5bfb18a00f..6058883544 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -324,7 +324,7 @@ class AssetMediaRepository { return 0; } - if (_isCancelled(cancelCompleter)) { + if (_isCancelled(cancelCompleter) || !context.mounted) { await _cleanupTempFiles(tempFiles); return 0; } diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 19782c8512..5986f0407d 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -157,6 +157,10 @@ class ActionService { } } + if (!context.mounted) { + return false; + } + final location = await showLocationPicker(context: context, initialLatLng: initialLatLng); if (location == null) { @@ -195,6 +199,10 @@ class ActionService { initialDate = dt; } + if (!context.mounted) { + return false; + } + final dateTime = await showDateTimePicker( context: context, initialDateTime: initialDate, diff --git a/mobile/lib/services/immich_logger.service.dart b/mobile/lib/services/immich_logger.service.dart index fab4b9966a..bfc9bd9b51 100644 --- a/mobile/lib/services/immich_logger.service.dart +++ b/mobile/lib/services/immich_logger.service.dart @@ -39,6 +39,10 @@ abstract final class ImmichLogger { await io.close(); } + if (!context.mounted) { + return; + } + final box = context.findRenderObject() as RenderBox?; // Share file diff --git a/mobile/lib/utils/map_utils.dart b/mobile/lib/utils/map_utils.dart index 19c66e51e9..d5e6f957a5 100644 --- a/mobile/lib/utils/map_utils.dart +++ b/mobile/lib/utils/map_utils.dart @@ -68,7 +68,11 @@ class MapUtils { bool silent = false, }) async { try { - final bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + final serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!context.mounted) { + return (null, LocationPermission.unableToDetermine); + } + if (!serviceEnabled && !silent) { unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog())); return (null, LocationPermission.deniedForever); @@ -78,6 +82,10 @@ class MapUtils { bool shouldRequestPermission = false; if (permission == LocationPermission.denied && !silent) { + if (!context.mounted) { + return (null, LocationPermission.unableToDetermine); + } + shouldRequestPermission = await showDialog( context: context, builder: (context) => _LocationPermissionDisabledDialog(), diff --git a/mobile/lib/widgets/activities/comment_bubble.dart b/mobile/lib/widgets/activities/comment_bubble.dart index 95cff7b87d..fcaff8bfc3 100644 --- a/mobile/lib/widgets/activities/comment_bubble.dart +++ b/mobile/lib/widgets/activities/comment_bubble.dart @@ -35,6 +35,10 @@ class CommentBubble extends ConsumerWidget { Future openAssetViewer() async { final activityService = ref.read(activityServiceProvider); final route = await activityService.buildAssetViewerRoute(activity.assetId!, ref); + if (!context.mounted) { + return; + } + if (route != null) { await context.pushRoute(route); } diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart index 085f4e2120..53c1eb1af9 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart @@ -126,6 +126,10 @@ class ImmichAppBarDialog extends HookConsumerWidget { await ref.read(authProvider.notifier).logout().whenComplete(() => isLoggingOut.value = false); ref.read(websocketProvider.notifier).disconnect(); + if (!context.mounted) { + return; + } + unawaited(context.replaceRoute(const LoginRoute())); }, ); @@ -199,6 +203,10 @@ class ImmichAppBarDialog extends HookConsumerWidget { onTap: () async { ContextHelper(context).pop(); final packageInfo = await PackageInfo.fromPlatform(); + if (!context.mounted) { + return; + } + showLicensePage( context: context, applicationIcon: const Padding( diff --git a/mobile/lib/widgets/common/date_time_picker.dart b/mobile/lib/widgets/common/date_time_picker.dart index 679241fc1b..3aedd55de5 100644 --- a/mobile/lib/widgets/common/date_time_picker.dart +++ b/mobile/lib/widgets/common/date_time_picker.dart @@ -90,7 +90,7 @@ class _DateTimePicker extends HookWidget { firstDate: DateTime(1800), lastDate: now, ); - if (newDate == null) { + if (newDate == null || !context.mounted) { return; } diff --git a/mobile/lib/widgets/forms/change_password_form.dart b/mobile/lib/widgets/forms/change_password_form.dart index 7ab556b292..dd307f4ede 100644 --- a/mobile/lib/widgets/forms/change_password_form.dart +++ b/mobile/lib/widgets/forms/change_password_form.dart @@ -59,26 +59,29 @@ class ChangePasswordForm extends HookConsumerWidget { .read(authProvider.notifier) .changePassword(passwordController.value.text); - if (isSuccess) { - await ref.read(authProvider.notifier).logout(); - ref.read(websocketProvider.notifier).disconnect(); - - AutoRouter.of(context).back(); - - ImmichToast.show( - context: context, - msg: "login_password_changed_success".tr(), - toastType: ToastType.success, - gravity: ToastGravity.TOP, - ); - } else { + if (!isSuccess && context.mounted) { ImmichToast.show( context: context, msg: "login_password_changed_error".tr(), toastType: ToastType.error, gravity: ToastGravity.TOP, ); + return; } + + await ref.read(authProvider.notifier).logout(); + ref.read(websocketProvider.notifier).disconnect(); + if (!context.mounted) { + return; + } + + AutoRouter.of(context).back(); + ImmichToast.show( + context: context, + msg: "login_password_changed_success".tr(), + toastType: ToastType.success, + gravity: ToastGravity.TOP, + ); } }, ), diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index 969c311bfb..18963f0cfd 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -115,6 +115,10 @@ class LoginForm extends HookConsumerWidget { serverEndpoint.value = endpoint; } on ApiException catch (e) { + if (!context.mounted) { + return; + } + ImmichToast.show( context: context, msg: e.message ?? 'login_form_api_exception'.tr(), @@ -124,6 +128,10 @@ class LoginForm extends HookConsumerWidget { isOauthEnable.value = false; isPasswordLoginEnable.value = true; } on HandshakeException { + if (!context.mounted) { + return; + } + ImmichToast.show( context: context, msg: 'login_form_handshake_exception'.tr(), @@ -133,6 +141,10 @@ class LoginForm extends HookConsumerWidget { isOauthEnable.value = false; isPasswordLoginEnable.value = true; } catch (e) { + if (!context.mounted) { + return; + } + ImmichToast.show( context: context, msg: 'login_form_server_error'.tr(), @@ -180,6 +192,10 @@ class LoginForm extends HookConsumerWidget { Future getManageMediaPermission() async { final hasPermission = await ref.read(permissionRepositoryProvider).hasManageMediaPermission(); + if (!context.mounted) { + return; + } + if (!hasPermission) { await showDialog( context: context, @@ -236,6 +252,10 @@ class LoginForm extends HookConsumerWidget { try { final result = await ref.read(authProvider.notifier).login(emailController.text, passwordController.text); + if (!context.mounted) { + return; + } + if (result.shouldChangePassword && !result.isAdmin) { unawaited(context.pushRoute(const ChangePasswordRoute())); } else { @@ -246,10 +266,18 @@ class LoginForm extends HookConsumerWidget { unawaited(handleSyncFlow()); ref.read(websocketProvider.notifier).connect(); unawaited(ref.read(featureMessageServiceProvider).markSeen()); + if (!context.mounted) { + return; + } + unawaited(context.router.replaceAll([const TabShellRoute()])); return; } } catch (error) { + if (!context.mounted) { + return; + } + ImmichToast.show( context: context, msg: "login_form_failed_login".tr(), @@ -304,6 +332,10 @@ class LoginForm extends HookConsumerWidget { } catch (error, stack) { log.severe('Error getting OAuth server Url: $error', stack); + if (!context.mounted) { + return; + } + ImmichToast.show( context: context, msg: "login_form_failed_get_oauth_server_config".tr(), @@ -334,12 +366,20 @@ class LoginForm extends HookConsumerWidget { } unawaited(handleSyncFlow()); unawaited(ref.read(featureMessageServiceProvider).markSeen()); + if (!context.mounted) { + return; + } + unawaited(context.router.replaceAll([const TabShellRoute()])); return; } } catch (error, stack) { log.severe('Error logging in with OAuth: $error', stack); + if (!context.mounted) { + return; + } + ImmichToast.show( context: context, msg: error.toString(), @@ -348,6 +388,10 @@ class LoginForm extends HookConsumerWidget { ); } finally {} } else { + if (!context.mounted) { + return; + } + ImmichToast.show( context: context, msg: "login_form_failed_get_oauth_server_disable".tr(), diff --git a/mobile/lib/widgets/forms/pin_registration_form.dart b/mobile/lib/widgets/forms/pin_registration_form.dart index b3270ec524..ebd299bcce 100644 --- a/mobile/lib/widgets/forms/pin_registration_form.dart +++ b/mobile/lib/widgets/forms/pin_registration_form.dart @@ -42,6 +42,10 @@ class PinRegistrationForm extends HookConsumerWidget { onDone(); } catch (error) { hasError.value = true; + if (!context.mounted) { + return; + } + context.showSnackBar(SnackBar(content: Text(error.toString()))); } } diff --git a/mobile/lib/widgets/settings/advanced_settings.dart b/mobile/lib/widgets/settings/advanced_settings.dart index dbf54fd082..bf8673ccc5 100644 --- a/mobile/lib/widgets/settings/advanced_settings.dart +++ b/mobile/lib/widgets/settings/advanced_settings.dart @@ -145,6 +145,10 @@ class AdvancedSettings extends HookConsumerWidget { try { clearedBytes = await remoteImageApi.clearCache(); } catch (e) { + if (!context.mounted) { + return; + } + context.scaffoldMessenger.showSnackBar( SnackBar( duration: const Duration(seconds: 2), @@ -157,7 +161,7 @@ class AdvancedSettings extends HookConsumerWidget { return; } - if (clearedBytes < 0) { + if (clearedBytes < 0 || !context.mounted) { return; } diff --git a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart index 1871dc8a62..58a044cea4 100644 --- a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart +++ b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart @@ -41,11 +41,13 @@ class SyncStatusAndActions extends HookConsumerWidget { // ignore: avoid_slow_async_io if (!await dbFile.exists()) { - if (context.mounted) { - context.scaffoldMessenger.showSnackBar( - SnackBar(content: Text("Database file not found".t(context: context))), - ); + if (!context.mounted) { + return; } + + context.scaffoldMessenger.showSnackBar( + SnackBar(content: Text("Database file not found".t(context: context))), + ); return; } @@ -54,6 +56,10 @@ class SyncStatusAndActions extends HookConsumerWidget { await dbFile.copy(exportFile.path); + if (!context.mounted) { + return; + } + final size = MediaQuery.of(context).size; await Share.shareXFiles( [XFile(exportFile.path)], @@ -67,18 +73,21 @@ class SyncStatusAndActions extends HookConsumerWidget { await exportFile.delete(); } }); + if (!context.mounted) { + return; + } - if (context.mounted) { - context.scaffoldMessenger.showSnackBar( - SnackBar(content: Text("Database exported successfully".t(context: context))), - ); - } + context.scaffoldMessenger.showSnackBar( + SnackBar(content: Text("Database exported successfully".t(context: context))), + ); } catch (e) { - if (context.mounted) { - context.scaffoldMessenger.showSnackBar( - SnackBar(content: Text("Failed to export database: $e".t(context: context))), - ); + if (!context.mounted) { + return; } + + context.scaffoldMessenger.showSnackBar( + SnackBar(content: Text("Failed to export database: $e".t(context: context))), + ); } } @@ -98,6 +107,10 @@ class SyncStatusAndActions extends HookConsumerWidget { TextButton( onPressed: () async { await ref.read(driftProvider).reset(); + if (!context.mounted) { + return; + } + context.pop(); unawaited( showDialog( diff --git a/mobile/lib/widgets/settings/language_settings.dart b/mobile/lib/widgets/settings/language_settings.dart index 2482801923..b12d1476f5 100644 --- a/mobile/lib/widgets/settings/language_settings.dart +++ b/mobile/lib/widgets/settings/language_settings.dart @@ -21,6 +21,10 @@ class LanguageSettings extends HookConsumerWidget { isLoading.value = true; await Future.delayed(const Duration(milliseconds: 500)); try { + if (!context.mounted) { + return; + } + await context.setLocale(selectedLocale.value); await loadTranslations(); } finally { diff --git a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart index f8b6b087a3..8b787bcaea 100644 --- a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart @@ -96,6 +96,9 @@ class LocalNetworkPreference extends HookConsumerWidget { Future autofillCurrentNetwork() async { final wifiName = await ref.read(networkProvider.notifier).getWifiName(); + if (!context.mounted) { + return; + } if (wifiName == null) { context.showSnackBar( diff --git a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart index e7510053e3..513648f030 100644 --- a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart +++ b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart @@ -46,6 +46,10 @@ class NetworkingSettings extends HookConsumerWidget { onPressed: () async { final isGrant = await ref.read(networkProvider.notifier).requestWifiReadPermission(); + if (!context.mounted) { + return; + } + Navigator.pop(context, isGrant); }, child: Text("grant_permission".tr()), @@ -56,6 +60,10 @@ class NetworkingSettings extends HookConsumerWidget { ); } + if (!context.mounted) { + return; + } + if (!hasLocationAlways) { isGrantLocationAlwaysPermission = await showDialog( context: context, @@ -68,6 +76,10 @@ class NetworkingSettings extends HookConsumerWidget { onPressed: () async { final isGrant = await ref.read(networkProvider.notifier).requestWifiReadBackgroundPermission(); + if (!context.mounted) { + return; + } + Navigator.pop(context, isGrant); }, child: Text("grant_permission".tr()), diff --git a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart index b0624dca23..b1564b4145 100644 --- a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart @@ -23,6 +23,10 @@ class PrimaryColorSetting extends HookConsumerWidget { void popBottomSheet() { Future.delayed(const Duration(milliseconds: 200), () { + if (!context.mounted) { + return; + } + Navigator.pop(context); }); }