mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:27:08 -04:00 
			
		
		
		
	fix: mobile unawaited_futures lint (#21661)
* chore: add unawaited_futures lint as warning # Conflicts: # mobile/analysis_options.yaml * remove unused dcm lints They will be added back later on a case by case basis * fix warning # Conflicts: # mobile/lib/presentation/pages/drift_remote_album.page.dart * auto gen file * review changes * conflict resolution --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									664a8fa499
								
							
						
					
					
						commit
						ac0d646401
					
				| @ -17,7 +17,7 @@ linter: | ||||
|   # section below to disable rules from the `package:flutter_lints/flutter.yaml` | ||||
|   # included above or to enable additional rules. A list of all available lints | ||||
|   # and their documentation is published at | ||||
|   # https://dart-lang.github.io/linter/lints/index.html. | ||||
|   # https://dart.dev/tools/linter-rules | ||||
|   # | ||||
|   # Instead of disabling a lint rule for the entire project in the | ||||
|   # section below, it can also be suppressed for a single line of code | ||||
| @ -28,6 +28,7 @@ linter: | ||||
|   rules: | ||||
|     # avoid_print: false  # Uncomment to disable the `avoid_print` rule | ||||
|     # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule | ||||
|     unawaited_futures: true | ||||
|     use_build_context_synchronously: false | ||||
|     require_trailing_commas: true | ||||
|     unrelated_type_equality_checks: true | ||||
| @ -46,6 +47,8 @@ analyzer: | ||||
|   # TODO: Re-enable after upgrading custom_lint | ||||
|   # plugins: | ||||
|   #   - custom_lint | ||||
|   errors: | ||||
|     unawaited_futures: warning | ||||
| 
 | ||||
| custom_lint: | ||||
|   debug: true | ||||
| @ -152,160 +155,6 @@ dart_code_metrics: | ||||
|   #  - avoid-passing-async-when-sync-expected | ||||
|   #  - avoid-throw-in-catch-block | ||||
|     - avoid-unused-parameters | ||||
|   #  - avoid-unnecessary-type-assertions | ||||
|   #  - avoid-unnecessary-type-casts | ||||
|   #  - avoid-unrelated-type-assertions | ||||
|   #  - avoid-unrelated-type-casts | ||||
|   #  - no-empty-block | ||||
|   #  - no-equal-then-else | ||||
|   #  - prefer-correct-test-file-name | ||||
|     - prefer-const-border-radius | ||||
|   #  - prefer-match-file-name | ||||
|   #  - prefer-return-await | ||||
|   #  - avoid-self-assignment | ||||
|   #  - avoid-self-compare | ||||
|   #  - avoid-shadowing | ||||
|   #  - prefer-iterable-of | ||||
|   #  - no-equal-switch-case | ||||
|   #  - no-equal-conditions | ||||
|   #  - avoid-equal-expressions | ||||
|   #  - avoid-missed-calls | ||||
|   #  - avoid-unnecessary-negations | ||||
|   #  - avoid-unused-generics | ||||
|   #  - function-always-returns-null | ||||
|   #  - avoid-throw-objects-without-tostring | ||||
|   #  - avoid-unsafe-collection-methods | ||||
|   #  - prefer-wildcard-pattern | ||||
|   #  - no-equal-switch-expression-cases | ||||
|   #  - avoid-future-tostring | ||||
|   #  - avoid-unassigned-late-fields | ||||
|   #  - avoid-nested-futures | ||||
|   #  - avoid-generics-shadowing | ||||
|   #  - prefer-parentheses-with-if-null | ||||
|   #  - no-equal-nested-conditions | ||||
|   #  - avoid-shadowed-extension-methods | ||||
|   #  - avoid-unnecessary-conditionals | ||||
|   #  - avoid-double-slash-imports | ||||
|   #  - avoid-map-keys-contains | ||||
|   #  - prefer-correct-json-casts | ||||
|   #  - avoid-duplicate-mixins | ||||
|   #  - avoid-nullable-interpolation | ||||
|   #  - avoid-unused-instances | ||||
|   #  - prefer-correct-for-loop-increment | ||||
|   #  - prefer-public-exception-classes | ||||
|   #  - avoid-uncaught-future-errors | ||||
|   #  - always-remove-listener | ||||
|   #  - avoid-unnecessary-setstate | ||||
|   #  - check-for-equals-in-render-object-setters | ||||
|   #  - consistent-update-render-object | ||||
|   #  - use-setstate-synchronously | ||||
|   #  - avoid-incomplete-copy-with | ||||
|   #  - proper-super-calls | ||||
|   #  - dispose-fields | ||||
|   #  - avoid-empty-setstate | ||||
|   #  - avoid-state-constructors | ||||
|   #  - avoid-recursive-widget-calls | ||||
|   #  - avoid-missing-image-alt | ||||
|   #  - avoid-passing-self-as-argument | ||||
|   #  - avoid-unnecessary-if | ||||
|   #  - avoid-unconditional-break | ||||
|   #  - avoid-referencing-discarded-variables | ||||
|   #  - avoid-unnecessary-local-late | ||||
|   #  - avoid-wildcard-cases-with-enums | ||||
|   #  - match-getter-setter-field-names | ||||
|   #  - avoid-accessing-collections-by-constant-index | ||||
|   #  - prefer-unique-test-names | ||||
|   #  - avoid-duplicate-cascades | ||||
|   #  - prefer-specific-cases-first | ||||
|   #  - avoid-duplicate-switch-case-conditions | ||||
|   #  - prefer-explicit-function-type | ||||
|   #  - avoid-misused-test-matchers | ||||
|   #  - avoid-duplicate-test-assertions | ||||
|   #  - prefer-switch-with-enums | ||||
|   #  - prefer-any-or-every | ||||
|   #  - avoid-duplicate-map-keys | ||||
|   #  - avoid-nullable-tostring | ||||
|   #  - avoid-undisposed-instances | ||||
|   #  - avoid-duplicate-initializers | ||||
|   #  - avoid-unassigned-stream-subscriptions | ||||
|   #  - avoid-empty-test-groups | ||||
|   #  - avoid-not-encodable-in-to-json | ||||
|   #  - avoid-contradictory-expressions | ||||
|   #  - avoid-excessive-expressions | ||||
|   #  - prefer-private-extension-type-field | ||||
|   #  - avoid-renaming-representation-getters | ||||
|   #  - avoid-empty-spread | ||||
|   #  - avoid-unnecessary-gesture-detector | ||||
|   #  - avoid-missing-completer-stack-trace | ||||
|   #  - avoid-casting-to-extension-type | ||||
|   #  - prefer-overriding-parent-equality | ||||
|   #  - avoid-missing-controller | ||||
|   #  - avoid-unknown-pragma | ||||
|   #  - avoid-conditions-with-boolean-literals | ||||
|   #  - avoid-multi-assignment | ||||
|   #  - avoid-collection-equality-checks | ||||
|   #  - avoid-only-rethrow | ||||
|   #  - avoid-incorrect-image-opacity | ||||
|   #  - avoid-misused-set-literals | ||||
|   #  - dispose-class-fields | ||||
|   #  - avoid-suspicious-super-overrides | ||||
|   #  - avoid-assignments-as-conditions | ||||
|   #  - avoid-unused-assignment | ||||
|   #  - avoid-unnecessary-overrides | ||||
|   #  - avoid-implicitly-nullable-extension-types | ||||
|     # Enable with the next release | ||||
|     # - avoid-late-final-reassignment | ||||
|     # - avoid-duplicate-constant-values | ||||
|     # - function-always-returns-same-value | ||||
|     # - avoid-flexible-outside-flex | ||||
|     # - avoid-unnecessary-patterns | ||||
|     # - use-closest-build-context | ||||
|     # - avoid-commented-out-code | ||||
|     # - avoid-recursive-tostring | ||||
|     # - avoid-enum-values-by-index | ||||
|     # - avoid-constant-assert-conditions | ||||
|     # - avoid-inconsistent-digit-separators | ||||
|     # - pass-existing-future-to-future-builder | ||||
|     # - pass-existing-stream-to-stream-builder | ||||
| 
 | ||||
|     # Code simplification | ||||
|   #  - avoid-redundant-async | ||||
|   #  - avoid-redundant-else | ||||
|   #  - avoid-unnecessary-nullable-return-type | ||||
|   #  - avoid-redundant-pragma-inline | ||||
|   #  - avoid-nested-records | ||||
|   #  - avoid-redundant-positional-field-name | ||||
|   #  - avoid-explicit-pattern-field-name | ||||
|   #  - prefer-simpler-patterns-null-check | ||||
|   #  - avoid-unnecessary-return | ||||
|   #  - avoid-duplicate-patterns | ||||
|   #  - avoid-keywords-in-wildcard-pattern | ||||
|   #  - avoid-unnecessary-futures | ||||
|   #  - avoid-unnecessary-reassignment | ||||
|   #  - avoid-unnecessary-call | ||||
|   #  - avoid-unnecessary-stateful-widgets | ||||
|   #  - prefer-dedicated-media-query-methods | ||||
|   #  - avoid-unnecessary-overrides-in-state | ||||
|   #  - move-variable-closer-to-its-usage | ||||
|   #  - avoid-nullable-parameters-with-default-values | ||||
|   #  - prefer-null-aware-spread | ||||
|   #  - avoid-inferrable-type-arguments | ||||
|   #  - avoid-unnecessary-super | ||||
|   #  - avoid-unnecessary-collections | ||||
|   #  - avoid-unnecessary-extends | ||||
|   #  - avoid-unnecessary-enum-arguments | ||||
|   #  - prefer-contains | ||||
|     # Enable with the next release | ||||
|     # - prefer-simpler-boolean-expressions | ||||
|     # - prefer-spacing | ||||
|     # - avoid-unnecessary-continue | ||||
|     # - avoid-unnecessary-compare-to | ||||
| 
 | ||||
|     # Style | ||||
|   #  - prefer-trailing-comma | ||||
|   #  - unnecessary-trailing-comma | ||||
|     - prefer-declaring-const-constructor | ||||
|   #  - prefer-single-widget-per-file | ||||
|     - prefer-switch-expression | ||||
|   #  - prefer-prefixed-global-constants | ||||
|   #  - prefer-correct-callback-field-name | ||||
|  | ||||
| @ -114,10 +114,10 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { | ||||
|       configureFileDownloaderNotifications(); | ||||
| 
 | ||||
|       // Notify the host that the background worker service has been initialized and is ready to use | ||||
|       _backgroundHostApi.onInitialized(); | ||||
|       unawaited(_backgroundHostApi.onInitialized()); | ||||
|     } catch (error, stack) { | ||||
|       _logger.severe("Failed to initialize background worker", error, stack); | ||||
|       _backgroundHostApi.close(); | ||||
|       unawaited(_backgroundHostApi.close()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -249,7 +249,7 @@ class LocalSyncService { | ||||
| 
 | ||||
|       if (assetsToUpsert.isEmpty && assetsToDelete.isEmpty) { | ||||
|         _log.fine("No asset changes detected in album ${deviceAlbum.name}. Updating metadata."); | ||||
|         _localAlbumRepository.upsert(updatedDeviceAlbum); | ||||
|         await _localAlbumRepository.upsert(updatedDeviceAlbum); | ||||
|         return true; | ||||
|       } | ||||
| 
 | ||||
|  | ||||
| @ -68,7 +68,7 @@ class RemoteImageRequest extends ImageRequest { | ||||
|     final cacheManager = this.cacheManager; | ||||
|     final streamController = StreamController<List<int>>(sync: true); | ||||
|     final Stream<List<int>> stream; | ||||
|     cacheManager?.putStreamedFile(url, streamController.stream); | ||||
|     unawaited(cacheManager?.putStreamedFile(url, streamController.stream)); | ||||
|     stream = response.map((chunk) { | ||||
|       if (_isCancelled) { | ||||
|         throw StateError('Cancelled request'); | ||||
| @ -81,11 +81,11 @@ class RemoteImageRequest extends ImageRequest { | ||||
| 
 | ||||
|     try { | ||||
|       final Uint8List bytes = await _downloadBytes(stream, response.contentLength); | ||||
|       streamController.close(); | ||||
|       unawaited(streamController.close()); | ||||
|       return await ImmutableBuffer.fromUint8List(bytes); | ||||
|     } catch (e) { | ||||
|       streamController.addError(e); | ||||
|       streamController.close(); | ||||
|       unawaited(streamController.close()); | ||||
|       if (_isCancelled) { | ||||
|         return null; | ||||
|       } | ||||
| @ -143,7 +143,7 @@ class RemoteImageRequest extends ImageRequest { | ||||
|       return await _decodeBuffer(buffer, decode, scale); | ||||
|     } catch (e) { | ||||
|       log.severe('Failed to decode cached image', e); | ||||
|       _evictFile(url); | ||||
|       unawaited(_evictFile(url)); | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -361,15 +361,13 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository { | ||||
|     return _db.managers.localAlbumEntity.count(); | ||||
|   } | ||||
| 
 | ||||
|   Future unlinkRemoteAlbum(String id) async { | ||||
|     return _db.localAlbumEntity.update() | ||||
|       ..where((row) => row.id.equals(id)) | ||||
|       ..write(const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null))); | ||||
|   Future<void> unlinkRemoteAlbum(String id) async { | ||||
|     final query = _db.localAlbumEntity.update()..where((row) => row.id.equals(id)); | ||||
|     await query.write(const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null))); | ||||
|   } | ||||
| 
 | ||||
|   Future linkRemoteAlbum(String localAlbumId, String remoteAlbumId) async { | ||||
|     return _db.localAlbumEntity.update() | ||||
|       ..where((row) => row.id.equals(localAlbumId)) | ||||
|       ..write(LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(remoteAlbumId))); | ||||
|   Future<void> linkRemoteAlbum(String localAlbumId, String remoteAlbumId) async { | ||||
|     final query = _db.localAlbumEntity.update()..where((row) => row.id.equals(localAlbumId)); | ||||
|     await query.write(LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(remoteAlbumId))); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -159,7 +159,7 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve | ||||
|     WidgetsBinding.instance.addObserver(this); | ||||
| 
 | ||||
|     // Draw the app from edge to edge | ||||
|     SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); | ||||
|     unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)); | ||||
| 
 | ||||
|     // Sets the navigation bar color | ||||
|     SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle(systemNavigationBarColor: Colors.transparent); | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @ -51,7 +53,7 @@ class AlbumOptionsPage extends HookConsumerWidget { | ||||
|         final isSuccess = await ref.read(albumProvider.notifier).leaveAlbum(album); | ||||
| 
 | ||||
|         if (isSuccess) { | ||||
|           context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); | ||||
|           unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); | ||||
|         } else { | ||||
|           showErrorMessage(); | ||||
|         } | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @ -29,8 +31,8 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget { | ||||
| 
 | ||||
|       if (newAlbum != null) { | ||||
|         ref.watch(albumTitleProvider.notifier).clearAlbumTitle(); | ||||
|         context.maybePop(true); | ||||
|         context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); | ||||
|         unawaited(context.maybePop(true)); | ||||
|         unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); | ||||
|       } | ||||
| 
 | ||||
|       ScaffoldMessenger( | ||||
| @ -109,8 +111,8 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget { | ||||
|         centerTitle: false, | ||||
|         leading: IconButton( | ||||
|           icon: const Icon(Icons.close_rounded), | ||||
|           onPressed: () async { | ||||
|             context.maybePop(); | ||||
|           onPressed: () { | ||||
|             unawaited(context.maybePop()); | ||||
|           }, | ||||
|         ), | ||||
|         actions: [ | ||||
|  | ||||
| @ -155,7 +155,7 @@ class BackupControllerPage extends HookConsumerWidget { | ||||
|                 // waited until returning from selection | ||||
|                 await ref.read(backupProvider.notifier).backupAlbumSelectionDone(); | ||||
|                 // waited until backup albums are stored in DB | ||||
|                 ref.read(albumProvider.notifier).refreshDeviceAlbums(); | ||||
|                 await ref.read(albumProvider.notifier).refreshDeviceAlbums(); | ||||
|               }, | ||||
|               child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(), | ||||
|             ), | ||||
|  | ||||
| @ -270,7 +270,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget { | ||||
|             if (currentUser == null) { | ||||
|               return; | ||||
|             } | ||||
|             ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); | ||||
|             unawaited(ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id)); | ||||
|           }, | ||||
|           child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(), | ||||
|         ), | ||||
|  | ||||
| @ -170,8 +170,8 @@ class DriftUploadDetailPage extends ConsumerWidget { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _showFileDetailDialog(BuildContext context, DriftUploadStatus item) async { | ||||
|     showDialog( | ||||
|   Future<void> _showFileDetailDialog(BuildContext context, DriftUploadStatus item) { | ||||
|     return showDialog( | ||||
|       context: context, | ||||
|       builder: (context) => FileDetailDialog(uploadStatus: item), | ||||
|     ); | ||||
|  | ||||
| @ -33,7 +33,7 @@ class ActivitiesPage extends HookConsumerWidget { | ||||
|     Future<void> onAddComment(String comment) async { | ||||
|       await activityNotifier.addComment(comment); | ||||
|       // Scroll to the end of the list to show the newly added activity | ||||
|       listViewScrollController.animateTo( | ||||
|       await listViewScrollController.animateTo( | ||||
|         listViewScrollController.position.maxScrollExtent + 200, | ||||
|         duration: const Duration(milliseconds: 600), | ||||
|         curve: Curves.fastOutSlowIn, | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @ -170,11 +172,11 @@ class CreateAlbumPage extends HookConsumerWidget { | ||||
|           .createAlbum(ref.read(albumTitleProvider), selectedAssets.value); | ||||
| 
 | ||||
|       if (newAlbum != null) { | ||||
|         ref.read(albumProvider.notifier).refreshRemoteAlbums(); | ||||
|         await ref.read(albumProvider.notifier).refreshRemoteAlbums(); | ||||
|         selectedAssets.value = {}; | ||||
|         ref.read(albumTitleProvider.notifier).clearAlbumTitle(); | ||||
|         ref.read(albumViewerProvider.notifier).disableEditAlbum(); | ||||
|         context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id)); | ||||
|         unawaited(context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id))); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -95,7 +95,7 @@ class GalleryViewerPage extends HookConsumerWidget { | ||||
|       } catch (e) { | ||||
|         // swallow error silently | ||||
|         log.severe('Error precaching next image: $e'); | ||||
|         context.maybePop(); | ||||
|         await context.maybePop(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -267,11 +267,13 @@ class NativeVideoViewerPage extends HookConsumerWidget { | ||||
|       nc.onPlaybackReady.addListener(onPlaybackReady); | ||||
|       nc.onPlaybackEnded.addListener(onPlaybackEnded); | ||||
| 
 | ||||
|       nc.loadVideoSource(source).catchError((error) { | ||||
|         log.severe('Error loading video source: $error'); | ||||
|       }); | ||||
|       unawaited( | ||||
|         nc.loadVideoSource(source).catchError((error) { | ||||
|           log.severe('Error loading video source: $error'); | ||||
|         }), | ||||
|       ); | ||||
|       final loopVideo = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.loopVideo); | ||||
|       nc.setLoop(loopVideo); | ||||
|       unawaited(nc.setLoop(loopVideo)); | ||||
| 
 | ||||
|       controller.value = nc; | ||||
|       Timer(const Duration(milliseconds: 200), checkIfBuffering); | ||||
| @ -342,12 +344,12 @@ class NativeVideoViewerPage extends HookConsumerWidget { | ||||
| 
 | ||||
|     useOnAppLifecycleStateChange((_, state) async { | ||||
|       if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) { | ||||
|         controller.value?.play(); | ||||
|         await controller.value?.play(); | ||||
|       } else if (state == AppLifecycleState.paused) { | ||||
|         final videoPlaying = await controller.value?.isPlaying(); | ||||
|         if (videoPlaying ?? true) { | ||||
|           shouldPlayOnForeground.value = true; | ||||
|           controller.value?.pause(); | ||||
|           await controller.value?.pause(); | ||||
|         } else { | ||||
|           shouldPlayOnForeground.value = false; | ||||
|         } | ||||
|  | ||||
| @ -55,48 +55,50 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> { | ||||
|       final backgroundManager = ref.read(backgroundSyncProvider); | ||||
|       final backupProvider = ref.read(driftBackupProvider.notifier); | ||||
| 
 | ||||
|       ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( | ||||
|         (_) async { | ||||
|           try { | ||||
|             wsProvider.connect(); | ||||
|             infoProvider.getServerInfo(); | ||||
|       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(), | ||||
|                 backgroundManager.syncRemote().then((success) => syncSuccess = success), | ||||
|               ]); | ||||
| 
 | ||||
|               if (syncSuccess) { | ||||
|               if (Store.isBetaTimelineEnabled) { | ||||
|                 bool syncSuccess = false; | ||||
|                 await Future.wait([ | ||||
|                   backgroundManager.hashAssets().then((_) { | ||||
|                     _resumeBackup(backupProvider); | ||||
|                   }), | ||||
|                   _resumeBackup(backupProvider), | ||||
|                   backgroundManager.syncLocal(), | ||||
|                   backgroundManager.syncRemote().then((success) => syncSuccess = success), | ||||
|                 ]); | ||||
|               } else { | ||||
|                 await backgroundManager.hashAssets(); | ||||
|               } | ||||
| 
 | ||||
|               if (Store.get(StoreKey.syncAlbums, false)) { | ||||
|                 await backgroundManager.syncLinkedAlbum(); | ||||
|                 if (syncSuccess) { | ||||
|                   await Future.wait([ | ||||
|                     backgroundManager.hashAssets().then((_) { | ||||
|                       _resumeBackup(backupProvider); | ||||
|                     }), | ||||
|                     _resumeBackup(backupProvider), | ||||
|                   ]); | ||||
|                 } else { | ||||
|                   await backgroundManager.hashAssets(); | ||||
|                 } | ||||
| 
 | ||||
|                 if (Store.get(StoreKey.syncAlbums, false)) { | ||||
|                   await backgroundManager.syncLinkedAlbum(); | ||||
|                 } | ||||
|               } | ||||
|             } catch (e) { | ||||
|               log.severe('Failed establishing connection to the server: $e'); | ||||
|             } | ||||
|           } 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()), | ||||
|         }, | ||||
|           }, | ||||
|           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'); | ||||
|       ref.read(authProvider.notifier).logout(); | ||||
|       context.replaceRoute(const LoginRoute()); | ||||
|       unawaited(ref.read(authProvider.notifier).logout()); | ||||
|       unawaited(context.replaceRoute(const LoginRoute())); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
| @ -106,11 +108,11 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> { | ||||
|       final needBetaMigration = Store.get(StoreKey.needBetaMigration, false); | ||||
|       if (needBetaMigration) { | ||||
|         await Store.put(StoreKey.needBetaMigration, false); | ||||
|         context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)]); | ||||
|         unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)])); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute()); | ||||
|       unawaited(context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute())); | ||||
|     } | ||||
| 
 | ||||
|     if (Store.isBetaTimelineEnabled) { | ||||
| @ -120,7 +122,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> { | ||||
|     final hasPermission = await ref.read(galleryPermissionNotifier.notifier).hasPermission; | ||||
|     if (hasPermission) { | ||||
|       // Resume backup (if enable) then navigate | ||||
|       ref.watch(backupProvider.notifier).resumeBackup(); | ||||
|       await ref.watch(backupProvider.notifier).resumeBackup(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -130,7 +132,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> { | ||||
|     if (isEnableBackup) { | ||||
|       final currentUser = Store.tryGet(StoreKey.currentUser); | ||||
|       if (currentUser != null) { | ||||
|         notifier.handleBackupResume(currentUser.id); | ||||
|         unawaited(notifier.handleBackupResume(currentUser.id)); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -1,13 +1,16 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:crop_image/crop_image.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/utils/hooks/crop_controller_hook.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| 
 | ||||
| import 'edit.page.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| 
 | ||||
| /// A widget for cropping an image. | ||||
| /// This widget uses [HookWidget] to manage its lifecycle and state. It allows | ||||
| @ -35,7 +38,7 @@ class CropImagePage extends HookWidget { | ||||
|             icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), | ||||
|             onPressed: () async { | ||||
|               final croppedImage = await cropController.croppedImage(); | ||||
|               context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true)); | ||||
|               unawaited(context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true))); | ||||
|             }, | ||||
|           ), | ||||
|         ], | ||||
|  | ||||
| @ -1,12 +1,13 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:ui' as ui; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/constants/filters.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| 
 | ||||
| /// A widget for filtering an image. | ||||
| @ -74,7 +75,7 @@ class FilterImagePage extends HookWidget { | ||||
|             icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), | ||||
|             onPressed: () async { | ||||
|               final filteredImage = await applyFilterAndConvert(colorFilter.value); | ||||
|               context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true)); | ||||
|               unawaited(context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true))); | ||||
|             }, | ||||
|           ), | ||||
|         ], | ||||
|  | ||||
| @ -1,14 +1,16 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart' show useState; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/entities/store.entity.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/providers/local_auth.provider.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/widgets/forms/pin_registration_form.dart'; | ||||
| import 'package:immich_mobile/widgets/forms/pin_verification_form.dart'; | ||||
| import 'package:immich_mobile/entities/store.entity.dart'; | ||||
| 
 | ||||
