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