Compare commits

..

9 Commits

Author SHA1 Message Date
Alex 37967da193 docs: modify warning to info for database dump section 2025-09-20 10:24:53 -05:00
Alex aaeac2ab73 fix(web): revert do not upscale small pictures (#22191) (#22233) 2025-09-20 09:25:27 -05:00
Jason Rasmussen de57fecb69 fix(web): copy to clipboard on safari (#22217) 2025-09-19 17:44:18 -04:00
renovate[bot] 1e0b4fac04 fix(deps): update typescript-projects (#21510)
* fix(deps): update typescript-projects

* chore: downgrade dependencies

* chore: downgrade svelte-gestures

* fix: svelte/no-navigation-without-resolve

* fix: dumb test

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Zack Pollard <zack@futo.org>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-09-19 12:29:01 -04:00
Jason Rasmussen 34339ea69f fix(web): show danger/warning when taken dates overlap (#22213) 2025-09-19 12:20:09 -04:00
Jason Rasmussen 6da039780e fix: automatically remove leading/trailing whitespace from search que… (#22214)
fix: automatically remove leading/trailing whitespace from search queries
2025-09-19 12:19:26 -04:00
Jason Rasmussen 3f2e0780d5 feat: availability checks (#22185) 2025-09-19 12:18:42 -04:00
Mert 52363cf0fb chore(mobile): ignore ios build folder (#22212)
ignore ios build folder
2025-09-19 09:50:24 -05:00
Jason Rasmussen 86df09a0e4 fix(mobile): smaller search page size (#22210) 2025-09-19 10:11:11 -04:00
137 changed files with 2221 additions and 2027 deletions
+1
View File
@@ -18,6 +18,7 @@ mobile/libisar.dylib
mobile/openapi/test mobile/openapi/test
mobile/openapi/doc mobile/openapi/doc
mobile/openapi/.openapi-generator/FILES mobile/openapi/.openapi-generator/FILES
mobile/ios/build
open-api/typescript-sdk/build open-api/typescript-sdk/build
mobile/android/fastlane/report.xml mobile/android/fastlane/report.xml
@@ -25,9 +25,9 @@ It is not recommended to directly backup the `DB_DATA_LOCATION` folder. Doing so
### Automatic Database Dumps ### Automatic Database Dumps
:::warning :::info
The automatic database dumps can be used to restore the database in the event of damage to the Postgres database files. The automatic database dumps can be used to restore the database in the event of damage to the Postgres database files.
There is no monitoring for these dumps and you will not be notified if they are unsuccessful. If the server fails to generate the database dump file, a notification will be shown in the in-app notification on the web
::: :::
:::caution :::caution
@@ -169,8 +169,6 @@ Redis (Sentinel) URL example JSON before encoding:
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | | `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning | | `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | | `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
| `MACHINE_LEARNING_PING_TIMEOUT` | How long (ms) to wait for a PING response when checking if an ML server is available | `2000` | server |
| `MACHINE_LEARNING_AVAILABILITY_BACKOFF_TIME` | How long to ignore ML servers that are offline before trying again | `30000` | server |
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning | | `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning | | `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning |
+9
View File
@@ -123,6 +123,13 @@
"logging_enable_description": "Enable logging", "logging_enable_description": "Enable logging",
"logging_level_description": "When enabled, what log level to use.", "logging_level_description": "When enabled, what log level to use.",
"logging_settings": "Logging", "logging_settings": "Logging",
"machine_learning_availability_checks": "Availability checks",
"machine_learning_availability_checks_description": "Automatically detect and prefer available machine learning servers",
"machine_learning_availability_checks_enabled": "Enable availability checks",
"machine_learning_availability_checks_interval": "Check interval",
"machine_learning_availability_checks_interval_description": "Interval in milliseconds between availability checks",
"machine_learning_availability_checks_timeout": "Request timeout",
"machine_learning_availability_checks_timeout_description": "Timeout in milliseconds for availability checks",
"machine_learning_clip_model": "CLIP model", "machine_learning_clip_model": "CLIP model",
"machine_learning_clip_model_description": "The name of a CLIP model listed <link>here</link>. Note that you must re-run the 'Smart Search' job for all images upon changing a model.", "machine_learning_clip_model_description": "The name of a CLIP model listed <link>here</link>. Note that you must re-run the 'Smart Search' job for all images upon changing a model.",
"machine_learning_duplicate_detection": "Duplicate Detection", "machine_learning_duplicate_detection": "Duplicate Detection",
@@ -913,6 +920,7 @@
"cant_get_number_of_comments": "Can't get number of comments", "cant_get_number_of_comments": "Can't get number of comments",
"cant_search_people": "Can't search people", "cant_search_people": "Can't search people",
"cant_search_places": "Can't search places", "cant_search_places": "Can't search places",
"clipboard_unsupported_mime_type": "The system clipboard does not support copying this type of content: {mimeType}",
"error_adding_assets_to_album": "Error adding assets to album", "error_adding_assets_to_album": "Error adding assets to album",
"error_adding_users_to_album": "Error adding users to album", "error_adding_users_to_album": "Error adding users to album",
"error_deleting_shared_user": "Error deleting shared user", "error_deleting_shared_user": "Error deleting shared user",
@@ -1916,6 +1924,7 @@
"stacktrace": "Stacktrace", "stacktrace": "Stacktrace",
"start": "Start", "start": "Start",
"start_date": "Start date", "start_date": "Start date",
"start_date_before_end_date": "Start date must be before end date",
"state": "State", "state": "State",
"status": "Status", "status": "Status",
"stop_casting": "Stop casting", "stop_casting": "Stop casting",
+1 -1
View File
@@ -1,7 +1,7 @@
[tools] [tools]
node = "22.19.0" node = "22.19.0"
flutter = "3.35.4" flutter = "3.35.4"
pnpm = "10.14.0" pnpm = "10.15.1"
dart = "3.8.2" dart = "3.8.2"
[tools."github:CQLabs/homebrew-dcm"] [tools."github:CQLabs/homebrew-dcm"]
+164 -5
View File
@@ -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.dev/tools/linter-rules # https://dart-lang.github.io/linter/lints/index.html.
# #
# 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,7 +28,6 @@ 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
@@ -48,9 +47,6 @@ analyzer:
# plugins: # plugins:
# - custom_lint # - custom_lint
errors:
unawaited_futures: warning
custom_lint: custom_lint:
debug: true debug: true
rules: rules:
@@ -146,7 +142,170 @@ dart_code_metrics:
exclude-paths: exclude-paths:
- 'lib/utils/debug_print.dart' - 'lib/utils/debug_print.dart'
severity: perf severity: perf
# All rules from "recommended" preset
# Show potential errors
# - avoid-cascade-after-if-null
# - avoid-collection-methods-with-unrelated-types
# - avoid-duplicate-exports
# - avoid-dynamic
# - avoid-missing-enum-constant-in-map
# - avoid-passing-async-when-sync-expected
# - 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
@@ -117,10 +117,10 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
} }
// 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
unawaited(_backgroundHostApi.onInitialized()); _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);
unawaited(_backgroundHostApi.close()); _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.");
await _localAlbumRepository.upsert(updatedDeviceAlbum); _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;
unawaited(cacheManager?.putStreamedFile(url, streamController.stream)); 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);
unawaited(streamController.close()); streamController.close();
return await ImmutableBuffer.fromUint8List(bytes); return await ImmutableBuffer.fromUint8List(bytes);
} catch (e) { } catch (e) {
streamController.addError(e); streamController.addError(e);
unawaited(streamController.close()); 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);
unawaited(_evictFile(url)); _evictFile(url);
return null; return null;
} }
} }
@@ -361,13 +361,15 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
return _db.managers.localAlbumEntity.count(); return _db.managers.localAlbumEntity.count();
} }
Future<void> unlinkRemoteAlbum(String id) async { Future unlinkRemoteAlbum(String id) async {
final query = _db.localAlbumEntity.update()..where((row) => row.id.equals(id)); return _db.localAlbumEntity.update()
await query.write(const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null))); ..where((row) => row.id.equals(id))
..write(const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null)));
} }
Future<void> linkRemoteAlbum(String localAlbumId, String remoteAlbumId) async { Future linkRemoteAlbum(String localAlbumId, String remoteAlbumId) async {
final query = _db.localAlbumEntity.update()..where((row) => row.id.equals(localAlbumId)); return _db.localAlbumEntity.update()
await query.write(LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(remoteAlbumId))); ..where((row) => row.id.equals(localAlbumId))
..write(LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(remoteAlbumId)));
} }
} }
@@ -33,7 +33,7 @@ class SearchApiRepository extends ApiRepository {
personIds: filter.people.map((e) => e.id).toList(), personIds: filter.people.map((e) => e.id).toList(),
type: type, type: type,
page: page, page: page,
size: 1000, size: 100,
), ),
); );
} }
+1 -1
View File
@@ -153,7 +153,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
unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)); 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,5 +1,3 @@
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';
@@ -53,7 +51,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) {
unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
} else { } else {
showErrorMessage(); showErrorMessage();
} }
@@ -1,5 +1,3 @@
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';
@@ -31,8 +29,8 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
if (newAlbum != null) { if (newAlbum != null) {
ref.watch(albumTitleProvider.notifier).clearAlbumTitle(); ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
unawaited(context.maybePop(true)); context.maybePop(true);
unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
} }
ScaffoldMessenger( ScaffoldMessenger(
@@ -111,8 +109,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: () { onPressed: () async {
unawaited(context.maybePop()); 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
await ref.read(albumProvider.notifier).refreshDeviceAlbums(); 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(),
), ),
@@ -211,7 +211,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
if (currentUser == null) { if (currentUser == null) {
return; return;
} }
unawaited(ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id)); 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(),
), ),
@@ -1,5 +1,3 @@
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';
@@ -56,11 +54,9 @@ class DriftBackupOptionsPage extends ConsumerWidget {
); );
final backupNotifier = ref.read(driftBackupProvider.notifier); final backupNotifier = ref.read(driftBackupProvider.notifier);
unawaited( backupNotifier.cancel().then((_) {
backupNotifier.cancel().then((_) { backupNotifier.startBackup(currentUser.id);
backupNotifier.startBackup(currentUser.id); });
}),
);
} }
}, },
child: Scaffold( child: Scaffold(
@@ -1,12 +1,12 @@
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';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:immich_mobile/utils/bytes_units.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@@ -163,8 +163,8 @@ class DriftUploadDetailPage extends ConsumerWidget {
); );
} }
Future<void> _showFileDetailDialog(BuildContext context, DriftUploadStatus item) { Future<void> _showFileDetailDialog(BuildContext context, DriftUploadStatus item) async {
return showDialog( showDialog(
context: context, context: context,
builder: (context) => FileDetailDialog(uploadStatus: item), builder: (context) => FileDetailDialog(uploadStatus: item),
); );
+1 -1
View File
@@ -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
await listViewScrollController.animateTo( 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,5 +1,3 @@
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';
@@ -172,11 +170,11 @@ class CreateAlbumPage extends HookConsumerWidget {
.createAlbum(ref.read(albumTitleProvider), selectedAssets.value); .createAlbum(ref.read(albumTitleProvider), selectedAssets.value);
if (newAlbum != null) { if (newAlbum != null) {
await ref.read(albumProvider.notifier).refreshRemoteAlbums(); 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();
unawaited(context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id))); 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');
await context.maybePop(); context.maybePop();
} }
} }
@@ -279,13 +279,11 @@ class NativeVideoViewerPage extends HookConsumerWidget {
nc.onPlaybackReady.addListener(onPlaybackReady); nc.onPlaybackReady.addListener(onPlaybackReady);
nc.onPlaybackEnded.addListener(onPlaybackEnded); nc.onPlaybackEnded.addListener(onPlaybackEnded);
unawaited( nc.loadVideoSource(source).catchError((error) {
nc.loadVideoSource(source).catchError((error) { log.severe('Error loading video source: $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);
unawaited(nc.setLoop(loopVideo)); nc.setLoop(loopVideo);
controller.value = nc; controller.value = nc;
Timer(const Duration(milliseconds: 200), checkIfBuffering); Timer(const Duration(milliseconds: 200), checkIfBuffering);
@@ -356,12 +354,12 @@ class NativeVideoViewerPage extends HookConsumerWidget {
useOnAppLifecycleStateChange((_, state) async { useOnAppLifecycleStateChange((_, state) async {
if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) { if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) {
await controller.value?.play(); 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;
await controller.value?.pause(); controller.value?.pause();
} else { } else {
shouldPlayOnForeground.value = false; shouldPlayOnForeground.value = false;
} }
+30 -34
View File
@@ -1,5 +1,3 @@
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';
@@ -55,41 +53,39 @@ 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);
unawaited( ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( (_) async {
(_) async { try {
try { wsProvider.connect();
wsProvider.connect(); infoProvider.getServerInfo();
unawaited(infoProvider.getServerInfo());
if (Store.isBetaTimelineEnabled) { if (Store.isBetaTimelineEnabled) {
await Future.wait([backgroundManager.syncLocal(), backgroundManager.syncRemote()]); await Future.wait([backgroundManager.syncLocal(), backgroundManager.syncRemote()]);
await Future.wait([ await Future.wait([
backgroundManager.hashAssets().then((_) { backgroundManager.hashAssets().then((_) {
_resumeBackup(backupProvider); _resumeBackup(backupProvider);
}), }),
_resumeBackup(backupProvider), _resumeBackup(backupProvider),
]); ]);
if (Store.get(StoreKey.syncAlbums, false)) { if (Store.get(StoreKey.syncAlbums, false)) {
await backgroundManager.syncLinkedAlbum(); await backgroundManager.syncLinkedAlbum();
}
} }
} catch (e) {
log.severe('Failed establishing connection to the server: $e');
} }
}, } catch (e) {
onError: (exception) => { log.severe('Failed establishing connection to the server: $e');
log.severe('Failed to update auth info with access token: $accessToken'), }
ref.read(authProvider.notifier).logout(), },
context.replaceRoute(const LoginRoute()), onError: (exception) => {
}, log.severe('Failed to update auth info with access token: $accessToken'),
), ref.read(authProvider.notifier).logout(),
context.replaceRoute(const LoginRoute()),
},
); );
} else { } else {
log.severe('Missing crucial offline login info - Logging out completely'); log.severe('Missing crucial offline login info - Logging out completely');
unawaited(ref.read(authProvider.notifier).logout()); ref.read(authProvider.notifier).logout();
unawaited(context.replaceRoute(const LoginRoute())); context.replaceRoute(const LoginRoute());
return; return;
} }
@@ -99,11 +95,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);
unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)])); context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)]);
return; return;
} }
unawaited(context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute())); context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute());
} }
if (Store.isBetaTimelineEnabled) { if (Store.isBetaTimelineEnabled) {
@@ -113,7 +109,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
unawaited(ref.watch(backupProvider.notifier).resumeBackup()); ref.watch(backupProvider.notifier).resumeBackup();
} }
} }
@@ -123,7 +119,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) {
unawaited(notifier.handleBackupResume(currentUser.id)); notifier.handleBackupResume(currentUser.id);
} }
} }
} }
+5 -8
View File
@@ -1,16 +1,13 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:crop_image/crop_image.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:crop_image/crop_image.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
@@ -38,7 +35,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();
unawaited(context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true))); context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true));
}, },
), ),
], ],
+5 -6
View File
@@ -1,13 +1,12 @@
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/constants/filters.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/entities/asset.entity.dart';
import 'package:immich_mobile/constants/filters.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:auto_route/auto_route.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
/// A widget for filtering an image. /// A widget for filtering an image.
@@ -75,7 +74,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);
unawaited(context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true))); context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true));
}, },
), ),
], ],
@@ -1,16 +1,14 @@
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 {
@@ -37,9 +35,9 @@ class PinAuthPage extends HookConsumerWidget {
); );
if (isBetaTimeline) { if (isBetaTimeline) {
unawaited(context.replaceRoute(const DriftLockedFolderRoute())); context.replaceRoute(const DriftLockedFolderRoute());
} else { } else {
unawaited(context.replaceRoute(const LockedRoute())); context.replaceRoute(const LockedRoute());
} }
} }
} }
@@ -333,7 +333,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
changeExpiry: changeExpiry, changeExpiry: changeExpiry,
); );
ref.invalidate(sharedLinksStateProvider); ref.invalidate(sharedLinksStateProvider);
await context.maybePop(); context.maybePop();
} }
return Scaffold( return Scaffold(
+4 -6
View File
@@ -82,12 +82,10 @@ class PhotosPage extends HookConsumerWidget {
final fullRefresh = refreshCount.value > 0; final fullRefresh = refreshCount.value > 0;
if (fullRefresh) { if (fullRefresh) {
unawaited( Future.wait([
Future.wait([ ref.read(assetProvider.notifier).getAllAsset(clear: true),
ref.read(assetProvider.notifier).getAllAsset(clear: true), ref.read(albumProvider.notifier).refreshRemoteAlbums(),
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;
+4 -5
View File
@@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
@@ -84,7 +83,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);
await reloadLayers(); reloadLayers();
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
@@ -129,7 +128,7 @@ class MapPage extends HookConsumerWidget {
); );
if (marker != null) { if (marker != null) {
await updateAssetMarkerPosition(marker); 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) {
@@ -166,7 +165,7 @@ class MapPage extends HookConsumerWidget {
if (asset.isVideo) { if (asset.isVideo) {
ref.read(showControlsProvider.notifier).show = false; ref.read(showControlsProvider.notifier).show = false;
} }
unawaited(context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList))); context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList));
} }
/// BOTTOM SHEET CALLBACKS /// BOTTOM SHEET CALLBACKS
@@ -210,7 +209,7 @@ class MapPage extends HookConsumerWidget {
} }
if (mapController.value != null && location != null) { if (mapController.value != null && location != null) {
await mapController.value!.animateCamera( 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;
await controller.value?.animateCamera(CameraUpdate.newLatLng(centre)); 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;
await controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng)); controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng));
} }
return MapThemeOverride( return MapThemeOverride(
+2 -2
View File
@@ -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;
unawaited(search()); search();
return; return;
} }
@@ -295,7 +295,7 @@ class SearchPage extends HookConsumerWidget {
); );
} }
unawaited(search()); search();
} }
// MEDIA PICKER // MEDIA PICKER
@@ -1,5 +1,3 @@
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';
@@ -49,7 +47,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);
unawaited(context.navigateTo(const DriftAlbumsRoute())); context.navigateTo(const DriftAlbumsRoute());
} catch (_) { } catch (_) {
showErrorMessage(); showErrorMessage();
} }
@@ -1,5 +1,3 @@
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';
@@ -181,7 +179,7 @@ class _DriftCreateAlbumPageState extends ConsumerState<DriftCreateAlbumPage> {
if (album != null) { if (album != null) {
ref.read(currentRemoteAlbumProvider.notifier).setAlbum(album); ref.read(currentRemoteAlbumProvider.notifier).setAlbum(album);
unawaited(context.replaceRoute(RemoteAlbumRoute(album: album))); context.replaceRoute(RemoteAlbumRoute(album: album));
} }
} }
@@ -1,5 +1,3 @@
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';
@@ -141,7 +139,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
toastType: ToastType.success, toastType: ToastType.success,
); );
unawaited(context.pushRoute(const DriftAlbumsRoute())); context.pushRoute(const DriftAlbumsRoute());
} catch (e) { } catch (e) {
ImmichToast.show( ImmichToast.show(
context: context, context: context,
@@ -163,12 +161,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 ?? '');
}); });
unawaited(HapticFeedback.mediumImpact()); HapticFeedback.mediumImpact();
} }
} }
Future<void> showActivity(BuildContext context) async { Future<void> showActivity(BuildContext context) async {
unawaited(context.pushRoute(const DriftActivitiesRoute())); context.pushRoute(const DriftActivitiesRoute());
} }
void showOptionSheet(BuildContext context) { void showOptionSheet(BuildContext context) {
@@ -209,7 +207,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
}, },
onCreateSharedLink: () async { onCreateSharedLink: () async {
context.pop(); context.pop();
unawaited(context.pushRoute(SharedLinkEditRoute(albumId: _album.id))); context.pushRoute(SharedLinkEditRoute(albumId: _album.id));
}, },
onShowOptions: () { onShowOptions: () {
context.pop(); context.pop();
@@ -1,5 +1,3 @@
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';
@@ -36,7 +34,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();
unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true))); context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true));
}, },
), ),
], ],
@@ -65,7 +65,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);
} }
unawaited(ref.read(backgroundSyncProvider).syncLocal(full: true)); ref.read(backgroundSyncProvider).syncLocal(full: true);
context.navigator.popUntil((route) => route.isFirst); context.navigator.popUntil((route) => route.isFirst);
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);
unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: filteredImage, isEdited: true))); context.pushRoute(DriftEditImageRoute(asset: asset, image: filteredImage, isEdited: true));
}, },
), ),
], ],
@@ -270,7 +270,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;
unawaited(search()); search();
return; return;
} }
@@ -300,7 +300,7 @@ class DriftSearchPage extends HookConsumerWidget {
); );
} }
unawaited(search()); search();
} }
// MEDIA PICKER // MEDIA PICKER
@@ -1,5 +1,3 @@
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';
@@ -17,7 +15,7 @@ class AdvancedInfoActionButton extends ConsumerWidget {
return; return;
} }
unawaited(ref.read(actionProvider.notifier).troubleshoot(source, context)); ref.read(actionProvider.notifier).troubleshoot(source, context);
} }
@override @override
@@ -39,7 +39,7 @@ class ShareActionButton extends ConsumerWidget {
return; return;
} }
await showDialog( showDialog(
context: context, context: context,
builder: (BuildContext buildContext) { builder: (BuildContext buildContext) {
ref.read(actionProvider.notifier).shareAssets(source).then((ActionResult result) { ref.read(actionProvider.notifier).shareAssets(source).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
unawaited(filterAlbums()); 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();
unawaited(context.pushRoute(RemoteAlbumRoute(album: newAlbum))); context.pushRoute(RemoteAlbumRoute(album: newAlbum));
} }
return SliverPadding( return SliverPadding(
@@ -634,9 +634,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) {
unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
} else { } else {
unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky)); SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
} }
}); });
@@ -298,13 +298,11 @@ class NativeVideoViewer extends HookConsumerWidget {
nc.onPlaybackReady.addListener(onPlaybackReady); nc.onPlaybackReady.addListener(onPlaybackReady);
nc.onPlaybackEnded.addListener(onPlaybackEnded); nc.onPlaybackEnded.addListener(onPlaybackEnded);
unawaited( nc.loadVideoSource(source).catchError((error) {
nc.loadVideoSource(source).catchError((error) { log.severe('Error loading video source: $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);
unawaited(nc.setLoop(!asset.isMotionPhoto && loopVideo)); nc.setLoop(!asset.isMotionPhoto && loopVideo);
controller.value = nc; controller.value = nc;
Timer(const Duration(milliseconds: 200), checkIfBuffering); Timer(const Duration(milliseconds: 200), checkIfBuffering);
@@ -375,12 +373,12 @@ class NativeVideoViewer extends HookConsumerWidget {
useOnAppLifecycleStateChange((_, state) async { useOnAppLifecycleStateChange((_, state) async {
if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) { if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) {
await controller.value?.play(); 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;
await controller.value?.pause(); controller.value?.pause();
} else { } else {
shouldPlayOnForeground.value = false; shouldPlayOnForeground.value = false;
} }
@@ -1,5 +1,3 @@
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';
@@ -53,14 +51,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;
unawaited(evict()); 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) {
unawaited(evict()); evict();
return; return;
} }
yield image; yield image;
@@ -85,7 +85,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
yield* initialImageStream(); yield* initialImageStream();
if (isCancelled) { if (isCancelled) {
unawaited(evict()); evict();
return; return;
} }
@@ -87,7 +87,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
yield* initialImageStream(); yield* initialImageStream();
if (isCancelled) { if (isCancelled) {
unawaited(evict()); evict();
return; return;
} }
@@ -100,7 +100,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
yield* loadRequest(request, decode); yield* loadRequest(request, decode);
if (isCancelled) { if (isCancelled) {
unawaited(evict()); evict();
return; return;
} }
@@ -9,8 +9,8 @@ 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/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart'; import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
import 'package:immich_mobile/utils/async_mutex.dart'; import 'package:immich_mobile/utils/async_mutex.dart';
import 'package:immich_mobile/utils/debounce.dart'; import 'package:immich_mobile/utils/debounce.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
@@ -114,14 +114,12 @@ class _DriftMapState extends ConsumerState<DriftMap> {
} }
final bounds = await controller.getVisibleRegion(); final bounds = await controller.getVisibleRegion();
unawaited( _reloadMutex.run(() async {
_reloadMutex.run(() async { if (mounted && ref.read(mapStateProvider.notifier).setBounds(bounds)) {
if (mounted && ref.read(mapStateProvider.notifier).setBounds(bounds)) { final markers = await ref.read(mapMarkerProvider(bounds).future);
final markers = await ref.read(mapMarkerProvider(bounds).future); await reloadMarkers(markers);
await reloadMarkers(markers); }
} });
}),
);
} }
Future<void> reloadMarkers(Map<String, dynamic> markers) async { Future<void> reloadMarkers(Map<String, dynamic> markers) async {
@@ -149,7 +147,7 @@ class _DriftMapState extends ConsumerState<DriftMap> {
final controller = mapController; final controller = mapController;
if (controller != null && location != null) { if (controller != null && location != null) {
await controller.animateCamera( 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) {
unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context))); showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context));
return (null, LocationPermission.deniedForever); return (null, LocationPermission.deniedForever);
} }
@@ -1,4 +1,3 @@
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';
@@ -16,8 +15,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/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.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/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';
@@ -157,13 +156,11 @@ 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);
unawaited( ctx.pushRoute(
ctx.pushRoute( AssetViewerRoute(
AssetViewerRoute( initialIndex: assetIndex,
initialIndex: assetIndex, timelineService: ref.read(timelineServiceProvider),
timelineService: ref.read(timelineServiceProvider), heroOffset: heroOffset,
heroOffset: heroOffset,
),
), ),
); );
} }
@@ -233,7 +233,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
} }
try { try {
await LogService.I.flush(); LogService.I.flush();
} catch (_) {} } catch (_) {}
} }
@@ -242,7 +242,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
// Flush logs before closing database // Flush logs before closing database
try { try {
await LogService.I.flush(); LogService.I.flush();
} catch (_) {} } catch (_) {}
// Close Isar database safely // Close Isar database safely
+1 -1
View File
@@ -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
await Store.delete(StoreKey.assetETag); Store.delete(StoreKey.assetETag);
await _syncService.syncNewAssetToDb(newAsset); await _syncService.syncNewAssetToDb(newAsset);
} }
@@ -1,16 +1,14 @@
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';
@@ -161,26 +159,24 @@ class DownloadStateNotifier extends StateNotifier<DownloadState> {
} }
void shareAsset(Asset asset, BuildContext context) async { void shareAsset(Asset asset, BuildContext context) async {
unawaited( showDialog(
showDialog( context: context,
context: context, builder: (BuildContext buildContext) {
builder: (BuildContext buildContext) { _shareService.shareAsset(asset, context).then((bool status) {
_shareService.shareAsset(asset, context).then((bool status) { if (!status) {
if (!status) { ImmichToast.show(
ImmichToast.show( context: context,
context: context, msg: 'image_viewer_page_state_provider_share_error'.tr(),
msg: 'image_viewer_page_state_provider_share_error'.tr(), toastType: ToastType.error,
toastType: ToastType.error, gravity: ToastGravity.BOTTOM,
gravity: ToastGravity.BOTTOM, );
); }
} buildContext.pop();
buildContext.pop(); });
}); return const ShareDialog();
return const ShareDialog(); },
}, barrierDismissible: false,
barrierDismissible: false, useRootNavigator: 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);
await _uploadService.enqueueTasks([task]); _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)) {
await Store.put(StoreKey.backgroundBackup, isEnabled); 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 {
await openAppSettings(); openAppSettings();
} }
} }
@@ -533,10 +533,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
progressInFileSpeedUpdateTime: DateTime.now(), progressInFileSpeedUpdateTime: DateTime.now(),
progressInFileSpeedUpdateSentBytes: 0, progressInFileSpeedUpdateSentBytes: 0,
); );
await _updatePersistentAlbumsSelection(); _updatePersistentAlbumsSelection();
} }
await updateDiskInfo(); 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/entities/asset.entity.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/services/backup_verification.service.dart'; import 'package:immich_mobile/services/backup_verification.service.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/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;
} }
unawaited(WakelockPlus.enable()); 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 {
unawaited(WakelockPlus.disable()); WakelockPlus.disable();
state = false; state = false;
} }
} }
@@ -7,7 +7,7 @@ part of 'backup_verification.provider.dart';
// ************************************************************************** // **************************************************************************
String _$backupVerificationHash() => String _$backupVerificationHash() =>
r'b4b34909ed1af3f28877ea457d53a4a18b6417f8'; r'b204e43ab575d5fa5b2ee663297f32bcee9074f5';
/// See also [BackupVerification]. /// See also [BackupVerification].
@ProviderFor(BackupVerification) @ProviderFor(BackupVerification)
@@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:cancellation_token_http/http.dart'; import 'package:cancellation_token_http/http.dart';
@@ -27,11 +26,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(
@@ -295,7 +294,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
); );
} }
} else { } else {
unawaited(openAppSettings()); openAppSettings();
dPrint(() => "[_startUpload] Do not have permission to the gallery"); dPrint(() => "[_startUpload] Do not have permission to the gallery");
} }
} catch (e) { } catch (e) {
@@ -3,6 +3,7 @@ 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';
@@ -76,7 +77,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 {
unawaited(chunkEvents.close()); chunkEvents.close();
} }
} }
@@ -1,5 +1,3 @@
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';
@@ -66,7 +64,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;
unawaited(_downloadService.saveLivePhotos(update.task, livePhotosId)); _downloadService.saveLivePhotos(update.task, livePhotosId);
} }
} }
@@ -124,7 +122,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');
} }
unawaited(context.pushRoute(AssetTroubleshootRoute(asset: assets.first))); context.pushRoute(AssetTroubleshootRoute(asset: assets.first));
return ActionResult(count: assets.length, success: true); return ActionResult(count: assets.length, success: true);
} }
@@ -1,5 +1,3 @@
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';
@@ -18,7 +16,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();
unawaited(fetchLinks()); fetchLinks();
} }
} }
@@ -1,18 +1,17 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart' as asset_entity; import 'package:immich_mobile/entities/asset.entity.dart' as asset_entity;
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/response_extensions.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/utils/hash.dart'; import 'package:immich_mobile/utils/hash.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:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/response_extensions.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider))); final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider)));
@@ -107,17 +106,15 @@ 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 unti // "preparing" dialog will not disappear unti
unawaited( Share.shareXFiles(downloadedXFiles).then((result) async {
Share.shareXFiles(downloadedXFiles).then((result) async { for (var file in downloadedXFiles) {
for (var file in downloadedXFiles) { try {
try { await File(file.path).delete();
await File(file.path).delete(); } catch (e) {
} catch (e) { _log.warning("Failed to delete temporary file: ${file.path}", e);
_log.warning("Failed to delete temporary file: ${file.path}", e);
}
} }
}), }
); });
return downloadedXFiles.length; return downloadedXFiles.length;
} }
@@ -1,5 +1,3 @@
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';
@@ -14,7 +12,7 @@ class AppNavigationObserver extends AutoRouterObserver {
@override @override
Future<void> didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) async { Future<void> didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) async {
unawaited(Future(() => ref.read(inLockedViewProvider.notifier).state = false)); Future(() => ref.read(inLockedViewProvider.notifier).state = false);
} }
@override @override
+3 -4
View File
@@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
@@ -27,18 +26,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');
unawaited(router.replaceAll([const LoginRoute()])); 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.');
unawaited(router.replaceAll([const LoginRoute()])); 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.");
unawaited(router.replaceAll([const LoginRoute()])); router.replaceAll([const LoginRoute()]);
return; return;
} }
} catch (e) { } catch (e) {
@@ -1,5 +1,3 @@
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';
@@ -15,7 +13,7 @@ class BackupPermissionGuard extends AutoRouteGuard {
if (p) { if (p) {
resolver.next(true); resolver.next(true);
} else { } else {
unawaited(router.push(const PermissionOnboardingRoute())); router.push(const PermissionOnboardingRoute());
} }
} }
} }
+6 -10
View File
@@ -1,5 +1,3 @@
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';
@@ -15,14 +13,12 @@ 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;
unawaited( router.replace(
router.replace( GalleryViewerRoute(
GalleryViewerRoute( renderList: args.renderList,
renderList: args.renderList, initialIndex: args.initialIndex,
initialIndex: args.initialIndex, heroOffset: args.heroOffset,
heroOffset: args.heroOffset, showStack: args.showStack,
showStack: args.showStack,
),
), ),
); );
// Prevent further navigation since we replaced the route // Prevent further navigation since we replaced the route
+4 -5
View File
@@ -1,9 +1,8 @@
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';
@@ -31,7 +30,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) {
unawaited(router.push(PinAuthRoute(createPinCode: true))); router.push(PinAuthRoute(createPinCode: true));
} }
if (authStatus.isElevated) { if (authStatus.isElevated) {
@@ -43,7 +42,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) {
unawaited(router.push(PinAuthRoute())); router.push(PinAuthRoute());
return; return;
} }
@@ -75,7 +74,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);
unawaited(router.push(PinAuthRoute())); 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
View File
@@ -1,5 +1,3 @@
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';
@@ -52,7 +50,7 @@ class ActionService {
); );
Future<void> shareLink(List<String> remoteIds, BuildContext context) async { Future<void> shareLink(List<String> remoteIds, BuildContext context) async {
unawaited(context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds))); context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds));
} }
Future<void> favorite(List<String> remoteIds) async { Future<void> favorite(List<String> remoteIds) async {
+1 -1
View File
@@ -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) {
await _syncService.removeAllLocalAlbumsAndAssets(); _syncService.removeAllLocalAlbumsAndAssets();
} }
return false; return false;
} }
+3 -3
View File
@@ -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
unawaited(Store.put(StoreKey.serverEndpoint, endpoint)); Store.put(StoreKey.serverEndpoint, endpoint);
return endpoint; return endpoint;
} }
+1 -1
View File
@@ -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();
await Store.put(StoreKey.serverUrl, validUrl); Store.put(StoreKey.serverUrl, validUrl);
return validUrl; return validUrl;
} }
+16 -22
View File
@@ -291,7 +291,7 @@ class BackgroundService {
case "backgroundProcessing": case "backgroundProcessing":
case "onAssetsChanged": case "onAssetsChanged":
try { try {
unawaited(_clearErrorNotifications()); _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();
await ref.read(apiServiceProvider).setAccessToken(Store.get(StoreKey.accessToken)); 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) {
await Store.put(StoreKey.backupFailedSince, DateTime.now()); 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,11 +412,9 @@ class BackgroundService {
try { try {
toUpload = await backupService.removeAlreadyUploadedAssets(toUpload); toUpload = await backupService.removeAlreadyUploadedAssets(toUpload);
} catch (e) { } catch (e) {
unawaited( _showErrorNotification(
_showErrorNotification( title: "backup_background_service_error_title".tr(),
title: "backup_background_service_error_title".tr(), content: "backup_background_service_connection_failed_message".tr(),
content: "backup_background_service_connection_failed_message".tr(),
),
); );
return false; return false;
} }
@@ -430,15 +428,13 @@ class BackgroundService {
} }
_assetsToUploadCount = toUpload.length; _assetsToUploadCount = toUpload.length;
_uploadedAssetsCount = 0; _uploadedAssetsCount = 0;
unawaited( _updateNotification(
_updateNotification( title: "backup_background_service_in_progress_notification".tr(),
title: "backup_background_service_in_progress_notification".tr(), content: notifyTotalProgress ? formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount) : null,
content: notifyTotalProgress ? formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount) : null, progress: 0,
progress: 0, max: notifyTotalProgress ? _assetsToUploadCount : 0,
max: notifyTotalProgress ? _assetsToUploadCount : 0, indeterminate: !notifyTotalProgress,
indeterminate: !notifyTotalProgress, onlyIfFG: !notifyTotalProgress,
onlyIfFG: !notifyTotalProgress,
),
); );
_cancellationToken = CancellationToken(); _cancellationToken = CancellationToken();
@@ -456,11 +452,9 @@ class BackgroundService {
); );
if (!ok && !_cancellationToken!.isCancelled) { if (!ok && !_cancellationToken!.isCancelled) {
unawaited( _showErrorNotification(
_showErrorNotification( title: "backup_background_service_error_title".tr(),
title: "backup_background_service_error_title".tr(), content: "backup_background_service_backup_failed_message".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);
await apiService.setAccessToken(tuple.auth); 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]);
+2 -2
View File
@@ -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();
await setHttpHeaders({'User-Agent': userAgent}); setHttpHeaders({'User-Agent': userAgent});
} }
Future<Iterable<MapMarker>> getMapMarkers({ Future<Iterable<MapMarker>> getMapMarkers({
+4 -8
View File
@@ -1,15 +1,13 @@
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/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/response_extensions.dart'; import 'package:immich_mobile/extensions/response_extensions.dart';
import 'package:immich_mobile/entities/asset.entity.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)));
@@ -60,11 +58,9 @@ class ShareService {
} }
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
unawaited( Share.shareXFiles(
Share.shareXFiles( downloadedXFiles,
downloadedXFiles, sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)),
sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)),
),
); );
return true; return true;
} catch (error) { } catch (error) {
+1 -1
View File
@@ -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)) {
await _toggleTrashStatusForAssets(assets); _toggleTrashStatusForAssets(assets);
} }
try { try {
+1 -1
View File
@@ -251,7 +251,7 @@ class UploadService {
return; return;
} }
await enqueueTasks([uploadTask]); 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");
} }
+2 -4
View File
@@ -1,10 +1,8 @@
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';
@@ -70,7 +68,7 @@ class MapUtils {
try { try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled && !silent) { if (!serviceEnabled && !silent) {
unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog())); showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog());
return (null, LocationPermission.deniedForever); return (null, LocationPermission.deniedForever);
} }
+2 -2
View File
@@ -101,7 +101,7 @@ Future<void> handleEditDateTime(WidgetRef ref, BuildContext context, List<Asset>
return; return;
} }
await ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime); 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;
} }
await ref.read(assetServiceProvider).changeLocation(selection.toList(), location); ref.read(assetServiceProvider).changeLocation(selection.toList(), location);
} }
Future<void> handleSetAssetsVisibility( Future<void> handleSetAssetsVisibility(
@@ -1,5 +1,3 @@
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';
@@ -59,7 +57,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);
unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
if (!success) { if (!success) {
ImmichToast.show( ImmichToast.show(
@@ -107,7 +105,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) {
unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); 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) {
unawaited(ref.watch(albumProvider.notifier).refreshRemoteAlbums()); ref.watch(albumProvider.notifier).refreshRemoteAlbums();
selectionEnabledHook.value = false; selectionEnabledHook.value = false;
unawaited(context.pushRoute(AlbumViewerRoute(albumId: result.id))); 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) {
unawaited(handleEditDateTime(ref, context, remoteAssets.toList())); 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) {
unawaited(handleEditLocation(ref, context, remoteAssets.toList())); handleEditLocation(ref, context, remoteAssets.toList());
} }
} finally { } finally {
selectionEnabledHook.value = false; selectionEnabledHook.value = false;
@@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
@@ -82,7 +81,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
await context.maybePop(); context.maybePop();
} }
totalAssets.value -= 1; totalAssets.value -= 1;
@@ -112,20 +111,18 @@ class BottomGalleryBar extends ConsumerWidget {
} }
// Asset is permanently removed // Asset is permanently removed
unawaited( showDialog(
showDialog( context: context,
context: context, builder: (BuildContext _) {
builder: (BuildContext _) { return DeleteDialog(
return DeleteDialog( onDelete: () async {
onDelete: () async { final isDeleted = await onDelete(true);
final isDeleted = await onDelete(true); if (isDeleted) {
if (isDeleted) { removeAssetFromStack();
removeAssetFromStack(); }
} },
}, );
); },
},
),
); );
} }
@@ -153,7 +150,7 @@ class BottomGalleryBar extends ConsumerWidget {
onTap: () async { onTap: () async {
await unStack(); await unStack();
ctx.pop(); ctx.pop();
await context.maybePop(); context.maybePop();
}, },
title: const Text("viewer_unstack", style: TextStyle(fontWeight: FontWeight.bold)).tr(), title: const Text("viewer_unstack", style: TextStyle(fontWeight: FontWeight.bold)).tr(),
), ),
@@ -181,11 +178,9 @@ 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));
unawaited( context.navigator.push(
context.navigator.push( MaterialPageRoute(
MaterialPageRoute( builder: (context) => EditImagePage(asset: asset, image: image, isEdited: false),
builder: (context) => EditImagePage(asset: asset, image: image, isEdited: false),
),
), ),
); );
} }
@@ -1,5 +1,3 @@
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';
@@ -95,7 +93,7 @@ class CastDialog extends ConsumerWidget {
} }
if (!isCurrentDevice(deviceName)) { if (!isCurrentDevice(deviceName)) {
unawaited(ref.read(castProvider.notifier).connect(type, deviceObj)); ref.read(castProvider.notifier).connect(type, deviceObj);
} }
}, },
); );
@@ -1,12 +1,11 @@
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;
@@ -69,7 +68,7 @@ class ExifMap extends StatelessWidget {
} }
dPrint(() => 'Opening Map Uri: $uri'); dPrint(() => 'Opening Map Uri: $uri');
unawaited(launchUrl(uri)); launchUrl(uri);
}, },
onCreated: onMapCreated, onCreated: onMapCreated,
); );
@@ -1,21 +1,19 @@
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:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:hooks_riverpod/hooks_riverpod.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';
@@ -99,27 +97,25 @@ class ImmichAppBarDialog extends HookConsumerWidget {
return; return;
} }
unawaited( showDialog(
showDialog( context: context,
context: context, builder: (BuildContext ctx) {
builder: (BuildContext ctx) { return ConfirmDialog(
return ConfirmDialog( title: "app_bar_signout_dialog_title",
title: "app_bar_signout_dialog_title", content: "app_bar_signout_dialog_content",
content: "app_bar_signout_dialog_content", ok: "yes",
ok: "yes", onOk: () async {
onOk: () async { isLoggingOut.value = true;
isLoggingOut.value = true; await ref.read(authProvider.notifier).logout().whenComplete(() => isLoggingOut.value = false);
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();
unawaited(ref.read(assetProvider.notifier).clearAllAssets()); ref.read(assetProvider.notifier).clearAllAssets();
ref.read(websocketProvider.notifier).disconnect(); ref.read(websocketProvider.notifier).disconnect();
unawaited(context.replaceRoute(const LoginRoute())); context.replaceRoute(const LoginRoute());
}, },
); );
}, },
),
); );
}, },
trailing: isLoggingOut.value trailing: isLoggingOut.value
@@ -1,5 +1,3 @@
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';
@@ -8,8 +6,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/backup/backup.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/backup/backup.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';
@@ -56,7 +54,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
ref.read(currentUserProvider.notifier).refresh(); ref.read(currentUserProvider.notifier).refresh();
} }
unawaited(ref.read(backupProvider.notifier).updateDiskInfo()); ref.read(backupProvider.notifier).updateDiskInfo();
} }
} }
} }
@@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
@@ -188,16 +187,16 @@ 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) {
unawaited(context.pushRoute(const ChangePasswordRoute())); 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();
unawaited(handleSyncFlow()); handleSyncFlow();
unawaited(context.replaceRoute(const TabShellRoute())); context.replaceRoute(const TabShellRoute());
return; return;
} }
unawaited(context.replaceRoute(const TabControllerRoute())); context.replaceRoute(const TabControllerRoute());
} }
} catch (error) { } catch (error) {
ImmichToast.show( ImmichToast.show(
@@ -287,15 +286,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)) {
unawaited(ref.watch(backupProvider.notifier).resumeBackup()); ref.watch(backupProvider.notifier).resumeBackup();
} }
if (isBeta) { if (isBeta) {
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
unawaited(handleSyncFlow()); handleSyncFlow();
unawaited(context.replaceRoute(const TabShellRoute())); context.replaceRoute(const TabShellRoute());
return; return;
} }
unawaited(context.replaceRoute(const TabControllerRoute())); 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);
+3 -7
View File
@@ -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/utils/draggable_scroll_controller.dart';
import 'package:immich_mobile/widgets/map/map_asset_grid.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';
class MapBottomSheet extends HookConsumerWidget { class MapBottomSheet extends HookConsumerWidget {
final Stream<MapEvent> mapEventStream; final Stream<MapEvent> mapEventStream;
@@ -34,11 +34,7 @@ class MapBottomSheet extends HookConsumerWidget {
void handleMapEvents(MapEvent event) async { void handleMapEvents(MapEvent event) async {
if (event is MapCloseBottomSheet) { if (event is MapCloseBottomSheet) {
await sheetController.animateTo( sheetController.animateTo(0.1, duration: const Duration(milliseconds: 200), curve: Curves.linearToEaseOut);
0.1,
duration: const Duration(milliseconds: 200),
curve: Curves.linearToEaseOut,
);
} }
} }
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -47,7 +45,7 @@ class BetaTimelineListTile extends ConsumerWidget {
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)])); context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)]);
}, },
child: Text("ok".t(context: context)), child: Text("ok".t(context: context)),
), ),
@@ -1,5 +1,3 @@
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';
@@ -104,13 +102,13 @@ class LocalNetworkPreference extends HookConsumerWidget {
), ),
); );
} else { } else {
unawaited(saveWifiName(wifiName)); saveWifiName(wifiName);
} }
final serverEndpoint = ref.read(authProvider.notifier).getServerEndpoint(); final serverEndpoint = ref.read(authProvider.notifier).getServerEndpoint();
if (serverEndpoint != null) { if (serverEndpoint != null) {
unawaited(saveLocalEndpoint(serverEndpoint)); saveLocalEndpoint(serverEndpoint);
} }
} }
+1
View File
@@ -393,6 +393,7 @@ Class | Method | HTTP request | Description
- [LoginCredentialDto](doc//LoginCredentialDto.md) - [LoginCredentialDto](doc//LoginCredentialDto.md)
- [LoginResponseDto](doc//LoginResponseDto.md) - [LoginResponseDto](doc//LoginResponseDto.md)
- [LogoutResponseDto](doc//LogoutResponseDto.md) - [LogoutResponseDto](doc//LogoutResponseDto.md)
- [MachineLearningAvailabilityChecksDto](doc//MachineLearningAvailabilityChecksDto.md)
- [ManualJobName](doc//ManualJobName.md) - [ManualJobName](doc//ManualJobName.md)
- [MapMarkerResponseDto](doc//MapMarkerResponseDto.md) - [MapMarkerResponseDto](doc//MapMarkerResponseDto.md)
- [MapReverseGeocodeResponseDto](doc//MapReverseGeocodeResponseDto.md) - [MapReverseGeocodeResponseDto](doc//MapReverseGeocodeResponseDto.md)
+1
View File
@@ -164,6 +164,7 @@ part 'model/log_level.dart';
part 'model/login_credential_dto.dart'; part 'model/login_credential_dto.dart';
part 'model/login_response_dto.dart'; part 'model/login_response_dto.dart';
part 'model/logout_response_dto.dart'; part 'model/logout_response_dto.dart';
part 'model/machine_learning_availability_checks_dto.dart';
part 'model/manual_job_name.dart'; part 'model/manual_job_name.dart';
part 'model/map_marker_response_dto.dart'; part 'model/map_marker_response_dto.dart';
part 'model/map_reverse_geocode_response_dto.dart'; part 'model/map_reverse_geocode_response_dto.dart';
+2
View File
@@ -382,6 +382,8 @@ class ApiClient {
return LoginResponseDto.fromJson(value); return LoginResponseDto.fromJson(value);
case 'LogoutResponseDto': case 'LogoutResponseDto':
return LogoutResponseDto.fromJson(value); return LogoutResponseDto.fromJson(value);
case 'MachineLearningAvailabilityChecksDto':
return MachineLearningAvailabilityChecksDto.fromJson(value);
case 'ManualJobName': case 'ManualJobName':
return ManualJobNameTypeTransformer().decode(value); return ManualJobNameTypeTransformer().decode(value);
case 'MapMarkerResponseDto': case 'MapMarkerResponseDto':
@@ -0,0 +1,115 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class MachineLearningAvailabilityChecksDto {
/// Returns a new [MachineLearningAvailabilityChecksDto] instance.
MachineLearningAvailabilityChecksDto({
required this.enabled,
required this.interval,
required this.timeout,
});
bool enabled;
num interval;
num timeout;
@override
bool operator ==(Object other) => identical(this, other) || other is MachineLearningAvailabilityChecksDto &&
other.enabled == enabled &&
other.interval == interval &&
other.timeout == timeout;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(enabled.hashCode) +
(interval.hashCode) +
(timeout.hashCode);
@override
String toString() => 'MachineLearningAvailabilityChecksDto[enabled=$enabled, interval=$interval, timeout=$timeout]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'enabled'] = this.enabled;
json[r'interval'] = this.interval;
json[r'timeout'] = this.timeout;
return json;
}
/// Returns a new [MachineLearningAvailabilityChecksDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static MachineLearningAvailabilityChecksDto? fromJson(dynamic value) {
upgradeDto(value, "MachineLearningAvailabilityChecksDto");
if (value is Map) {
final json = value.cast<String, dynamic>();
return MachineLearningAvailabilityChecksDto(
enabled: mapValueOfType<bool>(json, r'enabled')!,
interval: num.parse('${json[r'interval']}'),
timeout: num.parse('${json[r'timeout']}'),
);
}
return null;
}
static List<MachineLearningAvailabilityChecksDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MachineLearningAvailabilityChecksDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = MachineLearningAvailabilityChecksDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, MachineLearningAvailabilityChecksDto> mapFromJson(dynamic json) {
final map = <String, MachineLearningAvailabilityChecksDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = MachineLearningAvailabilityChecksDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of MachineLearningAvailabilityChecksDto-objects as value to a dart map
static Map<String, List<MachineLearningAvailabilityChecksDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<MachineLearningAvailabilityChecksDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = MachineLearningAvailabilityChecksDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'enabled',
'interval',
'timeout',
};
}
@@ -13,14 +13,16 @@ part of openapi.api;
class SystemConfigMachineLearningDto { class SystemConfigMachineLearningDto {
/// Returns a new [SystemConfigMachineLearningDto] instance. /// Returns a new [SystemConfigMachineLearningDto] instance.
SystemConfigMachineLearningDto({ SystemConfigMachineLearningDto({
required this.availabilityChecks,
required this.clip, required this.clip,
required this.duplicateDetection, required this.duplicateDetection,
required this.enabled, required this.enabled,
required this.facialRecognition, required this.facialRecognition,
this.url,
this.urls = const [], this.urls = const [],
}); });
MachineLearningAvailabilityChecksDto availabilityChecks;
CLIPConfig clip; CLIPConfig clip;
DuplicateDetectionConfig duplicateDetection; DuplicateDetectionConfig duplicateDetection;
@@ -29,50 +31,37 @@ class SystemConfigMachineLearningDto {
FacialRecognitionConfig facialRecognition; FacialRecognitionConfig facialRecognition;
/// This property was deprecated in v1.122.0
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? url;
List<String> urls; List<String> urls;
@override @override
bool operator ==(Object other) => identical(this, other) || other is SystemConfigMachineLearningDto && bool operator ==(Object other) => identical(this, other) || other is SystemConfigMachineLearningDto &&
other.availabilityChecks == availabilityChecks &&
other.clip == clip && other.clip == clip &&
other.duplicateDetection == duplicateDetection && other.duplicateDetection == duplicateDetection &&
other.enabled == enabled && other.enabled == enabled &&
other.facialRecognition == facialRecognition && other.facialRecognition == facialRecognition &&
other.url == url &&
_deepEquality.equals(other.urls, urls); _deepEquality.equals(other.urls, urls);
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(availabilityChecks.hashCode) +
(clip.hashCode) + (clip.hashCode) +
(duplicateDetection.hashCode) + (duplicateDetection.hashCode) +
(enabled.hashCode) + (enabled.hashCode) +
(facialRecognition.hashCode) + (facialRecognition.hashCode) +
(url == null ? 0 : url!.hashCode) +
(urls.hashCode); (urls.hashCode);
@override @override
String toString() => 'SystemConfigMachineLearningDto[clip=$clip, duplicateDetection=$duplicateDetection, enabled=$enabled, facialRecognition=$facialRecognition, url=$url, urls=$urls]'; String toString() => 'SystemConfigMachineLearningDto[availabilityChecks=$availabilityChecks, clip=$clip, duplicateDetection=$duplicateDetection, enabled=$enabled, facialRecognition=$facialRecognition, urls=$urls]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'availabilityChecks'] = this.availabilityChecks;
json[r'clip'] = this.clip; json[r'clip'] = this.clip;
json[r'duplicateDetection'] = this.duplicateDetection; json[r'duplicateDetection'] = this.duplicateDetection;
json[r'enabled'] = this.enabled; json[r'enabled'] = this.enabled;
json[r'facialRecognition'] = this.facialRecognition; json[r'facialRecognition'] = this.facialRecognition;
if (this.url != null) {
json[r'url'] = this.url;
} else {
// json[r'url'] = null;
}
json[r'urls'] = this.urls; json[r'urls'] = this.urls;
return json; return json;
} }
@@ -86,11 +75,11 @@ class SystemConfigMachineLearningDto {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return SystemConfigMachineLearningDto( return SystemConfigMachineLearningDto(
availabilityChecks: MachineLearningAvailabilityChecksDto.fromJson(json[r'availabilityChecks'])!,
clip: CLIPConfig.fromJson(json[r'clip'])!, clip: CLIPConfig.fromJson(json[r'clip'])!,
duplicateDetection: DuplicateDetectionConfig.fromJson(json[r'duplicateDetection'])!, duplicateDetection: DuplicateDetectionConfig.fromJson(json[r'duplicateDetection'])!,
enabled: mapValueOfType<bool>(json, r'enabled')!, enabled: mapValueOfType<bool>(json, r'enabled')!,
facialRecognition: FacialRecognitionConfig.fromJson(json[r'facialRecognition'])!, facialRecognition: FacialRecognitionConfig.fromJson(json[r'facialRecognition'])!,
url: mapValueOfType<String>(json, r'url'),
urls: json[r'urls'] is Iterable urls: json[r'urls'] is Iterable
? (json[r'urls'] as Iterable).cast<String>().toList(growable: false) ? (json[r'urls'] as Iterable).cast<String>().toList(growable: false)
: const [], : const [],
@@ -141,6 +130,7 @@ class SystemConfigMachineLearningDto {
/// The list of required keys that must be present in a JSON. /// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{ static const requiredKeys = <String>{
'availabilityChecks',
'clip', 'clip',
'duplicateDetection', 'duplicateDetection',
'enabled', 'enabled',
@@ -53,7 +53,7 @@ void main() {
}); });
tearDown(() async { tearDown(() async {
unawaited(sut.dispose()); 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()];
unawaited(expectLater(stream, emitsInOrder(events))); expectLater(stream, emitsInOrder(events));
for (final event in events) { for (final event in events) {
valueController.add(event); valueController.add(event);
@@ -1,5 +1,3 @@
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';
@@ -101,7 +99,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();
unawaited(expectLater(await db.storeValues.count(), isZero)); expectLater(await db.storeValues.count(), isZero);
}); });
}); });
@@ -126,31 +124,29 @@ void main() {
test('watch()', () async { test('watch()', () async {
final stream = sut.watch(StoreKey.version); final stream = sut.watch(StoreKey.version);
unawaited(expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10]))); 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();
unawaited( expectLater(
expectLater( stream,
stream, emitsInOrder([
emitsInOrder([ [
[ const StoreDto<Object>(StoreKey.version, _kTestVersion),
const StoreDto<Object>(StoreKey.version, _kTestVersion), StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed), const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken), const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface), ],
], [
[ const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10), StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed), const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken), const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
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));
await Store.put(StoreKey.currentUser, UserStub.admin); Store.put(StoreKey.currentUser, UserStub.admin);
await Store.put(StoreKey.serverEndpoint, ''); Store.put(StoreKey.serverEndpoint, '');
await Store.put(StoreKey.accessToken, ''); 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));
await Store.put(StoreKey.currentUser, UserStub.admin); Store.put(StoreKey.currentUser, UserStub.admin);
await Store.put(StoreKey.serverEndpoint, ''); 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));
await Store.put(StoreKey.currentUser, UserStub.admin); Store.put(StoreKey.currentUser, UserStub.admin);
await Store.put(StoreKey.serverEndpoint, ''); Store.put(StoreKey.serverEndpoint, '');
await Store.put(StoreKey.accessToken, ''); Store.put(StoreKey.accessToken, '');
}); });
setUp(() { setUp(() {
@@ -1,5 +1,3 @@
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';
@@ -9,11 +7,11 @@ void main() {
AsyncMutex lock = AsyncMutex(); AsyncMutex lock = AsyncMutex();
List<int> events = []; List<int> events = [];
expect(0, lock.enqueued); expect(0, lock.enqueued);
unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(1)))); lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(1)));
expect(1, lock.enqueued); expect(1, lock.enqueued);
unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 3), () => events.add(2)))); lock.run(() => Future.delayed(const Duration(milliseconds: 3), () => events.add(2)));
expect(2, lock.enqueued); expect(2, lock.enqueued);
unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 1), () => events.add(3)))); 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);

Some files were not shown because too many files have changed in this diff Show More