| @RoutePage() | ||||
| class PinAuthPage extends HookConsumerWidget { | ||||
| @ -35,9 +37,9 @@ class PinAuthPage extends HookConsumerWidget { | ||||
|         ); | ||||
| 
 | ||||
|         if (isBetaTimeline) { | ||||
|           context.replaceRoute(const DriftLockedFolderRoute()); | ||||
|           unawaited(context.replaceRoute(const DriftLockedFolderRoute())); | ||||
|         } else { | ||||
|           context.replaceRoute(const LockedRoute()); | ||||
|           unawaited(context.replaceRoute(const LockedRoute())); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @ -333,7 +333,7 @@ class SharedLinkEditPage extends HookConsumerWidget { | ||||
|             changeExpiry: changeExpiry, | ||||
|           ); | ||||
|       ref.invalidate(sharedLinksStateProvider); | ||||
|       context.maybePop(); | ||||
|       await context.maybePop(); | ||||
|     } | ||||
| 
 | ||||
|     return Scaffold( | ||||
|  | ||||
| @ -82,10 +82,12 @@ class PhotosPage extends HookConsumerWidget { | ||||
|       final fullRefresh = refreshCount.value > 0; | ||||
| 
 | ||||
|       if (fullRefresh) { | ||||
|         Future.wait([ | ||||
|           ref.read(assetProvider.notifier).getAllAsset(clear: true), | ||||
|           ref.read(albumProvider.notifier).refreshRemoteAlbums(), | ||||
|         ]); | ||||
|         unawaited( | ||||
|           Future.wait([ | ||||
|             ref.read(assetProvider.notifier).getAllAsset(clear: true), | ||||
|             ref.read(albumProvider.notifier).refreshRemoteAlbums(), | ||||
|           ]), | ||||
|         ); | ||||
| 
 | ||||
|         // refresh was forced: user requested another refresh within 2 seconds | ||||
|         refreshCount.value = 0; | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:math'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| @ -83,7 +84,7 @@ class MapPage extends HookConsumerWidget { | ||||
|         isLoading.value = true; | ||||
|         markers.value = await ref.read(mapMarkersProvider.future); | ||||
|         assetsDebouncer.run(updateAssetsInBounds); | ||||
|         reloadLayers(); | ||||
|         await reloadLayers(); | ||||
|       } finally { | ||||
|         isLoading.value = false; | ||||
|       } | ||||
| @ -128,7 +129,7 @@ class MapPage extends HookConsumerWidget { | ||||
|       ); | ||||
| 
 | ||||
|       if (marker != null) { | ||||
|         updateAssetMarkerPosition(marker); | ||||
|         await updateAssetMarkerPosition(marker); | ||||
|       } else { | ||||
|         // If no asset was previously selected and no new asset is available, close the bottom sheet | ||||
|         if (selectedMarker.value == null) { | ||||
| @ -165,7 +166,7 @@ class MapPage extends HookConsumerWidget { | ||||
|       if (asset.isVideo) { | ||||
|         ref.read(showControlsProvider.notifier).show = false; | ||||
|       } | ||||
|       context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList)); | ||||
|       unawaited(context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList))); | ||||
|     } | ||||
| 
 | ||||
|     /// BOTTOM SHEET CALLBACKS | ||||
| @ -209,7 +210,7 @@ class MapPage extends HookConsumerWidget { | ||||
|       } | ||||
| 
 | ||||
|       if (mapController.value != null && location != null) { | ||||
|         mapController.value!.animateCamera( | ||||
|         await mapController.value!.animateCamera( | ||||
|           CameraUpdate.newLatLngZoom(LatLng(location.latitude, location.longitude), mapZoomToAssetLevel), | ||||
|           duration: const Duration(milliseconds: 800), | ||||
|         ); | ||||
|  | ||||
| @ -8,9 +8,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart'; | ||||
| import 'package:immich_mobile/utils/map_utils.dart'; | ||||
| import 'package:immich_mobile/widgets/map/map_theme_override.dart'; | ||||
| import 'package:maplibre_gl/maplibre_gl.dart'; | ||||
| import 'package:immich_mobile/utils/map_utils.dart'; | ||||
| 
 | ||||
| @RoutePage() | ||||
| class MapLocationPickerPage extends HookConsumerWidget { | ||||
| @ -30,7 +30,7 @@ class MapLocationPickerPage extends HookConsumerWidget { | ||||
| 
 | ||||
|     Future<void> onMapClick(Point<num> point, LatLng centre) async { | ||||
|       selectedLatLng.value = centre; | ||||
|       controller.value?.animateCamera(CameraUpdate.newLatLng(centre)); | ||||
|       await controller.value?.animateCamera(CameraUpdate.newLatLng(centre)); | ||||
|       if (marker.value != null) { | ||||
|         await controller.value?.updateSymbol(marker.value!, SymbolOptions(geometry: centre)); | ||||
|       } | ||||
| @ -49,7 +49,7 @@ class MapLocationPickerPage extends HookConsumerWidget { | ||||
| 
 | ||||
|       var currentLatLng = LatLng(currentLocation.latitude, currentLocation.longitude); | ||||
|       selectedLatLng.value = currentLatLng; | ||||
|       controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng)); | ||||
|       await controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng)); | ||||
|     } | ||||
| 
 | ||||
|     return MapThemeOverride( | ||||
|  | ||||
| @ -266,7 +266,7 @@ class SearchPage extends HookConsumerWidget { | ||||
|         filter.value = filter.value.copyWith(date: SearchDateFilter()); | ||||
| 
 | ||||
|         dateRangeCurrentFilterWidget.value = null; | ||||
|         search(); | ||||
|         unawaited(search()); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
| @ -295,7 +295,7 @@ class SearchPage extends HookConsumerWidget { | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       search(); | ||||
|       unawaited(search()); | ||||
|     } | ||||
| 
 | ||||
|     // MEDIA PICKER | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:collection/collection.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @ -47,7 +49,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { | ||||
|     void leaveAlbum() async { | ||||
|       try { | ||||
|         await ref.read(remoteAlbumProvider.notifier).leaveAlbum(album.id, userId: userId); | ||||
|         context.navigateTo(const DriftAlbumsRoute()); | ||||
|         unawaited(context.navigateTo(const DriftAlbumsRoute())); | ||||
|       } catch (_) { | ||||
|         showErrorMessage(); | ||||
|       } | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @ -179,7 +181,7 @@ class _DriftCreateAlbumPageState extends ConsumerState<DriftCreateAlbumPage> { | ||||
| 
 | ||||
|     if (album != null) { | ||||
|       ref.read(currentRemoteAlbumProvider.notifier).setAlbum(album); | ||||
|       context.replaceRoute(RemoteAlbumRoute(album: album)); | ||||
|       unawaited(context.replaceRoute(RemoteAlbumRoute(album: album))); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| @ -139,7 +141,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> { | ||||
|           toastType: ToastType.success, | ||||
|         ); | ||||
| 
 | ||||
|         context.pushRoute(const DriftAlbumsRoute()); | ||||
|         unawaited(context.pushRoute(const DriftAlbumsRoute())); | ||||
|       } catch (e) { | ||||
|         ImmichToast.show( | ||||
|           context: context, | ||||
| @ -161,12 +163,12 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> { | ||||
|       setState(() { | ||||
|         _album = _album.copyWith(name: result.name, description: result.description ?? ''); | ||||
|       }); | ||||
|       HapticFeedback.mediumImpact(); | ||||
|       unawaited(HapticFeedback.mediumImpact()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<void> showActivity(BuildContext context) async { | ||||
|     context.pushRoute(const DriftActivitiesRoute()); | ||||
|     unawaited(context.pushRoute(const DriftActivitiesRoute())); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> showOptionSheet(BuildContext context) async { | ||||
| @ -175,56 +177,58 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> { | ||||
|     final canAddPhotos = | ||||
|         await ref.read(remoteAlbumServiceProvider).getUserRole(_album.id, user?.id ?? '') == AlbumUserRole.editor; | ||||
| 
 | ||||
|     showModalBottomSheet( | ||||
|       context: context, | ||||
|       backgroundColor: context.colorScheme.surface, | ||||
|       isScrollControlled: false, | ||||
|       builder: (context) { | ||||
|         return DriftRemoteAlbumOption( | ||||
|           onDeleteAlbum: isOwner | ||||
|               ? () async { | ||||
|                   await deleteAlbum(context); | ||||
|                   if (context.mounted) { | ||||
|     unawaited( | ||||
|       showModalBottomSheet( | ||||
|         context: context, | ||||
|         backgroundColor: context.colorScheme.surface, | ||||
|         isScrollControlled: false, | ||||
|         builder: (context) { | ||||
|           return DriftRemoteAlbumOption( | ||||
|             onDeleteAlbum: isOwner | ||||
|                 ? () async { | ||||
|                     await deleteAlbum(context); | ||||
|                     if (context.mounted) { | ||||
|                       context.pop(); | ||||
|                     } | ||||
|                   } | ||||
|                 : null, | ||||
|             onAddUsers: isOwner | ||||
|                 ? () async { | ||||
|                     await addUsers(context); | ||||
|                     context.pop(); | ||||
|                   } | ||||
|                 } | ||||
|               : null, | ||||
|           onAddUsers: isOwner | ||||
|               ? () async { | ||||
|                   await addUsers(context); | ||||
|                   context.pop(); | ||||
|                 } | ||||
|               : null, | ||||
|           onAddPhotos: isOwner || canAddPhotos | ||||
|               ? () async { | ||||
|                   await addAssets(context); | ||||
|                   context.pop(); | ||||
|                 } | ||||
|               : null, | ||||
|           onToggleAlbumOrder: isOwner | ||||
|               ? () async { | ||||
|                   await toggleAlbumOrder(); | ||||
|                   context.pop(); | ||||
|                 } | ||||
|               : null, | ||||
|           onEditAlbum: isOwner | ||||
|               ? () async { | ||||
|                   context.pop(); | ||||
|                   await showEditTitleAndDescription(context); | ||||
|                 } | ||||
|               : null, | ||||
|           onCreateSharedLink: isOwner | ||||
|               ? () async { | ||||
|                   context.pop(); | ||||
|                   context.pushRoute(SharedLinkEditRoute(albumId: _album.id)); | ||||
|                 } | ||||
|               : null, | ||||
|           onShowOptions: () { | ||||
|             context.pop(); | ||||
|             context.pushRoute(const DriftAlbumOptionsRoute()); | ||||
|           }, | ||||
|         ); | ||||
|       }, | ||||
|                 : null, | ||||
|             onAddPhotos: isOwner || canAddPhotos | ||||
|                 ? () async { | ||||
|                     await addAssets(context); | ||||
|                     context.pop(); | ||||
|                   } | ||||
|                 : null, | ||||
|             onToggleAlbumOrder: isOwner | ||||
|                 ? () async { | ||||
|                     await toggleAlbumOrder(); | ||||
|                     context.pop(); | ||||
|                   } | ||||
|                 : null, | ||||
|             onEditAlbum: isOwner | ||||
|                 ? () async { | ||||
|                     context.pop(); | ||||
|                     await showEditTitleAndDescription(context); | ||||
|                   } | ||||
|                 : null, | ||||
|             onCreateSharedLink: isOwner | ||||
|                 ? () async { | ||||
|                     context.pop(); | ||||
|                     unawaited(context.pushRoute(SharedLinkEditRoute(albumId: _album.id))); | ||||
|                   } | ||||
|                 : null, | ||||
|             onShowOptions: () { | ||||
|               context.pop(); | ||||
|               context.pushRoute(const DriftAlbumOptionsRoute()); | ||||
|             }, | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:crop_image/crop_image.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| @ -34,7 +36,7 @@ class DriftCropImagePage extends HookWidget { | ||||
|             icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), | ||||
|             onPressed: () async { | ||||
|               final croppedImage = await cropController.croppedImage(); | ||||
|               context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true)); | ||||
|               unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true))); | ||||
|             }, | ||||
|           ), | ||||
|         ], | ||||
|  | ||||
| @ -70,7 +70,7 @@ class DriftEditImagePage extends ConsumerWidget { | ||||
|         Logger("SaveEditedImage").warning("Failed to retrieve the saved image back from OS", e); | ||||
|       } | ||||
| 
 | ||||
|       ref.read(backgroundSyncProvider).syncLocal(full: true); | ||||
|       unawaited(ref.read(backgroundSyncProvider).syncLocal(full: true)); | ||||
|       _exitEditing(context); | ||||
|       ImmichToast.show(durationInSecond: 3, context: context, msg: 'Image Saved!'); | ||||
| 
 | ||||
|  | ||||
| @ -75,7 +75,7 @@ class DriftFilterImagePage extends HookWidget { | ||||
|             icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), | ||||
|             onPressed: () async { | ||||
|               final filteredImage = await applyFilterAndConvert(colorFilter.value); | ||||
|               context.pushRoute(DriftEditImageRoute(asset: asset, image: filteredImage, isEdited: true)); | ||||
|               unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: filteredImage, isEdited: true))); | ||||
|             }, | ||||
|           ), | ||||
|         ], | ||||
|  | ||||
| @ -271,7 +271,7 @@ class DriftSearchPage extends HookConsumerWidget { | ||||
|         filter.value = filter.value.copyWith(date: SearchDateFilter()); | ||||
| 
 | ||||
|         dateRangeCurrentFilterWidget.value = null; | ||||
|         search(); | ||||
|         unawaited(search()); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
| @ -301,7 +301,7 @@ class DriftSearchPage extends HookConsumerWidget { | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       search(); | ||||
|       unawaited(search()); | ||||
|     } | ||||
| 
 | ||||
|     // MEDIA PICKER | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/constants/enums.dart'; | ||||
| @ -15,7 +17,7 @@ class AdvancedInfoActionButton extends ConsumerWidget { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     ref.read(actionProvider.notifier).troubleshoot(source, context); | ||||
|     unawaited(ref.read(actionProvider.notifier).troubleshoot(source, context)); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|  | ||||
| @ -39,7 +39,7 @@ class ShareActionButton extends ConsumerWidget { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     showDialog( | ||||
|     await showDialog( | ||||
|       context: context, | ||||
|       builder: (BuildContext buildContext) { | ||||
|         ref.read(actionProvider.notifier).shareAssets(source, context).then((ActionResult result) { | ||||
|  | ||||
| @ -121,7 +121,7 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> { | ||||
| 
 | ||||
|     // we need to re-filter the albums after sorting | ||||
|     // so shownAlbums gets updated | ||||
|     filterAlbums(); | ||||
|     unawaited(filterAlbums()); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> filterAlbums() async { | ||||
| @ -711,7 +711,7 @@ class AddToAlbumHeader extends ConsumerWidget { | ||||
| 
 | ||||
|       ref.read(currentRemoteAlbumProvider.notifier).setAlbum(newAlbum); | ||||
|       ref.read(multiSelectProvider.notifier).reset(); | ||||
|       context.pushRoute(RemoteAlbumRoute(album: newAlbum)); | ||||
|       unawaited(context.pushRoute(RemoteAlbumRoute(album: newAlbum))); | ||||
|     } | ||||
| 
 | ||||
|     return SliverPadding( | ||||
|  | ||||
| @ -635,9 +635,9 @@ class _AssetViewerState extends ConsumerState<AssetViewer> { | ||||
|     // Listen for control visibility changes and change system UI mode accordingly | ||||
|     ref.listen(assetViewerProvider.select((value) => value.showingControls), (_, showingControls) async { | ||||
|       if (showingControls) { | ||||
|         SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); | ||||
|         unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)); | ||||
|       } else { | ||||
|         SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); | ||||
|         unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky)); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|  | ||||
| @ -295,11 +295,13 @@ class NativeVideoViewer extends HookConsumerWidget { | ||||
|       nc.onPlaybackReady.addListener(onPlaybackReady); | ||||
|       nc.onPlaybackEnded.addListener(onPlaybackEnded); | ||||
| 
 | ||||
|       nc.loadVideoSource(source).catchError((error) { | ||||
|         log.severe('Error loading video source: $error'); | ||||
|       }); | ||||
|       unawaited( | ||||
|         nc.loadVideoSource(source).catchError((error) { | ||||
|           log.severe('Error loading video source: $error'); | ||||
|         }), | ||||
|       ); | ||||
|       final loopVideo = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.loopVideo); | ||||
|       nc.setLoop(!asset.isMotionPhoto && loopVideo); | ||||
|       unawaited(nc.setLoop(!asset.isMotionPhoto && loopVideo)); | ||||
| 
 | ||||
|       controller.value = nc; | ||||
|       Timer(const Duration(milliseconds: 200), checkIfBuffering); | ||||
| @ -373,12 +375,12 @@ class NativeVideoViewer extends HookConsumerWidget { | ||||
| 
 | ||||
|     useOnAppLifecycleStateChange((_, state) async { | ||||
|       if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) { | ||||
|         controller.value?.play(); | ||||
|         await controller.value?.play(); | ||||
|       } else if (state == AppLifecycleState.paused) { | ||||
|         final videoPlaying = await controller.value?.isPlaying(); | ||||
|         if (videoPlaying ?? true) { | ||||
|           shouldPlayOnForeground.value = true; | ||||
|           controller.value?.pause(); | ||||
|           await controller.value?.pause(); | ||||
|         } else { | ||||
|           shouldPlayOnForeground.value = false; | ||||
|         } | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:async/async.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
| import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; | ||||
| @ -51,14 +53,14 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide | ||||
|   Stream<ImageInfo> loadRequest(ImageRequest request, ImageDecoderCallback decode) async* { | ||||
|     if (isCancelled) { | ||||
|       this.request = null; | ||||
|       evict(); | ||||
|       unawaited(evict()); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       final image = await request.load(decode); | ||||
|       if (image == null || isCancelled) { | ||||
|         evict(); | ||||
|         unawaited(evict()); | ||||
|         return; | ||||
|       } | ||||
|       yield image; | ||||
|  | ||||
| @ -85,7 +85,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv | ||||
|     yield* initialImageStream(); | ||||
| 
 | ||||
|     if (isCancelled) { | ||||
|       evict(); | ||||
|       unawaited(evict()); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
| @ -103,7 +103,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv | ||||
|     } | ||||
| 
 | ||||
|     if (isCancelled) { | ||||
|       evict(); | ||||
|       unawaited(evict()); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -87,7 +87,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr | ||||
|     yield* initialImageStream(); | ||||
| 
 | ||||
|     if (isCancelled) { | ||||
|       evict(); | ||||
|       unawaited(evict()); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
| @ -100,7 +100,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr | ||||
|     yield* loadRequest(request, decode); | ||||
| 
 | ||||
|     if (isCancelled) { | ||||
|       evict(); | ||||
|       unawaited(evict()); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -115,12 +115,14 @@ class _DriftMapState extends ConsumerState<DriftMap> { | ||||
|     } | ||||
| 
 | ||||
|     final bounds = await controller.getVisibleRegion(); | ||||
|     _reloadMutex.run(() async { | ||||
|       if (mounted && ref.read(mapStateProvider.notifier).setBounds(bounds)) { | ||||
|         final markers = await ref.read(mapMarkerProvider(bounds).future); | ||||
|         await reloadMarkers(markers); | ||||
|       } | ||||
|     }); | ||||
|     unawaited( | ||||
|       _reloadMutex.run(() async { | ||||
|         if (mounted && ref.read(mapStateProvider.notifier).setBounds(bounds)) { | ||||
|           final markers = await ref.read(mapMarkerProvider(bounds).future); | ||||
|           await reloadMarkers(markers); | ||||
|         } | ||||
|       }), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> reloadMarkers(Map<String, dynamic> markers) async { | ||||
| @ -148,7 +150,7 @@ class _DriftMapState extends ConsumerState<DriftMap> { | ||||
| 
 | ||||
|     final controller = mapController; | ||||
|     if (controller != null && location != null) { | ||||
|       controller.animateCamera( | ||||
|       await controller.animateCamera( | ||||
|         CameraUpdate.newLatLngZoom(LatLng(location.latitude, location.longitude), MapUtils.mapZoomToAssetLevel), | ||||
|         duration: const Duration(milliseconds: 800), | ||||
|       ); | ||||
|  | ||||
| @ -73,7 +73,7 @@ class MapUtils { | ||||
|     try { | ||||
|       bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); | ||||
|       if (!serviceEnabled && !silent) { | ||||
|         showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context)); | ||||
|         unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context))); | ||||
|         return (null, LocationPermission.deniedForever); | ||||
|       } | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:math' as math; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| @ -15,8 +16,8 @@ import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart' | ||||
| import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart'; | ||||
| import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; | ||||
| import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; | ||||
| import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; | ||||
| import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; | ||||
| import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; | ||||
| import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| 
 | ||||
| @ -156,11 +157,13 @@ class _AssetTileWidget extends ConsumerWidget { | ||||
|       await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1); | ||||
|       ref.read(isPlayingMotionVideoProvider.notifier).playing = false; | ||||
|       AssetViewer.setAsset(ref, asset); | ||||
|       ctx.pushRoute( | ||||
|         AssetViewerRoute( | ||||
|           initialIndex: assetIndex, | ||||
|           timelineService: ref.read(timelineServiceProvider), | ||||
|           heroOffset: heroOffset, | ||||
|       unawaited( | ||||
|         ctx.pushRoute( | ||||
|           AssetViewerRoute( | ||||
|             initialIndex: assetIndex, | ||||
|             timelineService: ref.read(timelineServiceProvider), | ||||
|             heroOffset: heroOffset, | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
| @ -242,7 +242,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> { | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       LogService.I.flush(); | ||||
|       await LogService.I.flush(); | ||||
|     } catch (_) {} | ||||
|   } | ||||
| 
 | ||||
| @ -255,7 +255,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> { | ||||
| 
 | ||||
|     // Flush logs before closing database | ||||
|     try { | ||||
|       LogService.I.flush(); | ||||
|       await LogService.I.flush(); | ||||
|     } catch (_) {} | ||||
| 
 | ||||
|     // Close Isar database safely | ||||
|  | ||||
| @ -98,7 +98,7 @@ class AssetNotifier extends StateNotifier<bool> { | ||||
| 
 | ||||
|   Future<void> onNewAssetUploaded(Asset newAsset) async { | ||||
|     // eTag on device is not valid after partially modifying the assets | ||||
|     Store.delete(StoreKey.assetETag); | ||||
|     await Store.delete(StoreKey.assetETag); | ||||
|     await _syncService.syncNewAssetToDb(newAsset); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -1,14 +1,16 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:background_downloader/background_downloader.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:fluttertoast/fluttertoast.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/models/download/download_state.model.dart'; | ||||
| import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; | ||||
| import 'package:immich_mobile/services/album.service.dart'; | ||||
| import 'package:immich_mobile/services/download.service.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/services/share.service.dart'; | ||||
| import 'package:immich_mobile/widgets/common/immich_toast.dart'; | ||||
| import 'package:immich_mobile/widgets/common/share_dialog.dart'; | ||||
| @ -159,24 +161,26 @@ class DownloadStateNotifier extends StateNotifier<DownloadState> { | ||||
|   } | ||||
| 
 | ||||
|   void shareAsset(Asset asset, BuildContext context) async { | ||||
|     showDialog( | ||||
|       context: context, | ||||
|       builder: (BuildContext buildContext) { | ||||
|         _shareService.shareAsset(asset, context).then((bool status) { | ||||
|           if (!status) { | ||||
|             ImmichToast.show( | ||||
|               context: context, | ||||
|               msg: 'image_viewer_page_state_provider_share_error'.tr(), | ||||
|               toastType: ToastType.error, | ||||
|               gravity: ToastGravity.BOTTOM, | ||||
|             ); | ||||
|           } | ||||
|           buildContext.pop(); | ||||
|         }); | ||||
|         return const ShareDialog(); | ||||
|       }, | ||||
|       barrierDismissible: false, | ||||
|       useRootNavigator: false, | ||||
|     unawaited( | ||||
|       showDialog( | ||||
|         context: context, | ||||
|         builder: (BuildContext buildContext) { | ||||
|           _shareService.shareAsset(asset, context).then((bool status) { | ||||
|             if (!status) { | ||||
|               ImmichToast.show( | ||||
|                 context: context, | ||||
|                 msg: 'image_viewer_page_state_provider_share_error'.tr(), | ||||
|                 toastType: ToastType.error, | ||||
|                 gravity: ToastGravity.BOTTOM, | ||||
|               ); | ||||
|             } | ||||
|             buildContext.pop(); | ||||
|           }); | ||||
|           return const ShareDialog(); | ||||
|         }, | ||||
|         barrierDismissible: false, | ||||
|         useRootNavigator: false, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -104,7 +104,7 @@ class ShareIntentUploadStateNotifier extends StateNotifier<List<ShareIntentAttac | ||||
|   Future<void> upload(File file) async { | ||||
|     final task = await _buildUploadTask(hash(file.path).toString(), file); | ||||
| 
 | ||||
|     _uploadService.enqueueTasks([task]); | ||||
|     await _uploadService.enqueueTasks([task]); | ||||
|   } | ||||
| 
 | ||||
|   Future<UploadTask> _buildUploadTask(String id, File file, {Map<String, String>? fields}) async { | ||||
|  | ||||
| @ -380,7 +380,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
| 
 | ||||
|     state = state.copyWith(backgroundBackup: isEnabled); | ||||
|     if (isEnabled != Store.get(StoreKey.backgroundBackup, !isEnabled)) { | ||||
|       Store.put(StoreKey.backgroundBackup, isEnabled); | ||||
|       await Store.put(StoreKey.backgroundBackup, isEnabled); | ||||
|     } | ||||
| 
 | ||||
|     if (state.backupProgress != BackUpProgressEnum.inBackground) { | ||||
| @ -474,7 +474,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|       ); | ||||
|       await notifyBackgroundServiceCanRun(); | ||||
|     } else { | ||||
|       openAppSettings(); | ||||
|       await openAppSettings(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -533,10 +533,10 @@ class BackupNotifier extends StateNotifier<BackUpState> { | ||||
|         progressInFileSpeedUpdateTime: DateTime.now(), | ||||
|         progressInFileSpeedUpdateSentBytes: 0, | ||||
|       ); | ||||
|       _updatePersistentAlbumsSelection(); | ||||
|       await _updatePersistentAlbumsSelection(); | ||||
|     } | ||||
| 
 | ||||
|     updateDiskInfo(); | ||||
|     await updateDiskInfo(); | ||||
|   } | ||||
| 
 | ||||
|   void _onUploadProgress(int sent, int total) { | ||||
|  | ||||
| @ -2,10 +2,10 @@ import 'dart:async'; | ||||
| 
 | ||||
| import 'package:connectivity_plus/connectivity_plus.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:immich_mobile/providers/backup/backup.provider.dart'; | ||||
| import 'package:immich_mobile/services/backup_verification.service.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/providers/asset.provider.dart'; | ||||
| import 'package:immich_mobile/providers/backup/backup.provider.dart'; | ||||
| import 'package:immich_mobile/services/backup_verification.service.dart'; | ||||
| import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; | ||||
| import 'package:immich_mobile/widgets/common/immich_toast.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| @ -44,7 +44,7 @@ class BackupVerification extends _$BackupVerification { | ||||
|         } | ||||
|         return; | ||||
|       } | ||||
|       WakelockPlus.enable(); | ||||
|       unawaited(WakelockPlus.enable()); | ||||
| 
 | ||||
|       const limit = 100; | ||||
|       final toDelete = await ref.read(backupVerificationServiceProvider).findWronglyBackedUpAssets(limit: limit); | ||||
| @ -73,7 +73,7 @@ class BackupVerification extends _$BackupVerification { | ||||
|         } | ||||
|       } | ||||
|     } finally { | ||||
|       WakelockPlus.disable(); | ||||
|       unawaited(WakelockPlus.disable()); | ||||
|       state = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -7,7 +7,7 @@ part of 'backup_verification.provider.dart'; | ||||
| // ************************************************************************** | ||||
| 
 | ||||
| String _$backupVerificationHash() => | ||||
|     r'b204e43ab575d5fa5b2ee663297f32bcee9074f5'; | ||||
|     r'b4b34909ed1af3f28877ea457d53a4a18b6417f8'; | ||||
| 
 | ||||
| /// See also [BackupVerification]. | ||||
| @ProviderFor(BackupVerification) | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:cancellation_token_http/http.dart'; | ||||
| @ -26,11 +27,11 @@ import 'package:immich_mobile/services/backup.service.dart'; | ||||
| import 'package:immich_mobile/services/backup_album.service.dart'; | ||||
| import 'package:immich_mobile/services/local_notification.service.dart'; | ||||
| import 'package:immich_mobile/utils/backup_progress.dart'; | ||||
| import 'package:immich_mobile/utils/debug_print.dart'; | ||||
| import 'package:immich_mobile/widgets/common/immich_toast.dart'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:permission_handler/permission_handler.dart'; | ||||
| import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; | ||||
| import 'package:immich_mobile/utils/debug_print.dart'; | ||||
| 
 | ||||
| final manualUploadProvider = StateNotifierProvider<ManualUploadNotifier, ManualUploadState>((ref) { | ||||
|   return ManualUploadNotifier( | ||||
| @ -294,7 +295,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> { | ||||
|           ); | ||||
|         } | ||||
|       } else { | ||||
|         openAppSettings(); | ||||
|         unawaited(openAppSettings()); | ||||
|         dPrint(() => "[_startUpload] Do not have permission to the gallery"); | ||||
|       } | ||||
|     } catch (e) { | ||||
|  | ||||
| @ -3,7 +3,6 @@ import 'dart:io'; | ||||
| import 'dart:ui' as ui; | ||||
| 
 | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| 
 | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/painting.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| @ -77,7 +76,7 @@ class ImmichLocalImageProvider extends ImageProvider<ImmichLocalImageProvider> { | ||||
|     } catch (error, stack) { | ||||
|       log.severe('Error loading local image ${asset.fileName}', error, stack); | ||||
|     } finally { | ||||
|       chunkEvents.close(); | ||||
|       unawaited(chunkEvents.close()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:background_downloader/background_downloader.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @ -70,7 +72,7 @@ class ActionNotifier extends Notifier<void> { | ||||
|   void _downloadLivePhotoCallback(TaskStatusUpdate update) async { | ||||
|     if (update.status == TaskStatus.complete) { | ||||
|       final livePhotosId = LivePhotosMetadata.fromJson(update.task.metaData).id; | ||||
|       _downloadService.saveLivePhotos(update.task, livePhotosId); | ||||
|       unawaited(_downloadService.saveLivePhotos(update.task, livePhotosId)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -131,7 +133,7 @@ class ActionNotifier extends Notifier<void> { | ||||
|     if (assets.length > 1) { | ||||
|       return ActionResult(count: assets.length, success: false, error: 'Cannot troubleshoot multiple assets'); | ||||
|     } | ||||
|     context.pushRoute(AssetTroubleshootRoute(asset: assets.first)); | ||||
|     unawaited(context.pushRoute(AssetTroubleshootRoute(asset: assets.first))); | ||||
| 
 | ||||
|     return ActionResult(count: assets.length, success: true); | ||||
|   } | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; | ||||
| import 'package:immich_mobile/services/shared_link.service.dart'; | ||||
| @ -16,7 +18,7 @@ class SharedLinksNotifier extends StateNotifier<AsyncValue<List<SharedLink>>> { | ||||
|   Future<void> deleteLink(String id) async { | ||||
|     await _sharedLinkService.deleteSharedLink(id); | ||||
|     state = const AsyncLoading(); | ||||
|     fetchLinks(); | ||||
|     unawaited(fetchLinks()); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:device_info_plus/device_info_plus.dart'; | ||||
| @ -138,18 +139,20 @@ class AssetMediaRepository { | ||||
|     // we dont want to await the share result since the | ||||
|     // "preparing" dialog will not disappear until | ||||
|     final size = context.sizeData; | ||||
|     Share.shareXFiles( | ||||
|       downloadedXFiles, | ||||
|       sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), | ||||
|     ).then((result) async { | ||||
|       for (var file in tempFiles) { | ||||
|         try { | ||||
|           await file.delete(); | ||||
|         } catch (e) { | ||||
|           _log.warning("Failed to delete temporary file: ${file.path}", e); | ||||
|     unawaited( | ||||
|       Share.shareXFiles( | ||||
|         downloadedXFiles, | ||||
|         sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), | ||||
|       ).then((result) async { | ||||
|         for (var file in tempFiles) { | ||||
|           try { | ||||
|             await file.delete(); | ||||
|           } catch (e) { | ||||
|             _log.warning("Failed to delete temporary file: ${file.path}", e); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|       }), | ||||
|     ); | ||||
| 
 | ||||
|     return downloadedXFiles.length; | ||||
|   } | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @ -12,7 +14,7 @@ class AppNavigationObserver extends AutoRouterObserver { | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) async { | ||||
|     Future(() => ref.read(inLockedViewProvider.notifier).state = false); | ||||
|     unawaited(Future(() => ref.read(inLockedViewProvider.notifier).state = false)); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| @ -26,18 +27,18 @@ class AuthGuard extends AutoRouteGuard { | ||||
|       if (res == null || res.authStatus != true) { | ||||
|         // If the access token is invalid, take user back to login | ||||
|         _log.fine('User token is invalid. Redirecting to login'); | ||||
|         router.replaceAll([const LoginRoute()]); | ||||
|         unawaited(router.replaceAll([const LoginRoute()])); | ||||
|       } | ||||
|     } on StoreKeyNotFoundException catch (_) { | ||||
|       // If there is no access token, take us to the login page | ||||
|       _log.warning('No access token in the store.'); | ||||
|       router.replaceAll([const LoginRoute()]); | ||||
|       unawaited(router.replaceAll([const LoginRoute()])); | ||||
|       return; | ||||
|     } on ApiException catch (e) { | ||||
|       // On an unauthorized request, take us to the login page | ||||
|       if (e.code == HttpStatus.unauthorized) { | ||||
|         _log.warning("Unauthorized access token."); | ||||
|         router.replaceAll([const LoginRoute()]); | ||||
|         unawaited(router.replaceAll([const LoginRoute()])); | ||||
|         return; | ||||
|       } | ||||
|     } catch (e) { | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:immich_mobile/providers/gallery_permission.provider.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| @ -13,7 +15,7 @@ class BackupPermissionGuard extends AutoRouteGuard { | ||||
|     if (p) { | ||||
|       resolver.next(true); | ||||
|     } else { | ||||
|       router.push(const PermissionOnboardingRoute()); | ||||
|       unawaited(router.push(const PermissionOnboardingRoute())); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| 
 | ||||
| @ -13,12 +15,14 @@ class GalleryGuard extends AutoRouteGuard { | ||||
|       // Replace instead of pushing duplicate | ||||
|       final args = resolver.route.args as GalleryViewerRouteArgs; | ||||
| 
 | ||||
|       router.replace( | ||||
|         GalleryViewerRoute( | ||||
|           renderList: args.renderList, | ||||
|           initialIndex: args.initialIndex, | ||||
|           heroOffset: args.heroOffset, | ||||
|           showStack: args.showStack, | ||||
|       unawaited( | ||||
|         router.replace( | ||||
|           GalleryViewerRoute( | ||||
|             renderList: args.renderList, | ||||
|             initialIndex: args.initialIndex, | ||||
|             heroOffset: args.heroOffset, | ||||
|             showStack: args.showStack, | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|       // Prevent further navigation since we replaced the route | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:immich_mobile/constants/constants.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| 
 | ||||
| import 'package:immich_mobile/services/api.service.dart'; | ||||
| import 'package:immich_mobile/services/local_auth.service.dart'; | ||||
| import 'package:immich_mobile/services/secure_storage.service.dart'; | ||||
| @ -30,7 +31,7 @@ class LockedGuard extends AutoRouteGuard { | ||||
| 
 | ||||
|     /// Check if a pincode has been created but this user. Show the form to create if not exist | ||||
|     if (!authStatus.pinCode) { | ||||
|       router.push(PinAuthRoute(createPinCode: true)); | ||||
|       unawaited(router.push(PinAuthRoute(createPinCode: true))); | ||||
|     } | ||||
| 
 | ||||
|     if (authStatus.isElevated) { | ||||
| @ -42,7 +43,7 @@ class LockedGuard extends AutoRouteGuard { | ||||
|     /// the user has enabled the biometric authentication | ||||
|     final securePinCode = await _secureStorageService.read(kSecuredPinCode); | ||||
|     if (securePinCode == null) { | ||||
|       router.push(PinAuthRoute()); | ||||
|       unawaited(router.push(PinAuthRoute())); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
| @ -74,7 +75,7 @@ class LockedGuard extends AutoRouteGuard { | ||||
|     } on ApiException { | ||||
|       // PIN code has changed, need to re-enter to access | ||||
|       await _secureStorageService.delete(kSecuredPinCode); | ||||
|       router.push(PinAuthRoute()); | ||||
|       unawaited(router.push(PinAuthRoute())); | ||||
|     } catch (error) { | ||||
|       _log.severe("Failed to access locked page", error); | ||||
|       resolver.next(false); | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @ -50,7 +52,7 @@ class ActionService { | ||||
|   ); | ||||
| 
 | ||||
|   Future<void> shareLink(List<String> remoteIds, BuildContext context) async { | ||||
|     context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds)); | ||||
|     unawaited(context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds))); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> favorite(List<String> remoteIds) async { | ||||
|  | ||||
| @ -83,7 +83,7 @@ class AlbumService { | ||||
|       if (selectedIds.isEmpty) { | ||||
|         final numLocal = await _albumRepository.count(local: true); | ||||
|         if (numLocal > 0) { | ||||
|           _syncService.removeAllLocalAlbumsAndAssets(); | ||||
|           await _syncService.removeAllLocalAlbumsAndAssets(); | ||||
|         } | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
| @ -6,11 +6,11 @@ import 'package:device_info_plus/device_info_plus.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:immich_mobile/domain/models/store.model.dart'; | ||||
| import 'package:immich_mobile/entities/store.entity.dart'; | ||||
| import 'package:immich_mobile/utils/debug_print.dart'; | ||||
| import 'package:immich_mobile/utils/url_helper.dart'; | ||||
| import 'package:immich_mobile/utils/user_agent.dart'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:immich_mobile/utils/user_agent.dart'; | ||||
| import 'package:immich_mobile/utils/debug_print.dart'; | ||||
| 
 | ||||
| class ApiService implements Authentication { | ||||
|   late ApiClient _apiClient; | ||||
| @ -86,7 +86,7 @@ class ApiService implements Authentication { | ||||
|     setEndpoint(endpoint); | ||||
| 
 | ||||
|     // Save in local database for next startup | ||||
|     Store.put(StoreKey.serverEndpoint, endpoint); | ||||
|     await Store.put(StoreKey.serverEndpoint, endpoint); | ||||
|     return endpoint; | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -58,7 +58,7 @@ class AuthService { | ||||
|   Future<String> validateServerUrl(String url) async { | ||||
|     final validUrl = await _apiService.resolveAndSetEndpoint(url); | ||||
|     await _apiService.setDeviceInfoHeader(); | ||||
|     Store.put(StoreKey.serverUrl, validUrl); | ||||
|     await Store.put(StoreKey.serverUrl, validUrl); | ||||
| 
 | ||||
|     return validUrl; | ||||
|   } | ||||
|  | ||||
| @ -291,7 +291,7 @@ class BackgroundService { | ||||
|       case "backgroundProcessing": | ||||
|       case "onAssetsChanged": | ||||
|         try { | ||||
|           _clearErrorNotifications(); | ||||
|           unawaited(_clearErrorNotifications()); | ||||
| 
 | ||||
|           // iOS should time out after some threshold so it doesn't wait | ||||
|           // indefinitely and can run later | ||||
| @ -342,7 +342,7 @@ class BackgroundService { | ||||
|     ); | ||||
| 
 | ||||
|     HttpSSLOptions.apply(); | ||||
|     ref.read(apiServiceProvider).setAccessToken(Store.get(StoreKey.accessToken)); | ||||
|     await ref.read(apiServiceProvider).setAccessToken(Store.get(StoreKey.accessToken)); | ||||
|     await ref.read(authServiceProvider).setOpenApiServiceEndpoint(); | ||||
|     dPrint(() => "[BG UPLOAD] Using endpoint: ${ref.read(apiServiceProvider).apiClient.basePath}"); | ||||
| 
 | ||||
| @ -385,7 +385,7 @@ class BackgroundService { | ||||
|         await ref.read(backupAlbumRepositoryProvider).deleteAll(toDelete); | ||||
|         await ref.read(backupAlbumRepositoryProvider).updateAll(toUpsert); | ||||
|       } else if (Store.tryGet(StoreKey.backupFailedSince) == null) { | ||||
|         Store.put(StoreKey.backupFailedSince, DateTime.now()); | ||||
|         await Store.put(StoreKey.backupFailedSince, DateTime.now()); | ||||
|         return false; | ||||
|       } | ||||
|       // Android should check for new assets added while performing backup | ||||
| @ -412,9 +412,11 @@ class BackgroundService { | ||||
|     try { | ||||
|       toUpload = await backupService.removeAlreadyUploadedAssets(toUpload); | ||||
|     } catch (e) { | ||||
|       _showErrorNotification( | ||||
|         title: "backup_background_service_error_title".tr(), | ||||
|         content: "backup_background_service_connection_failed_message".tr(), | ||||
|       unawaited( | ||||
|         _showErrorNotification( | ||||
|           title: "backup_background_service_error_title".tr(), | ||||
|           content: "backup_background_service_connection_failed_message".tr(), | ||||
|         ), | ||||
|       ); | ||||
|       return false; | ||||
|     } | ||||
| @ -428,13 +430,15 @@ class BackgroundService { | ||||
|     } | ||||
|     _assetsToUploadCount = toUpload.length; | ||||
|     _uploadedAssetsCount = 0; | ||||
|     _updateNotification( | ||||
|       title: "backup_background_service_in_progress_notification".tr(), | ||||
|       content: notifyTotalProgress ? formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount) : null, | ||||
|       progress: 0, | ||||
|       max: notifyTotalProgress ? _assetsToUploadCount : 0, | ||||
|       indeterminate: !notifyTotalProgress, | ||||
|       onlyIfFG: !notifyTotalProgress, | ||||
|     unawaited( | ||||
|       _updateNotification( | ||||
|         title: "backup_background_service_in_progress_notification".tr(), | ||||
|         content: notifyTotalProgress ? formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount) : null, | ||||
|         progress: 0, | ||||
|         max: notifyTotalProgress ? _assetsToUploadCount : 0, | ||||
|         indeterminate: !notifyTotalProgress, | ||||
|         onlyIfFG: !notifyTotalProgress, | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     _cancellationToken = CancellationToken(); | ||||
| @ -452,9 +456,11 @@ class BackgroundService { | ||||
|     ); | ||||
| 
 | ||||
|     if (!ok && !_cancellationToken!.isCancelled) { | ||||
|       _showErrorNotification( | ||||
|         title: "backup_background_service_error_title".tr(), | ||||
|         content: "backup_background_service_backup_failed_message".tr(), | ||||
|       unawaited( | ||||
|         _showErrorNotification( | ||||
|           title: "backup_background_service_error_title".tr(), | ||||
|           content: "backup_background_service_backup_failed_message".tr(), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -120,7 +120,7 @@ class BackupVerificationService { | ||||
|     await tuple.fileMediaRepository.enableBackgroundAccess(); | ||||
|     final ApiService apiService = ApiService(); | ||||
|     apiService.setEndpoint(tuple.endpoint); | ||||
|     apiService.setAccessToken(tuple.auth); | ||||
|     await apiService.setAccessToken(tuple.auth); | ||||
|     for (int i = 0; i < tuple.deleteCandidates.length; i++) { | ||||
|       if (await _compareAssets(tuple.deleteCandidates[i], tuple.originals[i], apiService)) { | ||||
|         result.add(tuple.deleteCandidates[i]); | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| import 'package:immich_mobile/mixins/error_logger.mixin.dart'; | ||||
| import 'package:immich_mobile/models/map/map_marker.model.dart'; | ||||
| import 'package:immich_mobile/services/api.service.dart'; | ||||
| import 'package:immich_mobile/utils/user_agent.dart'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:maplibre_gl/maplibre_gl.dart'; | ||||
| import 'package:immich_mobile/utils/user_agent.dart'; | ||||
| 
 | ||||
| class MapService with ErrorLoggerMixin { | ||||
|   final ApiService _apiService; | ||||
| @ -16,7 +16,7 @@ class MapService with ErrorLoggerMixin { | ||||
| 
 | ||||
|   Future<void> _setMapUserAgentHeader() async { | ||||
|     final userAgent = await getUserAgentString(); | ||||
|     setHttpHeaders({'User-Agent': userAgent}); | ||||
|     await setHttpHeaders({'User-Agent': userAgent}); | ||||
|   } | ||||
| 
 | ||||
|   Future<Iterable<MapMarker>> getMapMarkers({ | ||||
|  | ||||
| @ -1,13 +1,15 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/extensions/response_extensions.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/extensions/response_extensions.dart'; | ||||
| import 'package:immich_mobile/providers/api.provider.dart'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:path_provider/path_provider.dart'; | ||||
| import 'package:share_plus/share_plus.dart'; | ||||
| 
 | ||||
| import 'api.service.dart'; | ||||
| 
 | ||||
| final shareServiceProvider = Provider((ref) => ShareService(ref.watch(apiServiceProvider))); | ||||
| @ -58,9 +60,11 @@ class ShareService { | ||||
|       } | ||||
| 
 | ||||
|       final size = MediaQuery.of(context).size; | ||||
|       Share.shareXFiles( | ||||
|         downloadedXFiles, | ||||
|         sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), | ||||
|       unawaited( | ||||
|         Share.shareXFiles( | ||||
|           downloadedXFiles, | ||||
|           sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), | ||||
|         ), | ||||
|       ); | ||||
|       return true; | ||||
|     } catch (error) { | ||||
|  | ||||
| @ -705,7 +705,7 @@ class SyncService { | ||||
|     if (assets.isEmpty) return; | ||||
| 
 | ||||
|     if (Platform.isAndroid && _appSettingsService.getSetting<bool>(AppSettingsEnum.manageLocalMediaAndroid)) { | ||||
|       _toggleTrashStatusForAssets(assets); | ||||
|       await _toggleTrashStatusForAssets(assets); | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|  | ||||
| @ -214,7 +214,7 @@ class UploadService { | ||||
|   void _handleTaskStatusUpdate(TaskStatusUpdate update) async { | ||||
|     switch (update.status) { | ||||
|       case TaskStatus.complete: | ||||
|         _handleLivePhoto(update); | ||||
|         unawaited(_handleLivePhoto(update)); | ||||
| 
 | ||||
|         if (CurrentPlatform.isIOS) { | ||||
|           try { | ||||
| @ -259,7 +259,7 @@ class UploadService { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       enqueueTasks([uploadTask]); | ||||
|       await enqueueTasks([uploadTask]); | ||||
|     } catch (error, stackTrace) { | ||||
|       dPrint(() => "Error handling live photo upload task: $error $stackTrace"); | ||||
|     } | ||||
|  | ||||
| @ -1,8 +1,10 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:geolocator/geolocator.dart'; | ||||
| import 'package:immich_mobile/models/map/map_marker.model.dart'; | ||||
| import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; | ||||
| import 'package:geolocator/geolocator.dart'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:maplibre_gl/maplibre_gl.dart'; | ||||
| 
 | ||||
| @ -68,7 +70,7 @@ class MapUtils { | ||||
|     try { | ||||
|       bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); | ||||
|       if (!serviceEnabled && !silent) { | ||||
|         showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog()); | ||||
|         unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog())); | ||||
|         return (null, LocationPermission.deniedForever); | ||||
|       } | ||||
| 
 | ||||
|  | ||||
| @ -101,7 +101,7 @@ Future<void> handleEditDateTime(WidgetRef ref, BuildContext context, List<Asset> | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime); | ||||
|   await ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime); | ||||
| } | ||||
| 
 | ||||
| Future<void> handleEditLocation(WidgetRef ref, BuildContext context, List<Asset> selection) async { | ||||
| @ -120,7 +120,7 @@ Future<void> handleEditLocation(WidgetRef ref, BuildContext context, List<Asset> | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   ref.read(assetServiceProvider).changeLocation(selection.toList(), location); | ||||
|   await ref.read(assetServiceProvider).changeLocation(selection.toList(), location); | ||||
| } | ||||
| 
 | ||||
| Future<void> handleSetAssetsVisibility( | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @ -57,7 +59,7 @@ class AlbumViewerAppbar extends HookConsumerWidget implements PreferredSizeWidge | ||||
|     deleteAlbum() async { | ||||
|       final bool success = await ref.watch(albumProvider.notifier).deleteAlbum(album); | ||||
| 
 | ||||
|       context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); | ||||
|       unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); | ||||
| 
 | ||||
|       if (!success) { | ||||
|         ImmichToast.show( | ||||
| @ -105,7 +107,7 @@ class AlbumViewerAppbar extends HookConsumerWidget implements PreferredSizeWidge | ||||
|       bool isSuccess = await ref.watch(albumProvider.notifier).leaveAlbum(album); | ||||
| 
 | ||||
|       if (isSuccess) { | ||||
|         context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])); | ||||
|         unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); | ||||
|       } else { | ||||
|         context.pop(); | ||||
|         ImmichToast.show( | ||||
|  | ||||
| @ -314,10 +314,10 @@ class MultiselectGrid extends HookConsumerWidget { | ||||
|         final result = await ref.read(albumServiceProvider).createAlbumWithGeneratedName(assets); | ||||
| 
 | ||||
|         if (result != null) { | ||||
|           ref.watch(albumProvider.notifier).refreshRemoteAlbums(); | ||||
|           unawaited(ref.watch(albumProvider.notifier).refreshRemoteAlbums()); | ||||
|           selectionEnabledHook.value = false; | ||||
| 
 | ||||
|           context.pushRoute(AlbumViewerRoute(albumId: result.id)); | ||||
|           unawaited(context.pushRoute(AlbumViewerRoute(albumId: result.id))); | ||||
|         } | ||||
|       } finally { | ||||
|         processing.value = false; | ||||
| @ -346,7 +346,7 @@ class MultiselectGrid extends HookConsumerWidget { | ||||
|         ); | ||||
| 
 | ||||
|         if (remoteAssets.isNotEmpty) { | ||||
|           handleEditDateTime(ref, context, remoteAssets.toList()); | ||||
|           unawaited(handleEditDateTime(ref, context, remoteAssets.toList())); | ||||
|         } | ||||
|       } finally { | ||||
|         selectionEnabledHook.value = false; | ||||
| @ -361,7 +361,7 @@ class MultiselectGrid extends HookConsumerWidget { | ||||
|         ); | ||||
| 
 | ||||
|         if (remoteAssets.isNotEmpty) { | ||||
|           handleEditLocation(ref, context, remoteAssets.toList()); | ||||
|           unawaited(handleEditLocation(ref, context, remoteAssets.toList())); | ||||
|         } | ||||
|       } finally { | ||||
|         selectionEnabledHook.value = false; | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| @ -81,7 +82,7 @@ class BottomGalleryBar extends ConsumerWidget { | ||||
|           // to not throw the error when the next preCache index is called | ||||
|           if (totalAssets.value == 1 || assetIndex.value == totalAssets.value - 1) { | ||||
|             // Handle only one asset | ||||
|             context.maybePop(); | ||||
|             await context.maybePop(); | ||||
|           } | ||||
| 
 | ||||
|           totalAssets.value -= 1; | ||||
| @ -111,18 +112,20 @@ class BottomGalleryBar extends ConsumerWidget { | ||||
|       } | ||||
| 
 | ||||
|       // Asset is permanently removed | ||||
|       showDialog( | ||||
|         context: context, | ||||
|         builder: (BuildContext _) { | ||||
|           return DeleteDialog( | ||||
|             onDelete: () async { | ||||
|               final isDeleted = await onDelete(true); | ||||
|               if (isDeleted) { | ||||
|                 removeAssetFromStack(); | ||||
|               } | ||||
|             }, | ||||
|           ); | ||||
|         }, | ||||
|       unawaited( | ||||
|         showDialog( | ||||
|           context: context, | ||||
|           builder: (BuildContext _) { | ||||
|             return DeleteDialog( | ||||
|               onDelete: () async { | ||||
|                 final isDeleted = await onDelete(true); | ||||
|                 if (isDeleted) { | ||||
|                   removeAssetFromStack(); | ||||
|                 } | ||||
|               }, | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
| @ -150,7 +153,7 @@ class BottomGalleryBar extends ConsumerWidget { | ||||
|                     onTap: () async { | ||||
|                       await unStack(); | ||||
|                       ctx.pop(); | ||||
|                       context.maybePop(); | ||||
|                       await context.maybePop(); | ||||
|                     }, | ||||
|                     title: const Text("viewer_unstack", style: TextStyle(fontWeight: FontWeight.bold)).tr(), | ||||
|                   ), | ||||
| @ -178,9 +181,11 @@ class BottomGalleryBar extends ConsumerWidget { | ||||
|     void handleEdit() async { | ||||
|       final image = Image(image: ImmichImage.imageProvider(asset: asset)); | ||||
| 
 | ||||
|       context.navigator.push( | ||||
|         MaterialPageRoute( | ||||
|           builder: (context) => EditImagePage(asset: asset, image: image, isEdited: false), | ||||
|       unawaited( | ||||
|         context.navigator.push( | ||||
|           MaterialPageRoute( | ||||
|             builder: (context) => EditImagePage(asset: asset, image: image, isEdited: false), | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
| @ -1,3 +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,7 +95,7 @@ class CastDialog extends ConsumerWidget { | ||||
|                       } | ||||
| 
 | ||||
|                       if (!isCurrentDevice(deviceName)) { | ||||
|                         ref.read(castProvider.notifier).connect(type, deviceObj); | ||||
|                         unawaited(ref.read(castProvider.notifier).connect(type, deviceObj)); | ||||
|                       } | ||||
|                     }, | ||||
|                   ); | ||||
|  | ||||
| @ -1,11 +1,12 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:immich_mobile/domain/models/exif.model.dart'; | ||||
| import 'package:immich_mobile/utils/debug_print.dart'; | ||||
| import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; | ||||
| import 'package:maplibre_gl/maplibre_gl.dart'; | ||||
| import 'package:url_launcher/url_launcher.dart'; | ||||
| import 'package:immich_mobile/utils/debug_print.dart'; | ||||
| 
 | ||||
| class ExifMap extends StatelessWidget { | ||||
|   final ExifInfo exifInfo; | ||||
| @ -68,7 +69,7 @@ class ExifMap extends StatelessWidget { | ||||
|             } | ||||
| 
 | ||||
|             dPrint(() => 'Opening Map Uri: $uri'); | ||||
|             launchUrl(uri); | ||||
|             unawaited(launchUrl(uri)); | ||||
|           }, | ||||
|           onCreated: onMapCreated, | ||||
|         ); | ||||
|  | ||||
| @ -1,19 +1,21 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart' hide Store; | ||||
| import 'package:immich_mobile/entities/store.entity.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/entities/store.entity.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/models/backup/backup_state.model.dart'; | ||||
| import 'package:immich_mobile/providers/asset.provider.dart'; | ||||
| import 'package:immich_mobile/providers/auth.provider.dart'; | ||||
| import 'package:immich_mobile/providers/backup/backup.provider.dart'; | ||||
| import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; | ||||
| import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; | ||||
| import 'package:immich_mobile/providers/locale_provider.dart'; | ||||
| import 'package:immich_mobile/providers/user.provider.dart'; | ||||
| import 'package:immich_mobile/providers/websocket.provider.dart'; | ||||
| import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/utils/bytes_units.dart'; | ||||
| import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_profile_info.dart'; | ||||
| @ -97,25 +99,27 @@ class ImmichAppBarDialog extends HookConsumerWidget { | ||||
|             return; | ||||
|           } | ||||
| 
 | ||||
|           showDialog( | ||||
|             context: context, | ||||
|             builder: (BuildContext ctx) { | ||||
|               return ConfirmDialog( | ||||
|                 title: "app_bar_signout_dialog_title", | ||||
|                 content: "app_bar_signout_dialog_content", | ||||
|                 ok: "yes", | ||||
|                 onOk: () async { | ||||
|                   isLoggingOut.value = true; | ||||
|                   await ref.read(authProvider.notifier).logout().whenComplete(() => isLoggingOut.value = false); | ||||
|           unawaited( | ||||
|             showDialog( | ||||
|               context: context, | ||||
|               builder: (BuildContext ctx) { | ||||
|                 return ConfirmDialog( | ||||
|                   title: "app_bar_signout_dialog_title", | ||||
|                   content: "app_bar_signout_dialog_content", | ||||
|                   ok: "yes", | ||||
|                   onOk: () async { | ||||
|                     isLoggingOut.value = true; | ||||
|                     await ref.read(authProvider.notifier).logout().whenComplete(() => isLoggingOut.value = false); | ||||
| 
 | ||||
|                   ref.read(manualUploadProvider.notifier).cancelBackup(); | ||||
|                   ref.read(backupProvider.notifier).cancelBackup(); | ||||
|                   ref.read(assetProvider.notifier).clearAllAssets(); | ||||
|                   ref.read(websocketProvider.notifier).disconnect(); | ||||
|                   context.replaceRoute(const LoginRoute()); | ||||
|                 }, | ||||
|               ); | ||||
|             }, | ||||
|                     ref.read(manualUploadProvider.notifier).cancelBackup(); | ||||
|                     ref.read(backupProvider.notifier).cancelBackup(); | ||||
|                     unawaited(ref.read(assetProvider.notifier).clearAllAssets()); | ||||
|                     ref.read(websocketProvider.notifier).disconnect(); | ||||
|                     unawaited(context.replaceRoute(const LoginRoute())); | ||||
|                   }, | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
|           ); | ||||
|         }, | ||||
|         trailing: isLoggingOut.value | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @ -6,8 +8,8 @@ import 'package:immich_mobile/entities/store.entity.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/extensions/theme_extensions.dart'; | ||||
| import 'package:immich_mobile/providers/auth.provider.dart'; | ||||
| import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; | ||||
| import 'package:immich_mobile/providers/backup/backup.provider.dart'; | ||||
| import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; | ||||
| import 'package:immich_mobile/providers/upload_profile_image.provider.dart'; | ||||
| import 'package:immich_mobile/providers/user.provider.dart'; | ||||
| import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; | ||||
| @ -54,7 +56,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget { | ||||
|             ref.read(currentUserProvider.notifier).refresh(); | ||||
|           } | ||||
| 
 | ||||
|           ref.read(backupProvider.notifier).updateDiskInfo(); | ||||
|           unawaited(ref.read(backupProvider.notifier).updateDiskInfo()); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:io'; | ||||
| import 'dart:math'; | ||||
| @ -188,17 +189,17 @@ class LoginForm extends HookConsumerWidget { | ||||
|         final result = await ref.read(authProvider.notifier).login(emailController.text, passwordController.text); | ||||
| 
 | ||||
|         if (result.shouldChangePassword && !result.isAdmin) { | ||||
|           context.pushRoute(const ChangePasswordRoute()); | ||||
|           unawaited(context.pushRoute(const ChangePasswordRoute())); | ||||
|         } else { | ||||
|           final isBeta = Store.isBetaTimelineEnabled; | ||||
|           if (isBeta) { | ||||
|             await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); | ||||
|             handleSyncFlow(); | ||||
|             unawaited(handleSyncFlow()); | ||||
|             ref.read(websocketProvider.notifier).connect(); | ||||
|             context.replaceRoute(const TabShellRoute()); | ||||
|             unawaited(context.replaceRoute(const TabShellRoute())); | ||||
|             return; | ||||
|           } | ||||
|           context.replaceRoute(const TabControllerRoute()); | ||||
|           unawaited(context.replaceRoute(const TabControllerRoute())); | ||||
|         } | ||||
|       } catch (error) { | ||||
|         ImmichToast.show( | ||||
| @ -288,15 +289,15 @@ class LoginForm extends HookConsumerWidget { | ||||
|             final permission = ref.watch(galleryPermissionNotifier); | ||||
|             final isBeta = Store.isBetaTimelineEnabled; | ||||
|             if (!isBeta && (permission.isGranted || permission.isLimited)) { | ||||
|               ref.watch(backupProvider.notifier).resumeBackup(); | ||||
|               unawaited(ref.watch(backupProvider.notifier).resumeBackup()); | ||||
|             } | ||||
|             if (isBeta) { | ||||
|               await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); | ||||
|               handleSyncFlow(); | ||||
|               context.replaceRoute(const TabShellRoute()); | ||||
|               unawaited(handleSyncFlow()); | ||||
|               unawaited(context.replaceRoute(const TabShellRoute())); | ||||
|               return; | ||||
|             } | ||||
|             context.replaceRoute(const TabControllerRoute()); | ||||
|             unawaited(context.replaceRoute(const TabControllerRoute())); | ||||
|           } | ||||
|         } catch (error, stack) { | ||||
|           log.severe('Error logging in with OAuth: $error', stack); | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/models/map/map_event.model.dart'; | ||||
| import 'package:immich_mobile/widgets/map/map_asset_grid.dart'; | ||||
| import 'package:immich_mobile/entities/asset.entity.dart'; | ||||
| import 'package:immich_mobile/utils/draggable_scroll_controller.dart'; | ||||
| import 'package:immich_mobile/widgets/map/map_asset_grid.dart'; | ||||
| 
 | ||||
| class MapBottomSheet extends HookConsumerWidget { | ||||
|   final Stream<MapEvent> mapEventStream; | ||||
| @ -34,7 +34,11 @@ class MapBottomSheet extends HookConsumerWidget { | ||||
| 
 | ||||
|     void handleMapEvents(MapEvent event) async { | ||||
|       if (event is MapCloseBottomSheet) { | ||||
|         sheetController.animateTo(0.1, duration: const Duration(milliseconds: 200), curve: Curves.linearToEaseOut); | ||||
|         await sheetController.animateTo( | ||||
|           0.1, | ||||
|           duration: const Duration(milliseconds: 200), | ||||
|           curve: Curves.linearToEaseOut, | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @ -42,7 +44,7 @@ class BetaTimelineListTile extends ConsumerWidget { | ||||
|               ElevatedButton( | ||||
|                 onPressed: () async { | ||||
|                   Navigator.of(context).pop(); | ||||
|                   context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)]); | ||||
|                   unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)])); | ||||
|                 }, | ||||
|                 child: Text("ok".t(context: context)), | ||||
|               ), | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| @ -102,13 +104,13 @@ class LocalNetworkPreference extends HookConsumerWidget { | ||||
|           ), | ||||
|         ); | ||||
|       } else { | ||||
|         saveWifiName(wifiName); | ||||
|         unawaited(saveWifiName(wifiName)); | ||||
|       } | ||||
| 
 | ||||
|       final serverEndpoint = ref.read(authProvider.notifier).getServerEndpoint(); | ||||
| 
 | ||||
|       if (serverEndpoint != null) { | ||||
|         saveLocalEndpoint(serverEndpoint); | ||||
|         unawaited(saveLocalEndpoint(serverEndpoint)); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -53,7 +53,7 @@ void main() { | ||||
|   }); | ||||
| 
 | ||||
|   tearDown(() async { | ||||
|     sut.dispose(); | ||||
|     unawaited(sut.dispose()); | ||||
|     await controller.close(); | ||||
|   }); | ||||
| 
 | ||||
| @ -129,7 +129,7 @@ void main() { | ||||
|       final stream = sut.watch(StoreKey.accessToken); | ||||
|       final events = <String?>[_kAccessToken, _kAccessToken.toUpperCase(), null, _kAccessToken.toLowerCase()]; | ||||
| 
 | ||||
|       expectLater(stream, emitsInOrder(events)); | ||||
|       unawaited(expectLater(stream, emitsInOrder(events))); | ||||
| 
 | ||||
|       for (final event in events) { | ||||
|         valueController.add(event); | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:immich_mobile/domain/models/store.model.dart'; | ||||
| import 'package:immich_mobile/domain/models/user.model.dart'; | ||||
| @ -99,7 +101,7 @@ void main() { | ||||
|       final count = await db.storeValues.count(); | ||||
|       expect(count, isNot(isZero)); | ||||
|       await sut.deleteAll(); | ||||
|       expectLater(await db.storeValues.count(), isZero); | ||||
|       unawaited(expectLater(await db.storeValues.count(), isZero)); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
| @ -124,29 +126,31 @@ void main() { | ||||
| 
 | ||||
|     test('watch()', () async { | ||||
|       final stream = sut.watch(StoreKey.version); | ||||
|       expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10])); | ||||
|       unawaited(expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10]))); | ||||
|       await pumpEventQueue(); | ||||
|       await sut.upsert(StoreKey.version, _kTestVersion + 10); | ||||
|     }); | ||||
| 
 | ||||
|     test('watchAll()', () async { | ||||
|       final stream = sut.watchAll(); | ||||
|       expectLater( | ||||
|         stream, | ||||
|         emitsInOrder([ | ||||
|           [ | ||||
|             const StoreDto<Object>(StoreKey.version, _kTestVersion), | ||||
|             StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed), | ||||
|             const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken), | ||||
|             const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface), | ||||
|           ], | ||||
|           [ | ||||
|             const StoreDto<Object>(StoreKey.version, _kTestVersion + 10), | ||||
|             StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed), | ||||
|             const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken), | ||||
|             const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface), | ||||
|           ], | ||||
|         ]), | ||||
|       unawaited( | ||||
|         expectLater( | ||||
|           stream, | ||||
|           emitsInOrder([ | ||||
|             [ | ||||
|               const StoreDto<Object>(StoreKey.version, _kTestVersion), | ||||
|               StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed), | ||||
|               const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken), | ||||
|               const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface), | ||||
|             ], | ||||
|             [ | ||||
|               const StoreDto<Object>(StoreKey.version, _kTestVersion + 10), | ||||
|               StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed), | ||||
|               const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken), | ||||
|               const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface), | ||||
|             ], | ||||
|           ]), | ||||
|         ), | ||||
|       ); | ||||
|       await sut.upsert(StoreKey.version, _kTestVersion + 10); | ||||
|     }); | ||||
|  | ||||
| @ -64,9 +64,9 @@ void main() { | ||||
|     TestUtils.init(); | ||||
|     db = await TestUtils.initIsar(); | ||||
|     await StoreService.init(storeRepository: IsarStoreRepository(db)); | ||||
|     Store.put(StoreKey.currentUser, UserStub.admin); | ||||
|     Store.put(StoreKey.serverEndpoint, ''); | ||||
|     Store.put(StoreKey.accessToken, ''); | ||||
|     await Store.put(StoreKey.currentUser, UserStub.admin); | ||||
|     await Store.put(StoreKey.serverEndpoint, ''); | ||||
|     await Store.put(StoreKey.accessToken, ''); | ||||
|   }); | ||||
| 
 | ||||
|   setUp(() async { | ||||
|  | ||||
| @ -35,8 +35,8 @@ void main() { | ||||
|     TestUtils.init(); | ||||
|     db = await TestUtils.initIsar(); | ||||
|     await StoreService.init(storeRepository: IsarStoreRepository(db)); | ||||
|     Store.put(StoreKey.currentUser, UserStub.admin); | ||||
|     Store.put(StoreKey.serverEndpoint, ''); | ||||
|     await Store.put(StoreKey.currentUser, UserStub.admin); | ||||
|     await Store.put(StoreKey.serverEndpoint, ''); | ||||
|   }); | ||||
| 
 | ||||
|   setUp(() { | ||||
|  | ||||
| @ -31,9 +31,9 @@ void main() { | ||||
|     db = await TestUtils.initIsar(); | ||||
|     // For UserCircleAvatar | ||||
|     await StoreService.init(storeRepository: IsarStoreRepository(db)); | ||||
|     Store.put(StoreKey.currentUser, UserStub.admin); | ||||
|     Store.put(StoreKey.serverEndpoint, ''); | ||||
|     Store.put(StoreKey.accessToken, ''); | ||||
|     await Store.put(StoreKey.currentUser, UserStub.admin); | ||||
|     await Store.put(StoreKey.serverEndpoint, ''); | ||||
|     await Store.put(StoreKey.accessToken, ''); | ||||
|   }); | ||||
| 
 | ||||
|   setUp(() { | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
| import 'package:immich_mobile/utils/async_mutex.dart'; | ||||
| 
 | ||||
| @ -7,11 +9,11 @@ void main() { | ||||
|       AsyncMutex lock = AsyncMutex(); | ||||
|       List<int> events = []; | ||||
|       expect(0, lock.enqueued); | ||||
|       lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(1))); | ||||
|       unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(1)))); | ||||
|       expect(1, lock.enqueued); | ||||
|       lock.run(() => Future.delayed(const Duration(milliseconds: 3), () => events.add(2))); | ||||
|       unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 3), () => events.add(2)))); | ||||
|       expect(2, lock.enqueued); | ||||
|       lock.run(() => Future.delayed(const Duration(milliseconds: 1), () => events.add(3))); | ||||
|       unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 1), () => events.add(3)))); | ||||
|       expect(3, lock.enqueued); | ||||
|       await lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(4))); | ||||
|       expect(0, lock.enqueued); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user