Compare commits
18 Commits
refactor/r
...
fix/mobile
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ab7f4297c | ||
|
|
e1e24f3d60 | ||
|
|
33d76fb386 | ||
|
|
642065f506 | ||
|
|
de897f6069 | ||
|
|
68f3ed89c5 | ||
|
|
78516a97b3 | ||
|
|
b8a17c3c26 | ||
|
|
e42886b767 | ||
|
|
d36c26bf97 | ||
|
|
dcbc266b83 | ||
|
|
e8a9a2209d | ||
|
|
379d9ab1e4 | ||
|
|
f3e427f268 | ||
|
|
28380b1b46 | ||
|
|
abb5df8bab | ||
|
|
8face037da | ||
|
|
4a449104aa |
@@ -5,8 +5,7 @@
|
||||
"immich-server",
|
||||
"redis",
|
||||
"database",
|
||||
"immich-machine-learning",
|
||||
"init"
|
||||
"immich-machine-learning"
|
||||
],
|
||||
"dockerComposeFile": [
|
||||
"../docker/docker-compose.dev.yml",
|
||||
|
||||
34
mise.lock
34
mise.lock
@@ -1,34 +0,0 @@
|
||||
[tools.dart]
|
||||
version = "3.8.2"
|
||||
backend = "asdf:dart"
|
||||
|
||||
[tools.flutter]
|
||||
version = "3.35.3-stable"
|
||||
backend = "asdf:flutter"
|
||||
|
||||
[tools."github:CQLabs/homebrew-dcm"]
|
||||
version = "1.31.4"
|
||||
backend = "github:CQLabs/homebrew-dcm"
|
||||
|
||||
[tools."github:CQLabs/homebrew-dcm".platforms.linux-x64]
|
||||
checksum = "blake3:e9df5b765df327e1248fccf2c6165a89d632a065667f99c01765bf3047b94955"
|
||||
size = 8821083
|
||||
url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.31.4/dcm-linux-x64-release.zip"
|
||||
|
||||
[tools.node]
|
||||
version = "22.18.0"
|
||||
backend = "core:node"
|
||||
|
||||
[tools.node.platforms.linux-x64]
|
||||
checksum = "sha256:a2e703725d8683be86bb5da967bf8272f4518bdaf10f21389e2b2c9eaeae8c8a"
|
||||
size = 54824343
|
||||
url = "https://nodejs.org/dist/v22.18.0/node-v22.18.0-linux-x64.tar.gz"
|
||||
|
||||
[tools.pnpm]
|
||||
version = "10.14.0"
|
||||
backend = "aqua:pnpm/pnpm"
|
||||
|
||||
[tools.pnpm.platforms.linux-x64]
|
||||
checksum = "blake3:13dfa46b7173d3cad3bad60a756a492ecf0bce48b23eb9f793e7ccec5a09b46d"
|
||||
size = 66231525
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v10.14.0/pnpm-linux-x64"
|
||||
@@ -11,7 +11,6 @@ postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm"
|
||||
|
||||
[settings]
|
||||
experimental = true
|
||||
lockfile = true
|
||||
pin = true
|
||||
|
||||
# .github
|
||||
|
||||
@@ -17,7 +17,7 @@ linter:
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
# https://dart.dev/tools/linter-rules
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
@@ -28,6 +28,7 @@ linter:
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
unawaited_futures: true
|
||||
use_build_context_synchronously: false
|
||||
require_trailing_commas: true
|
||||
unrelated_type_equality_checks: true
|
||||
@@ -47,6 +48,9 @@ analyzer:
|
||||
# plugins:
|
||||
# - custom_lint
|
||||
|
||||
errors:
|
||||
unawaited_futures: warning
|
||||
|
||||
custom_lint:
|
||||
debug: true
|
||||
rules:
|
||||
@@ -142,170 +146,7 @@ dart_code_metrics:
|
||||
exclude-paths:
|
||||
- 'lib/utils/debug_print.dart'
|
||||
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-unnecessary-type-assertions
|
||||
# - avoid-unnecessary-type-casts
|
||||
# - avoid-unrelated-type-assertions
|
||||
# - avoid-unrelated-type-casts
|
||||
# - no-empty-block
|
||||
# - no-equal-then-else
|
||||
# - prefer-correct-test-file-name
|
||||
- prefer-const-border-radius
|
||||
# - prefer-match-file-name
|
||||
# - prefer-return-await
|
||||
# - avoid-self-assignment
|
||||
# - avoid-self-compare
|
||||
# - avoid-shadowing
|
||||
# - prefer-iterable-of
|
||||
# - no-equal-switch-case
|
||||
# - no-equal-conditions
|
||||
# - avoid-equal-expressions
|
||||
# - avoid-missed-calls
|
||||
# - avoid-unnecessary-negations
|
||||
# - avoid-unused-generics
|
||||
# - function-always-returns-null
|
||||
# - avoid-throw-objects-without-tostring
|
||||
# - avoid-unsafe-collection-methods
|
||||
# - prefer-wildcard-pattern
|
||||
# - no-equal-switch-expression-cases
|
||||
# - avoid-future-tostring
|
||||
# - avoid-unassigned-late-fields
|
||||
# - avoid-nested-futures
|
||||
# - avoid-generics-shadowing
|
||||
# - prefer-parentheses-with-if-null
|
||||
# - no-equal-nested-conditions
|
||||
# - avoid-shadowed-extension-methods
|
||||
# - avoid-unnecessary-conditionals
|
||||
# - avoid-double-slash-imports
|
||||
# - avoid-map-keys-contains
|
||||
# - prefer-correct-json-casts
|
||||
# - avoid-duplicate-mixins
|
||||
# - avoid-nullable-interpolation
|
||||
# - avoid-unused-instances
|
||||
# - prefer-correct-for-loop-increment
|
||||
# - prefer-public-exception-classes
|
||||
# - avoid-uncaught-future-errors
|
||||
# - always-remove-listener
|
||||
# - avoid-unnecessary-setstate
|
||||
# - check-for-equals-in-render-object-setters
|
||||
# - consistent-update-render-object
|
||||
# - use-setstate-synchronously
|
||||
# - avoid-incomplete-copy-with
|
||||
# - proper-super-calls
|
||||
# - dispose-fields
|
||||
# - avoid-empty-setstate
|
||||
# - avoid-state-constructors
|
||||
# - avoid-recursive-widget-calls
|
||||
# - avoid-missing-image-alt
|
||||
# - avoid-passing-self-as-argument
|
||||
# - avoid-unnecessary-if
|
||||
# - avoid-unconditional-break
|
||||
# - avoid-referencing-discarded-variables
|
||||
# - avoid-unnecessary-local-late
|
||||
# - avoid-wildcard-cases-with-enums
|
||||
# - match-getter-setter-field-names
|
||||
# - avoid-accessing-collections-by-constant-index
|
||||
# - prefer-unique-test-names
|
||||
# - avoid-duplicate-cascades
|
||||
# - prefer-specific-cases-first
|
||||
# - avoid-duplicate-switch-case-conditions
|
||||
# - prefer-explicit-function-type
|
||||
# - avoid-misused-test-matchers
|
||||
# - avoid-duplicate-test-assertions
|
||||
# - prefer-switch-with-enums
|
||||
# - prefer-any-or-every
|
||||
# - avoid-duplicate-map-keys
|
||||
# - avoid-nullable-tostring
|
||||
# - avoid-undisposed-instances
|
||||
# - avoid-duplicate-initializers
|
||||
# - avoid-unassigned-stream-subscriptions
|
||||
# - avoid-empty-test-groups
|
||||
# - avoid-not-encodable-in-to-json
|
||||
# - avoid-contradictory-expressions
|
||||
# - avoid-excessive-expressions
|
||||
# - prefer-private-extension-type-field
|
||||
# - avoid-renaming-representation-getters
|
||||
# - avoid-empty-spread
|
||||
# - avoid-unnecessary-gesture-detector
|
||||
# - avoid-missing-completer-stack-trace
|
||||
# - avoid-casting-to-extension-type
|
||||
# - prefer-overriding-parent-equality
|
||||
# - avoid-missing-controller
|
||||
# - avoid-unknown-pragma
|
||||
# - avoid-conditions-with-boolean-literals
|
||||
# - avoid-multi-assignment
|
||||
# - avoid-collection-equality-checks
|
||||
# - avoid-only-rethrow
|
||||
# - avoid-incorrect-image-opacity
|
||||
# - avoid-misused-set-literals
|
||||
# - dispose-class-fields
|
||||
# - avoid-suspicious-super-overrides
|
||||
# - avoid-assignments-as-conditions
|
||||
# - avoid-unused-assignment
|
||||
# - avoid-unnecessary-overrides
|
||||
# - avoid-implicitly-nullable-extension-types
|
||||
# Enable with the next release
|
||||
# - avoid-late-final-reassignment
|
||||
# - avoid-duplicate-constant-values
|
||||
# - function-always-returns-same-value
|
||||
# - avoid-flexible-outside-flex
|
||||
# - avoid-unnecessary-patterns
|
||||
# - use-closest-build-context
|
||||
# - avoid-commented-out-code
|
||||
# - avoid-recursive-tostring
|
||||
# - avoid-enum-values-by-index
|
||||
# - avoid-constant-assert-conditions
|
||||
# - avoid-inconsistent-digit-separators
|
||||
# - pass-existing-future-to-future-builder
|
||||
# - pass-existing-stream-to-stream-builder
|
||||
|
||||
# Code simplification
|
||||
# - avoid-redundant-async
|
||||
# - avoid-redundant-else
|
||||
# - avoid-unnecessary-nullable-return-type
|
||||
# - avoid-redundant-pragma-inline
|
||||
# - avoid-nested-records
|
||||
# - avoid-redundant-positional-field-name
|
||||
# - avoid-explicit-pattern-field-name
|
||||
# - prefer-simpler-patterns-null-check
|
||||
# - avoid-unnecessary-return
|
||||
# - avoid-duplicate-patterns
|
||||
# - avoid-keywords-in-wildcard-pattern
|
||||
# - avoid-unnecessary-futures
|
||||
# - avoid-unnecessary-reassignment
|
||||
# - avoid-unnecessary-call
|
||||
# - avoid-unnecessary-stateful-widgets
|
||||
# - prefer-dedicated-media-query-methods
|
||||
# - avoid-unnecessary-overrides-in-state
|
||||
# - move-variable-closer-to-its-usage
|
||||
# - avoid-nullable-parameters-with-default-values
|
||||
# - prefer-null-aware-spread
|
||||
# - avoid-inferrable-type-arguments
|
||||
# - avoid-unnecessary-super
|
||||
# - avoid-unnecessary-collections
|
||||
# - avoid-unnecessary-extends
|
||||
# - avoid-unnecessary-enum-arguments
|
||||
# - prefer-contains
|
||||
# Enable with the next release
|
||||
# - prefer-simpler-boolean-expressions
|
||||
# - prefer-spacing
|
||||
# - avoid-unnecessary-continue
|
||||
# - avoid-unnecessary-compare-to
|
||||
|
||||
# Style
|
||||
# - prefer-trailing-comma
|
||||
# - unnecessary-trailing-comma
|
||||
- prefer-declaring-const-constructor
|
||||
# - prefer-single-widget-per-file
|
||||
- prefer-switch-expression
|
||||
# - prefer-prefixed-global-constants
|
||||
# - prefer-correct-callback-field-name
|
||||
|
||||
1
mobile/drift_schemas/main/drift_schema_v11.json
generated
Normal file
1
mobile/drift_schemas/main/drift_schema_v11.json
generated
Normal file
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -133,6 +133,8 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = Sync;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -519,14 +521,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
@@ -555,14 +553,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
|
||||
@@ -117,10 +117,10 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||
}
|
||||
|
||||
// Notify the host that the background worker service has been initialized and is ready to use
|
||||
_backgroundHostApi.onInitialized();
|
||||
unawaited(_backgroundHostApi.onInitialized());
|
||||
} catch (error, stack) {
|
||||
_logger.severe("Failed to initialize background worker", error, stack);
|
||||
_backgroundHostApi.close();
|
||||
unawaited(_backgroundHostApi.close());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ class LocalSyncService {
|
||||
|
||||
if (assetsToUpsert.isEmpty && assetsToDelete.isEmpty) {
|
||||
_log.fine("No asset changes detected in album ${deviceAlbum.name}. Updating metadata.");
|
||||
_localAlbumRepository.upsert(updatedDeviceAlbum);
|
||||
await _localAlbumRepository.upsert(updatedDeviceAlbum);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ class LocalAlbumAssetEntity extends Table with DriftDefaultsMixin {
|
||||
|
||||
TextColumn get albumId => text().references(LocalAlbumEntity, #id, onDelete: KeyAction.cascade)();
|
||||
|
||||
// Used for mark & sweep
|
||||
BoolColumn get marker_ => boolean().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {assetId, albumId};
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ typedef $$LocalAlbumAssetEntityTableCreateCompanionBuilder =
|
||||
i1.LocalAlbumAssetEntityCompanion Function({
|
||||
required String assetId,
|
||||
required String albumId,
|
||||
i0.Value<bool?> marker_,
|
||||
});
|
||||
typedef $$LocalAlbumAssetEntityTableUpdateCompanionBuilder =
|
||||
i1.LocalAlbumAssetEntityCompanion Function({
|
||||
i0.Value<String> assetId,
|
||||
i0.Value<String> albumId,
|
||||
i0.Value<bool?> marker_,
|
||||
});
|
||||
|
||||
final class $$LocalAlbumAssetEntityTableReferences
|
||||
@@ -113,6 +115,11 @@ class $$LocalAlbumAssetEntityTableFilterComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnFilters<bool> get marker_ => $composableBuilder(
|
||||
column: $table.marker_,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i3.$$LocalAssetEntityTableFilterComposer get assetId {
|
||||
final i3.$$LocalAssetEntityTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
@@ -177,6 +184,11 @@ class $$LocalAlbumAssetEntityTableOrderingComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.ColumnOrderings<bool> get marker_ => $composableBuilder(
|
||||
column: $table.marker_,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i3.$$LocalAssetEntityTableOrderingComposer get assetId {
|
||||
final i3.$$LocalAssetEntityTableOrderingComposer composer =
|
||||
$composerBuilder(
|
||||
@@ -243,6 +255,9 @@ class $$LocalAlbumAssetEntityTableAnnotationComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
i0.GeneratedColumn<bool> get marker_ =>
|
||||
$composableBuilder(column: $table.marker_, builder: (column) => column);
|
||||
|
||||
i3.$$LocalAssetEntityTableAnnotationComposer get assetId {
|
||||
final i3.$$LocalAssetEntityTableAnnotationComposer composer =
|
||||
$composerBuilder(
|
||||
@@ -344,16 +359,22 @@ class $$LocalAlbumAssetEntityTableTableManager
|
||||
({
|
||||
i0.Value<String> assetId = const i0.Value.absent(),
|
||||
i0.Value<String> albumId = const i0.Value.absent(),
|
||||
i0.Value<bool?> marker_ = const i0.Value.absent(),
|
||||
}) => i1.LocalAlbumAssetEntityCompanion(
|
||||
assetId: assetId,
|
||||
albumId: albumId,
|
||||
marker_: marker_,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({required String assetId, required String albumId}) =>
|
||||
i1.LocalAlbumAssetEntityCompanion.insert(
|
||||
assetId: assetId,
|
||||
albumId: albumId,
|
||||
),
|
||||
({
|
||||
required String assetId,
|
||||
required String albumId,
|
||||
i0.Value<bool?> marker_ = const i0.Value.absent(),
|
||||
}) => i1.LocalAlbumAssetEntityCompanion.insert(
|
||||
assetId: assetId,
|
||||
albumId: albumId,
|
||||
marker_: marker_,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map(
|
||||
(e) => (
|
||||
@@ -477,8 +498,22 @@ class $LocalAlbumAssetEntityTable extends i2.LocalAlbumAssetEntity
|
||||
'REFERENCES local_album_entity (id) ON DELETE CASCADE',
|
||||
),
|
||||
);
|
||||
static const i0.VerificationMeta _marker_Meta = const i0.VerificationMeta(
|
||||
'marker_',
|
||||
);
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [assetId, albumId];
|
||||
late final i0.GeneratedColumn<bool> marker_ = i0.GeneratedColumn<bool>(
|
||||
'marker',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i0.DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("marker" IN (0, 1))',
|
||||
),
|
||||
);
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [assetId, albumId, marker_];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
@@ -507,6 +542,12 @@ class $LocalAlbumAssetEntityTable extends i2.LocalAlbumAssetEntity
|
||||
} else if (isInserting) {
|
||||
context.missing(_albumIdMeta);
|
||||
}
|
||||
if (data.containsKey('marker')) {
|
||||
context.handle(
|
||||
_marker_Meta,
|
||||
marker_.isAcceptableOrUnknown(data['marker']!, _marker_Meta),
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -527,6 +568,10 @@ class $LocalAlbumAssetEntityTable extends i2.LocalAlbumAssetEntity
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}album_id'],
|
||||
)!,
|
||||
marker_: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.bool,
|
||||
data['${effectivePrefix}marker'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -545,15 +590,20 @@ class LocalAlbumAssetEntityData extends i0.DataClass
|
||||
implements i0.Insertable<i1.LocalAlbumAssetEntityData> {
|
||||
final String assetId;
|
||||
final String albumId;
|
||||
final bool? marker_;
|
||||
const LocalAlbumAssetEntityData({
|
||||
required this.assetId,
|
||||
required this.albumId,
|
||||
this.marker_,
|
||||
});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['asset_id'] = i0.Variable<String>(assetId);
|
||||
map['album_id'] = i0.Variable<String>(albumId);
|
||||
if (!nullToAbsent || marker_ != null) {
|
||||
map['marker'] = i0.Variable<bool>(marker_);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -565,6 +615,7 @@ class LocalAlbumAssetEntityData extends i0.DataClass
|
||||
return LocalAlbumAssetEntityData(
|
||||
assetId: serializer.fromJson<String>(json['assetId']),
|
||||
albumId: serializer.fromJson<String>(json['albumId']),
|
||||
marker_: serializer.fromJson<bool?>(json['marker_']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
@@ -573,20 +624,26 @@ class LocalAlbumAssetEntityData extends i0.DataClass
|
||||
return <String, dynamic>{
|
||||
'assetId': serializer.toJson<String>(assetId),
|
||||
'albumId': serializer.toJson<String>(albumId),
|
||||
'marker_': serializer.toJson<bool?>(marker_),
|
||||
};
|
||||
}
|
||||
|
||||
i1.LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) =>
|
||||
i1.LocalAlbumAssetEntityData(
|
||||
assetId: assetId ?? this.assetId,
|
||||
albumId: albumId ?? this.albumId,
|
||||
);
|
||||
i1.LocalAlbumAssetEntityData copyWith({
|
||||
String? assetId,
|
||||
String? albumId,
|
||||
i0.Value<bool?> marker_ = const i0.Value.absent(),
|
||||
}) => i1.LocalAlbumAssetEntityData(
|
||||
assetId: assetId ?? this.assetId,
|
||||
albumId: albumId ?? this.albumId,
|
||||
marker_: marker_.present ? marker_.value : this.marker_,
|
||||
);
|
||||
LocalAlbumAssetEntityData copyWithCompanion(
|
||||
i1.LocalAlbumAssetEntityCompanion data,
|
||||
) {
|
||||
return LocalAlbumAssetEntityData(
|
||||
assetId: data.assetId.present ? data.assetId.value : this.assetId,
|
||||
albumId: data.albumId.present ? data.albumId.value : this.albumId,
|
||||
marker_: data.marker_.present ? data.marker_.value : this.marker_,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -594,51 +651,60 @@ class LocalAlbumAssetEntityData extends i0.DataClass
|
||||
String toString() {
|
||||
return (StringBuffer('LocalAlbumAssetEntityData(')
|
||||
..write('assetId: $assetId, ')
|
||||
..write('albumId: $albumId')
|
||||
..write('albumId: $albumId, ')
|
||||
..write('marker_: $marker_')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(assetId, albumId);
|
||||
int get hashCode => Object.hash(assetId, albumId, marker_);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.LocalAlbumAssetEntityData &&
|
||||
other.assetId == this.assetId &&
|
||||
other.albumId == this.albumId);
|
||||
other.albumId == this.albumId &&
|
||||
other.marker_ == this.marker_);
|
||||
}
|
||||
|
||||
class LocalAlbumAssetEntityCompanion
|
||||
extends i0.UpdateCompanion<i1.LocalAlbumAssetEntityData> {
|
||||
final i0.Value<String> assetId;
|
||||
final i0.Value<String> albumId;
|
||||
final i0.Value<bool?> marker_;
|
||||
const LocalAlbumAssetEntityCompanion({
|
||||
this.assetId = const i0.Value.absent(),
|
||||
this.albumId = const i0.Value.absent(),
|
||||
this.marker_ = const i0.Value.absent(),
|
||||
});
|
||||
LocalAlbumAssetEntityCompanion.insert({
|
||||
required String assetId,
|
||||
required String albumId,
|
||||
this.marker_ = const i0.Value.absent(),
|
||||
}) : assetId = i0.Value(assetId),
|
||||
albumId = i0.Value(albumId);
|
||||
static i0.Insertable<i1.LocalAlbumAssetEntityData> custom({
|
||||
i0.Expression<String>? assetId,
|
||||
i0.Expression<String>? albumId,
|
||||
i0.Expression<bool>? marker_,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
if (assetId != null) 'asset_id': assetId,
|
||||
if (albumId != null) 'album_id': albumId,
|
||||
if (marker_ != null) 'marker': marker_,
|
||||
});
|
||||
}
|
||||
|
||||
i1.LocalAlbumAssetEntityCompanion copyWith({
|
||||
i0.Value<String>? assetId,
|
||||
i0.Value<String>? albumId,
|
||||
i0.Value<bool?>? marker_,
|
||||
}) {
|
||||
return i1.LocalAlbumAssetEntityCompanion(
|
||||
assetId: assetId ?? this.assetId,
|
||||
albumId: albumId ?? this.albumId,
|
||||
marker_: marker_ ?? this.marker_,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -651,6 +717,9 @@ class LocalAlbumAssetEntityCompanion
|
||||
if (albumId.present) {
|
||||
map['album_id'] = i0.Variable<String>(albumId.value);
|
||||
}
|
||||
if (marker_.present) {
|
||||
map['marker'] = i0.Variable<bool>(marker_.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -658,7 +727,8 @@ class LocalAlbumAssetEntityCompanion
|
||||
String toString() {
|
||||
return (StringBuffer('LocalAlbumAssetEntityCompanion(')
|
||||
..write('assetId: $assetId, ')
|
||||
..write('albumId: $albumId')
|
||||
..write('albumId: $albumId, ')
|
||||
..write('marker_: $marker_')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class RemoteImageRequest extends ImageRequest {
|
||||
final cacheManager = this.cacheManager;
|
||||
final streamController = StreamController<List<int>>(sync: true);
|
||||
final Stream<List<int>> stream;
|
||||
cacheManager?.putStreamedFile(url, streamController.stream);
|
||||
unawaited(cacheManager?.putStreamedFile(url, streamController.stream));
|
||||
stream = response.map((chunk) {
|
||||
if (_isCancelled) {
|
||||
throw StateError('Cancelled request');
|
||||
@@ -81,11 +81,11 @@ class RemoteImageRequest extends ImageRequest {
|
||||
|
||||
try {
|
||||
final Uint8List bytes = await _downloadBytes(stream, response.contentLength);
|
||||
streamController.close();
|
||||
unawaited(streamController.close());
|
||||
return await ImmutableBuffer.fromUint8List(bytes);
|
||||
} catch (e) {
|
||||
streamController.addError(e);
|
||||
streamController.close();
|
||||
unawaited(streamController.close());
|
||||
if (_isCancelled) {
|
||||
return null;
|
||||
}
|
||||
@@ -143,7 +143,7 @@ class RemoteImageRequest extends ImageRequest {
|
||||
return await _decodeBuffer(buffer, decode, scale);
|
||||
} catch (e) {
|
||||
log.severe('Failed to decode cached image', e);
|
||||
_evictFile(url);
|
||||
unawaited(_evictFile(url));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 10;
|
||||
int get schemaVersion => 11;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -156,6 +156,9 @@ class Drift extends $Drift implements IDatabaseRepository {
|
||||
await m.addColumn(v10.userEntity, v10.userEntity.avatarColor);
|
||||
await m.alterTable(TableMigration(v10.userEntity));
|
||||
},
|
||||
from10To11: (m, v11) async {
|
||||
await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -4270,6 +4270,395 @@ i1.GeneratedColumn<String> _column_94(String aliasedName) =>
|
||||
true,
|
||||
type: i1.DriftSqlType.string,
|
||||
);
|
||||
|
||||
final class Schema11 extends i0.VersionedSchema {
|
||||
Schema11({required super.database}) : super(version: 11);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
userEntity,
|
||||
remoteAssetEntity,
|
||||
stackEntity,
|
||||
localAssetEntity,
|
||||
remoteAlbumEntity,
|
||||
localAlbumEntity,
|
||||
localAlbumAssetEntity,
|
||||
idxLocalAssetChecksum,
|
||||
idxRemoteAssetOwnerChecksum,
|
||||
uQRemoteAssetsOwnerChecksum,
|
||||
uQRemoteAssetsOwnerLibraryChecksum,
|
||||
idxRemoteAssetChecksum,
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
remoteExifEntity,
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity,
|
||||
memoryEntity,
|
||||
memoryAssetEntity,
|
||||
personEntity,
|
||||
assetFaceEntity,
|
||||
storeEntity,
|
||||
idxLatLng,
|
||||
];
|
||||
late final Shape20 userEntity = Shape20(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_3,
|
||||
_column_84,
|
||||
_column_85,
|
||||
_column_91,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape17 remoteAssetEntity = Shape17(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_1,
|
||||
_column_8,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_10,
|
||||
_column_11,
|
||||
_column_12,
|
||||
_column_0,
|
||||
_column_13,
|
||||
_column_14,
|
||||
_column_15,
|
||||
_column_16,
|
||||
_column_17,
|
||||
_column_18,
|
||||
_column_19,
|
||||
_column_20,
|
||||
_column_21,
|
||||
_column_86,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape3 stackEntity = Shape3(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'stack_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape2 localAssetEntity = Shape2(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_1,
|
||||
_column_8,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_10,
|
||||
_column_11,
|
||||
_column_12,
|
||||
_column_0,
|
||||
_column_22,
|
||||
_column_14,
|
||||
_column_23,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape9 remoteAlbumEntity = Shape9(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_56,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_15,
|
||||
_column_57,
|
||||
_column_58,
|
||||
_column_59,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape19 localAlbumEntity = Shape19(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_album_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_5,
|
||||
_column_31,
|
||||
_column_32,
|
||||
_column_90,
|
||||
_column_33,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape22 localAlbumAssetEntity = Shape22(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_album_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||
columns: [_column_34, _column_35, _column_33],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxLocalAssetChecksum = i1.Index(
|
||||
'idx_local_asset_checksum',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
|
||||
'idx_remote_asset_owner_checksum',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
|
||||
);
|
||||
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
||||
'UQ_remote_assets_owner_checksum',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
||||
);
|
||||
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
||||
'UQ_remote_assets_owner_library_checksum',
|
||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
||||
'idx_remote_asset_checksum',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
||||
);
|
||||
late final Shape21 authUserEntity = Shape21(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'auth_user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_3,
|
||||
_column_2,
|
||||
_column_84,
|
||||
_column_85,
|
||||
_column_92,
|
||||
_column_93,
|
||||
_column_7,
|
||||
_column_94,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape4 userMetadataEntity = Shape4(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_metadata_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
||||
columns: [_column_25, _column_26, _column_27],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape5 partnerEntity = Shape5(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'partner_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||
columns: [_column_28, _column_29, _column_30],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape8 remoteExifEntity = Shape8(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_exif_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||
columns: [
|
||||
_column_36,
|
||||
_column_37,
|
||||
_column_38,
|
||||
_column_39,
|
||||
_column_40,
|
||||
_column_41,
|
||||
_column_11,
|
||||
_column_10,
|
||||
_column_42,
|
||||
_column_43,
|
||||
_column_44,
|
||||
_column_45,
|
||||
_column_46,
|
||||
_column_47,
|
||||
_column_48,
|
||||
_column_49,
|
||||
_column_50,
|
||||
_column_51,
|
||||
_column_52,
|
||||
_column_53,
|
||||
_column_54,
|
||||
_column_55,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||
columns: [_column_36, _column_60],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape10 remoteAlbumUserEntity = Shape10(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
||||
columns: [_column_60, _column_25, _column_61],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape11 memoryEntity = Shape11(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'memory_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_18,
|
||||
_column_15,
|
||||
_column_8,
|
||||
_column_62,
|
||||
_column_63,
|
||||
_column_64,
|
||||
_column_65,
|
||||
_column_66,
|
||||
_column_67,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape12 memoryAssetEntity = Shape12(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'memory_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
||||
columns: [_column_36, _column_68],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape14 personEntity = Shape14(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'person_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_9,
|
||||
_column_5,
|
||||
_column_15,
|
||||
_column_1,
|
||||
_column_69,
|
||||
_column_71,
|
||||
_column_72,
|
||||
_column_73,
|
||||
_column_74,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape15 assetFaceEntity = Shape15(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'asset_face_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_36,
|
||||
_column_76,
|
||||
_column_77,
|
||||
_column_78,
|
||||
_column_79,
|
||||
_column_80,
|
||||
_column_81,
|
||||
_column_82,
|
||||
_column_83,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape18 storeEntity = Shape18(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'store_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [_column_87, _column_88, _column_89],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxLatLng = i1.Index(
|
||||
'idx_lat_lng',
|
||||
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
||||
);
|
||||
}
|
||||
|
||||
class Shape22 extends i0.VersionedTable {
|
||||
Shape22({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<String> get assetId =>
|
||||
columnsByName['asset_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get albumId =>
|
||||
columnsByName['album_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<bool> get marker_ =>
|
||||
columnsByName['marker']! as i1.GeneratedColumn<bool>;
|
||||
}
|
||||
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
@@ -4280,6 +4669,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
@@ -4328,6 +4718,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from9To10(migrator, schema);
|
||||
return 10;
|
||||
case 10:
|
||||
final schema = Schema11(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from10To11(migrator, schema);
|
||||
return 11;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
@@ -4344,6 +4739,7 @@ i1.OnUpgrade stepByStep({
|
||||
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from1To2: from1To2,
|
||||
@@ -4355,5 +4751,6 @@ i1.OnUpgrade stepByStep({
|
||||
from7To8: from7To8,
|
||||
from8To9: from8To9,
|
||||
from9To10: from9To10,
|
||||
from10To11: from10To11,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -72,17 +72,33 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
final deleteSmt = _db.localAssetEntity.delete();
|
||||
deleteSmt.where((localAsset) {
|
||||
final subQuery = _db.localAlbumAssetEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||
..join([innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id))]);
|
||||
subQuery.where(
|
||||
_db.localAlbumEntity.id.equals(albumId) & _db.localAlbumAssetEntity.assetId.isNotIn(assetIdsToKeep),
|
||||
);
|
||||
return localAsset.id.isInQuery(subQuery);
|
||||
return _db.transaction(() async {
|
||||
await _db.managers.localAlbumAssetEntity
|
||||
.filter((row) => row.albumId.id.equals(albumId))
|
||||
.update((album) => album(marker_: const Value(true)));
|
||||
|
||||
await _db.batch((batch) {
|
||||
for (final assetId in assetIdsToKeep) {
|
||||
batch.update(
|
||||
_db.localAlbumAssetEntity,
|
||||
const LocalAlbumAssetEntityCompanion(marker_: Value(null)),
|
||||
where: (row) => row.assetId.equals(assetId) & row.albumId.equals(albumId),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
final query = _db.localAssetEntity.delete()
|
||||
..where(
|
||||
(row) => row.id.isInQuery(
|
||||
_db.localAlbumAssetEntity.selectOnly()
|
||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||
..where(
|
||||
_db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAlbumAssetEntity.marker_.isNotNull(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await query.go();
|
||||
});
|
||||
await deleteSmt.go();
|
||||
}
|
||||
|
||||
Future<void> upsert(
|
||||
@@ -198,10 +214,9 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
// List<String>
|
||||
await _db.batch((batch) async {
|
||||
assetAlbums.cast<String, List<Object?>>().forEach((assetId, albumIds) {
|
||||
batch.deleteWhere(
|
||||
_db.localAlbumAssetEntity,
|
||||
(f) => f.albumId.isNotIn(albumIds.cast<String?>().nonNulls) & f.assetId.equals(assetId),
|
||||
);
|
||||
for (final albumId in albumIds.cast<String?>().nonNulls) {
|
||||
batch.deleteWhere(_db.localAlbumAssetEntity, (f) => f.albumId.equals(albumId) & f.assetId.equals(assetId));
|
||||
}
|
||||
});
|
||||
});
|
||||
await _db.batch((batch) async {
|
||||
@@ -288,12 +303,14 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
|
||||
return transaction(() async {
|
||||
if (assetsToUnLink.isNotEmpty) {
|
||||
await _db.batch(
|
||||
(batch) => batch.deleteWhere(
|
||||
_db.localAlbumAssetEntity,
|
||||
(f) => f.assetId.isIn(assetsToUnLink) & f.albumId.equals(albumId),
|
||||
),
|
||||
);
|
||||
await _db.batch((batch) {
|
||||
for (final assetId in assetsToUnLink) {
|
||||
batch.deleteWhere(
|
||||
_db.localAlbumAssetEntity,
|
||||
(row) => row.assetId.equals(assetId) & row.albumId.equals(albumId),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await _deleteAssets(assetsToDelete);
|
||||
@@ -320,7 +337,9 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
|
||||
return _db.batch((batch) {
|
||||
batch.deleteWhere(_db.localAssetEntity, (f) => f.id.isIn(ids));
|
||||
for (final id in ids) {
|
||||
batch.deleteWhere(_db.localAssetEntity, (row) => row.id.equals(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -342,15 +361,13 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||
return _db.managers.localAlbumEntity.count();
|
||||
}
|
||||
|
||||
Future unlinkRemoteAlbum(String id) async {
|
||||
return _db.localAlbumEntity.update()
|
||||
..where((row) => row.id.equals(id))
|
||||
..write(const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null)));
|
||||
Future<void> unlinkRemoteAlbum(String id) async {
|
||||
final query = _db.localAlbumEntity.update()..where((row) => row.id.equals(id));
|
||||
await query.write(const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null)));
|
||||
}
|
||||
|
||||
Future linkRemoteAlbum(String localAlbumId, String remoteAlbumId) async {
|
||||
return _db.localAlbumEntity.update()
|
||||
..where((row) => row.id.equals(localAlbumId))
|
||||
..write(LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(remoteAlbumId)));
|
||||
Future<void> linkRemoteAlbum(String localAlbumId, String remoteAlbumId) async {
|
||||
final query = _db.localAlbumEntity.update()..where((row) => row.id.equals(localAlbumId));
|
||||
await query.write(LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(remoteAlbumId)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
@@ -58,8 +57,8 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
|
||||
return _db.batch((batch) {
|
||||
for (final slice in ids.slices(32000)) {
|
||||
batch.deleteWhere(_db.localAssetEntity, (e) => e.id.isIn(slice));
|
||||
for (final id in ids) {
|
||||
batch.deleteWhere(_db.localAssetEntity, (e) => e.id.equals(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -166,8 +166,15 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> removeAssets(String albumId, List<String> assetIds) {
|
||||
return _db.remoteAlbumAssetEntity.deleteWhere((tbl) => tbl.albumId.equals(albumId) & tbl.assetId.isIn(assetIds));
|
||||
Future<void> removeAssets(String albumId, List<String> assetIds) {
|
||||
return _db.batch((batch) {
|
||||
for (final assetId in assetIds) {
|
||||
batch.deleteWhere(
|
||||
_db.remoteAlbumAssetEntity,
|
||||
(row) => row.albumId.equals(albumId) & row.assetId.equals(assetId),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FutureOr<(DateTime, DateTime)> getDateRange(String albumId) {
|
||||
|
||||
@@ -160,7 +160,11 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
|
||||
Future<void> delete(List<String> ids) {
|
||||
return _db.remoteAssetEntity.deleteWhere((row) => row.id.isIn(ids));
|
||||
return _db.batch((batch) {
|
||||
for (final id in ids) {
|
||||
batch.deleteWhere(_db.remoteAssetEntity, (row) => row.id.equals(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateLocation(List<String> ids, LatLng location) {
|
||||
@@ -199,7 +203,11 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
.map((row) => row.id)
|
||||
.get();
|
||||
|
||||
await _db.stackEntity.deleteWhere((row) => row.id.isIn(stackIds));
|
||||
await _db.batch((batch) {
|
||||
for (final stackId in stackIds) {
|
||||
batch.deleteWhere(_db.stackEntity, (row) => row.id.equals(stackId));
|
||||
}
|
||||
});
|
||||
|
||||
await _db.batch((batch) {
|
||||
final companion = StackEntityCompanion(ownerId: Value(userId), primaryAssetId: Value(stack.primaryAssetId));
|
||||
@@ -219,15 +227,21 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
||||
|
||||
Future<void> unStack(List<String> stackIds) {
|
||||
return _db.transaction(() async {
|
||||
await _db.stackEntity.deleteWhere((row) => row.id.isIn(stackIds));
|
||||
await _db.batch((batch) {
|
||||
for (final stackId in stackIds) {
|
||||
batch.deleteWhere(_db.stackEntity, (row) => row.id.equals(stackId));
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: delete this after adding foreign key on stackId
|
||||
await _db.batch((batch) {
|
||||
batch.update(
|
||||
_db.remoteAssetEntity,
|
||||
const RemoteAssetEntityCompanion(stackId: Value(null)),
|
||||
where: (e) => e.stackId.isIn(stackIds),
|
||||
);
|
||||
for (final stackId in stackIds) {
|
||||
batch.update(
|
||||
_db.remoteAssetEntity,
|
||||
const RemoteAssetEntityCompanion(stackId: Value(null)),
|
||||
where: (e) => e.stackId.equals(stackId),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,7 +93,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
|
||||
Future<void> deleteUsersV1(Iterable<SyncUserDeleteV1> data) async {
|
||||
try {
|
||||
await _db.userEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.userId)));
|
||||
await _db.batch((batch) {
|
||||
for (final user in data) {
|
||||
batch.deleteWhere(_db.userEntity, (row) => row.id.equals(user.userId));
|
||||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: SyncUserDeleteV1', error, stack);
|
||||
rethrow;
|
||||
@@ -158,7 +162,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
|
||||
Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data, {String debugLabel = 'user'}) async {
|
||||
try {
|
||||
await _db.remoteAssetEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.assetId)));
|
||||
await _db.batch((batch) {
|
||||
for (final asset in data) {
|
||||
batch.deleteWhere(_db.remoteAssetEntity, (row) => row.id.equals(asset.assetId));
|
||||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteAssetsV1 - $debugLabel', error, stack);
|
||||
rethrow;
|
||||
@@ -243,7 +251,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
|
||||
Future<void> deleteAlbumsV1(Iterable<SyncAlbumDeleteV1> data) async {
|
||||
try {
|
||||
await _db.remoteAlbumEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.albumId)));
|
||||
await _db.batch((batch) {
|
||||
for (final album in data) {
|
||||
batch.deleteWhere(_db.remoteAlbumEntity, (row) => row.id.equals(album.albumId));
|
||||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteAlbumsV1', error, stack);
|
||||
rethrow;
|
||||
@@ -379,7 +391,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
|
||||
Future<void> deleteMemoriesV1(Iterable<SyncMemoryDeleteV1> data) async {
|
||||
try {
|
||||
await _db.memoryEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.memoryId)));
|
||||
await _db.batch((batch) {
|
||||
for (final memory in data) {
|
||||
batch.deleteWhere(_db.memoryEntity, (row) => row.id.equals(memory.memoryId));
|
||||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteMemoriesV1', error, stack);
|
||||
rethrow;
|
||||
@@ -443,7 +459,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
|
||||
Future<void> deleteStacksV1(Iterable<SyncStackDeleteV1> data, {String debugLabel = 'user'}) async {
|
||||
try {
|
||||
await _db.stackEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.stackId)));
|
||||
await _db.batch((batch) {
|
||||
for (final stack in data) {
|
||||
batch.deleteWhere(_db.stackEntity, (row) => row.id.equals(stack.stackId));
|
||||
}
|
||||
});
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Error: deleteStacksV1 - $debugLabel', error, stack);
|
||||
rethrow;
|
||||
|
||||
@@ -153,7 +153,7 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// Draw the app from edge to edge
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge));
|
||||
|
||||
// Sets the navigation bar color
|
||||
SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle(systemNavigationBarColor: Colors.transparent);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -51,7 +53,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
||||
final isSuccess = await ref.read(albumProvider.notifier).leaveAlbum(album);
|
||||
|
||||
if (isSuccess) {
|
||||
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
|
||||
unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])));
|
||||
} else {
|
||||
showErrorMessage();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -29,8 +31,8 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
|
||||
if (newAlbum != null) {
|
||||
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
|
||||
context.maybePop(true);
|
||||
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
|
||||
unawaited(context.maybePop(true));
|
||||
unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])));
|
||||
}
|
||||
|
||||
ScaffoldMessenger(
|
||||
@@ -109,8 +111,8 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
centerTitle: false,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
onPressed: () async {
|
||||
context.maybePop();
|
||||
onPressed: () {
|
||||
unawaited(context.maybePop());
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
|
||||
@@ -155,7 +155,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
// waited until returning from selection
|
||||
await ref.read(backupProvider.notifier).backupAlbumSelectionDone();
|
||||
// waited until backup albums are stored in DB
|
||||
ref.read(albumProvider.notifier).refreshDeviceAlbums();
|
||||
await ref.read(albumProvider.notifier).refreshDeviceAlbums();
|
||||
},
|
||||
child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.w
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
|
||||
@@ -33,7 +34,14 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||
await ref.read(backgroundSyncProvider).syncRemote();
|
||||
|
||||
if (mounted) {
|
||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -44,7 +52,6 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
||||
.toList();
|
||||
|
||||
final backupNotifier = ref.read(driftBackupProvider.notifier);
|
||||
final backgroundManager = ref.read(backgroundSyncProvider);
|
||||
|
||||
Future<void> startBackup() async {
|
||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||
@@ -52,7 +59,6 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
await backgroundManager.syncRemote();
|
||||
await backupNotifier.getBackupStatus(currentUser.id);
|
||||
await backupNotifier.startBackup(currentUser.id);
|
||||
}
|
||||
@@ -205,7 +211,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
if (currentUser == null) {
|
||||
return;
|
||||
}
|
||||
ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||
unawaited(ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id));
|
||||
},
|
||||
child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
@@ -235,11 +241,13 @@ class _BackupCard extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final backupCount = ref.watch(driftBackupProvider.select((p) => p.backupCount));
|
||||
final syncStatus = ref.watch(syncStatusProvider);
|
||||
|
||||
return BackupInfoCard(
|
||||
title: "backup_controller_page_backup".tr(),
|
||||
subtitle: "backup_controller_page_backup_sub".tr(),
|
||||
info: backupCount.toString(),
|
||||
isLoading: syncStatus.isRemoteSyncing,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -250,10 +258,13 @@ class _RemainderCard extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final remainderCount = ref.watch(driftBackupProvider.select((p) => p.remainderCount));
|
||||
final syncStatus = ref.watch(syncStatusProvider);
|
||||
|
||||
return BackupInfoCard(
|
||||
title: "backup_controller_page_remainder".tr(),
|
||||
subtitle: "backup_controller_page_remainder_sub".tr(),
|
||||
info: remainderCount.toString(),
|
||||
isLoading: syncStatus.isRemoteSyncing,
|
||||
onTap: () => context.pushRoute(const DriftBackupAssetDetailRoute()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -54,9 +56,11 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
||||
);
|
||||
|
||||
final backupNotifier = ref.read(driftBackupProvider.notifier);
|
||||
backupNotifier.cancel().then((_) {
|
||||
backupNotifier.startBackup(currentUser.id);
|
||||
});
|
||||
unawaited(
|
||||
backupNotifier.cancel().then((_) {
|
||||
backupNotifier.startBackup(currentUser.id);
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.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/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
@@ -163,8 +163,8 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showFileDetailDialog(BuildContext context, DriftUploadStatus item) async {
|
||||
showDialog(
|
||||
Future<void> _showFileDetailDialog(BuildContext context, DriftUploadStatus item) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => FileDetailDialog(uploadStatus: item),
|
||||
);
|
||||
|
||||
@@ -33,7 +33,7 @@ class ActivitiesPage extends HookConsumerWidget {
|
||||
Future<void> onAddComment(String comment) async {
|
||||
await activityNotifier.addComment(comment);
|
||||
// Scroll to the end of the list to show the newly added activity
|
||||
listViewScrollController.animateTo(
|
||||
await listViewScrollController.animateTo(
|
||||
listViewScrollController.position.maxScrollExtent + 200,
|
||||
duration: const Duration(milliseconds: 600),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -170,11 +172,11 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
.createAlbum(ref.read(albumTitleProvider), selectedAssets.value);
|
||||
|
||||
if (newAlbum != null) {
|
||||
ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
||||
await ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
||||
selectedAssets.value = {};
|
||||
ref.read(albumTitleProvider.notifier).clearAlbumTitle();
|
||||
ref.read(albumViewerProvider.notifier).disableEditAlbum();
|
||||
context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id));
|
||||
unawaited(context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
} catch (e) {
|
||||
// swallow error silently
|
||||
log.severe('Error precaching next image: $e');
|
||||
context.maybePop();
|
||||
await context.maybePop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -279,11 +279,13 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||
nc.onPlaybackReady.addListener(onPlaybackReady);
|
||||
nc.onPlaybackEnded.addListener(onPlaybackEnded);
|
||||
|
||||
nc.loadVideoSource(source).catchError((error) {
|
||||
log.severe('Error loading video source: $error');
|
||||
});
|
||||
unawaited(
|
||||
nc.loadVideoSource(source).catchError((error) {
|
||||
log.severe('Error loading video source: $error');
|
||||
}),
|
||||
);
|
||||
final loopVideo = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.loopVideo);
|
||||
nc.setLoop(loopVideo);
|
||||
unawaited(nc.setLoop(loopVideo));
|
||||
|
||||
controller.value = nc;
|
||||
Timer(const Duration(milliseconds: 200), checkIfBuffering);
|
||||
@@ -354,12 +356,12 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||
|
||||
useOnAppLifecycleStateChange((_, state) async {
|
||||
if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) {
|
||||
controller.value?.play();
|
||||
await controller.value?.play();
|
||||
} else if (state == AppLifecycleState.paused) {
|
||||
final videoPlaying = await controller.value?.isPlaying();
|
||||
if (videoPlaying ?? true) {
|
||||
shouldPlayOnForeground.value = true;
|
||||
controller.value?.pause();
|
||||
await controller.value?.pause();
|
||||
} else {
|
||||
shouldPlayOnForeground.value = false;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -53,39 +55,41 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
final backgroundManager = ref.read(backgroundSyncProvider);
|
||||
final backupProvider = ref.read(driftBackupProvider.notifier);
|
||||
|
||||
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
||||
(_) async {
|
||||
try {
|
||||
wsProvider.connect();
|
||||
infoProvider.getServerInfo();
|
||||
unawaited(
|
||||
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
||||
(_) async {
|
||||
try {
|
||||
wsProvider.connect();
|
||||
unawaited(infoProvider.getServerInfo());
|
||||
|
||||
if (Store.isBetaTimelineEnabled) {
|
||||
await Future.wait([backgroundManager.syncLocal(), backgroundManager.syncRemote()]);
|
||||
await Future.wait([
|
||||
backgroundManager.hashAssets().then((_) {
|
||||
_resumeBackup(backupProvider);
|
||||
}),
|
||||
_resumeBackup(backupProvider),
|
||||
]);
|
||||
if (Store.isBetaTimelineEnabled) {
|
||||
await Future.wait([backgroundManager.syncLocal(), backgroundManager.syncRemote()]);
|
||||
await Future.wait([
|
||||
backgroundManager.hashAssets().then((_) {
|
||||
_resumeBackup(backupProvider);
|
||||
}),
|
||||
_resumeBackup(backupProvider),
|
||||
]);
|
||||
|
||||
if (Store.get(StoreKey.syncAlbums, false)) {
|
||||
await backgroundManager.syncLinkedAlbum();
|
||||
if (Store.get(StoreKey.syncAlbums, false)) {
|
||||
await backgroundManager.syncLinkedAlbum();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log.severe('Failed establishing connection to the server: $e');
|
||||
}
|
||||
} catch (e) {
|
||||
log.severe('Failed establishing connection to the server: $e');
|
||||
}
|
||||
},
|
||||
onError: (exception) => {
|
||||
log.severe('Failed to update auth info with access token: $accessToken'),
|
||||
ref.read(authProvider.notifier).logout(),
|
||||
context.replaceRoute(const LoginRoute()),
|
||||
},
|
||||
},
|
||||
onError: (exception) => {
|
||||
log.severe('Failed to update auth info with access token: $accessToken'),
|
||||
ref.read(authProvider.notifier).logout(),
|
||||
context.replaceRoute(const LoginRoute()),
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log.severe('Missing crucial offline login info - Logging out completely');
|
||||
ref.read(authProvider.notifier).logout();
|
||||
context.replaceRoute(const LoginRoute());
|
||||
unawaited(ref.read(authProvider.notifier).logout());
|
||||
unawaited(context.replaceRoute(const LoginRoute()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,11 +99,11 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
final needBetaMigration = Store.get(StoreKey.needBetaMigration, false);
|
||||
if (needBetaMigration) {
|
||||
await Store.put(StoreKey.needBetaMigration, false);
|
||||
context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)]);
|
||||
unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)]));
|
||||
return;
|
||||
}
|
||||
|
||||
context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute());
|
||||
unawaited(context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute()));
|
||||
}
|
||||
|
||||
if (Store.isBetaTimelineEnabled) {
|
||||
@@ -109,7 +113,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
final hasPermission = await ref.read(galleryPermissionNotifier.notifier).hasPermission;
|
||||
if (hasPermission) {
|
||||
// Resume backup (if enable) then navigate
|
||||
ref.watch(backupProvider.notifier).resumeBackup();
|
||||
unawaited(ref.watch(backupProvider.notifier).resumeBackup());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +123,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
if (isEnableBackup) {
|
||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||
if (currentUser != null) {
|
||||
notifier.handleBackupResume(currentUser.id);
|
||||
unawaited(notifier.handleBackupResume(currentUser.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:crop_image/crop_image.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/utils/hooks/crop_controller_hook.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
|
||||
import 'edit.page.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
|
||||
/// A widget for cropping an image.
|
||||
/// This widget uses [HookWidget] to manage its lifecycle and state. It allows
|
||||
@@ -35,7 +38,7 @@ class CropImagePage extends HookWidget {
|
||||
icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24),
|
||||
onPressed: () async {
|
||||
final croppedImage = await cropController.croppedImage();
|
||||
context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true));
|
||||
unawaited(context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true)));
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/constants/filters.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
/// A widget for filtering an image.
|
||||
@@ -74,7 +75,7 @@ class FilterImagePage extends HookWidget {
|
||||
icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24),
|
||||
onPressed: () async {
|
||||
final filteredImage = await applyFilterAndConvert(colorFilter.value);
|
||||
context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true));
|
||||
unawaited(context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true)));
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart' show useState;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/local_auth.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/forms/pin_registration_form.dart';
|
||||
import 'package:immich_mobile/widgets/forms/pin_verification_form.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
|
||||
@RoutePage()
|
||||
class PinAuthPage extends HookConsumerWidget {
|
||||
@@ -35,9 +37,9 @@ class PinAuthPage extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
if (isBetaTimeline) {
|
||||
context.replaceRoute(const DriftLockedFolderRoute());
|
||||
unawaited(context.replaceRoute(const DriftLockedFolderRoute()));
|
||||
} else {
|
||||
context.replaceRoute(const LockedRoute());
|
||||
unawaited(context.replaceRoute(const LockedRoute()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
changeExpiry: changeExpiry,
|
||||
);
|
||||
ref.invalidate(sharedLinksStateProvider);
|
||||
context.maybePop();
|
||||
await context.maybePop();
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
|
||||
@@ -82,10 +82,12 @@ class PhotosPage extends HookConsumerWidget {
|
||||
final fullRefresh = refreshCount.value > 0;
|
||||
|
||||
if (fullRefresh) {
|
||||
Future.wait([
|
||||
ref.read(assetProvider.notifier).getAllAsset(clear: true),
|
||||
ref.read(albumProvider.notifier).refreshRemoteAlbums(),
|
||||
]);
|
||||
unawaited(
|
||||
Future.wait([
|
||||
ref.read(assetProvider.notifier).getAllAsset(clear: true),
|
||||
ref.read(albumProvider.notifier).refreshRemoteAlbums(),
|
||||
]),
|
||||
);
|
||||
|
||||
// refresh was forced: user requested another refresh within 2 seconds
|
||||
refreshCount.value = 0;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
@@ -83,7 +84,7 @@ class MapPage extends HookConsumerWidget {
|
||||
isLoading.value = true;
|
||||
markers.value = await ref.read(mapMarkersProvider.future);
|
||||
assetsDebouncer.run(updateAssetsInBounds);
|
||||
reloadLayers();
|
||||
await reloadLayers();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
@@ -128,7 +129,7 @@ class MapPage extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
if (marker != null) {
|
||||
updateAssetMarkerPosition(marker);
|
||||
await updateAssetMarkerPosition(marker);
|
||||
} else {
|
||||
// If no asset was previously selected and no new asset is available, close the bottom sheet
|
||||
if (selectedMarker.value == null) {
|
||||
@@ -165,7 +166,7 @@ class MapPage extends HookConsumerWidget {
|
||||
if (asset.isVideo) {
|
||||
ref.read(showControlsProvider.notifier).show = false;
|
||||
}
|
||||
context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList));
|
||||
unawaited(context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList)));
|
||||
}
|
||||
|
||||
/// BOTTOM SHEET CALLBACKS
|
||||
@@ -209,7 +210,7 @@ class MapPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
if (mapController.value != null && location != null) {
|
||||
mapController.value!.animateCamera(
|
||||
await mapController.value!.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(LatLng(location.latitude, location.longitude), mapZoomToAssetLevel),
|
||||
duration: const Duration(milliseconds: 800),
|
||||
);
|
||||
|
||||
@@ -8,9 +8,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
|
||||
import 'package:immich_mobile/utils/map_utils.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_theme_override.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
import 'package:immich_mobile/utils/map_utils.dart';
|
||||
|
||||
@RoutePage()
|
||||
class MapLocationPickerPage extends HookConsumerWidget {
|
||||
@@ -30,7 +30,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
|
||||
|
||||
Future<void> onMapClick(Point<num> point, LatLng centre) async {
|
||||
selectedLatLng.value = centre;
|
||||
controller.value?.animateCamera(CameraUpdate.newLatLng(centre));
|
||||
await controller.value?.animateCamera(CameraUpdate.newLatLng(centre));
|
||||
if (marker.value != null) {
|
||||
await controller.value?.updateSymbol(marker.value!, SymbolOptions(geometry: centre));
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
|
||||
|
||||
var currentLatLng = LatLng(currentLocation.latitude, currentLocation.longitude);
|
||||
selectedLatLng.value = currentLatLng;
|
||||
controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng));
|
||||
await controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng));
|
||||
}
|
||||
|
||||
return MapThemeOverride(
|
||||
|
||||
@@ -266,7 +266,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
filter.value = filter.value.copyWith(date: SearchDateFilter());
|
||||
|
||||
dateRangeCurrentFilterWidget.value = null;
|
||||
search();
|
||||
unawaited(search());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
search();
|
||||
unawaited(search());
|
||||
}
|
||||
|
||||
// MEDIA PICKER
|
||||
|
||||
57
mobile/lib/presentation/pages/download_info.page.dart
Normal file
57
mobile/lib/presentation/pages/download_info.page.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/pages/common/download_panel.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DownloadInfoPage extends ConsumerWidget {
|
||||
const DownloadInfoPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList();
|
||||
|
||||
onCancelDownload(String id) {
|
||||
ref.watch(downloadStateProvider.notifier).cancelDownload(id);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("download".t(context: context)),
|
||||
actions: [],
|
||||
),
|
||||
body: ListView.builder(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: tasks.length,
|
||||
itemBuilder: (context, index) {
|
||||
final task = tasks[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
child: DownloadTaskTile(
|
||||
progress: task.value.progress,
|
||||
fileName: task.value.fileName,
|
||||
status: task.value.status,
|
||||
onCancelDownload: () => onCancelDownload(task.key),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
persistentFooterButtons: [
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
tasks.map((e) => e.key).forEach(onCancelDownload);
|
||||
},
|
||||
style: OutlinedButton.styleFrom(side: BorderSide(color: context.colorScheme.primary)),
|
||||
child: Text(
|
||||
'clear_all'.t(context: context),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -47,7 +49,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget {
|
||||
void leaveAlbum() async {
|
||||
try {
|
||||
await ref.read(remoteAlbumProvider.notifier).leaveAlbum(album.id, userId: userId);
|
||||
context.navigateTo(const DriftAlbumsRoute());
|
||||
unawaited(context.navigateTo(const DriftAlbumsRoute()));
|
||||
} catch (_) {
|
||||
showErrorMessage();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -179,7 +181,7 @@ class _DriftCreateAlbumPageState extends ConsumerState<DriftCreateAlbumPage> {
|
||||
|
||||
if (album != null) {
|
||||
ref.read(currentRemoteAlbumProvider.notifier).setAlbum(album);
|
||||
context.replaceRoute(RemoteAlbumRoute(album: album));
|
||||
unawaited(context.replaceRoute(RemoteAlbumRoute(album: album)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -139,7 +141,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
toastType: ToastType.success,
|
||||
);
|
||||
|
||||
context.pushRoute(const DriftAlbumsRoute());
|
||||
unawaited(context.pushRoute(const DriftAlbumsRoute()));
|
||||
} catch (e) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
@@ -161,12 +163,12 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
setState(() {
|
||||
_album = _album.copyWith(name: result.name, description: result.description ?? '');
|
||||
});
|
||||
HapticFeedback.mediumImpact();
|
||||
unawaited(HapticFeedback.mediumImpact());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showActivity(BuildContext context) async {
|
||||
context.pushRoute(const DriftActivitiesRoute());
|
||||
unawaited(context.pushRoute(const DriftActivitiesRoute()));
|
||||
}
|
||||
|
||||
void showOptionSheet(BuildContext context) {
|
||||
@@ -207,7 +209,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
},
|
||||
onCreateSharedLink: () async {
|
||||
context.pop();
|
||||
context.pushRoute(SharedLinkEditRoute(albumId: _album.id));
|
||||
unawaited(context.pushRoute(SharedLinkEditRoute(albumId: _album.id)));
|
||||
},
|
||||
onShowOptions: () {
|
||||
context.pop();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:crop_image/crop_image.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -34,7 +36,7 @@ class DriftCropImagePage extends HookWidget {
|
||||
icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24),
|
||||
onPressed: () async {
|
||||
final croppedImage = await cropController.croppedImage();
|
||||
context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true));
|
||||
unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true)));
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -65,7 +65,7 @@ class DriftEditImagePage extends ConsumerWidget {
|
||||
Logger("SaveEditedImage").warning("Failed to retrieve the saved image back from OS", e);
|
||||
}
|
||||
|
||||
ref.read(backgroundSyncProvider).syncLocal(full: true);
|
||||
unawaited(ref.read(backgroundSyncProvider).syncLocal(full: true));
|
||||
context.navigator.popUntil((route) => route.isFirst);
|
||||
ImmichToast.show(durationInSecond: 3, context: context, msg: 'Image Saved!');
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ class DriftFilterImagePage extends HookWidget {
|
||||
icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24),
|
||||
onPressed: () async {
|
||||
final filteredImage = await applyFilterAndConvert(colorFilter.value);
|
||||
context.pushRoute(DriftEditImageRoute(asset: asset, image: filteredImage, isEdited: true));
|
||||
unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: filteredImage, isEdited: true)));
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -270,7 +270,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
||||
filter.value = filter.value.copyWith(date: SearchDateFilter());
|
||||
|
||||
dateRangeCurrentFilterWidget.value = null;
|
||||
search();
|
||||
unawaited(search());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
search();
|
||||
unawaited(search());
|
||||
}
|
||||
|
||||
// MEDIA PICKER
|
||||
@@ -633,7 +633,7 @@ class _SearchResultGrid extends ConsumerWidget {
|
||||
groupBy: GroupAssetsBy.none,
|
||||
appBar: null,
|
||||
bottomSheet: const GeneralBottomSheet(minChildSize: 0.20),
|
||||
withScrubber: false,
|
||||
snapToMonth: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
@@ -15,7 +17,7 @@ class AdvancedInfoActionButton extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(actionProvider.notifier).troubleshoot(source, context);
|
||||
unawaited(ref.read(actionProvider.notifier).troubleshoot(source, context));
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,54 +1,45 @@
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class DownloadActionButton extends ConsumerWidget {
|
||||
final ActionSource source;
|
||||
final bool menuItem;
|
||||
const DownloadActionButton({super.key, required this.source, this.menuItem = false});
|
||||
|
||||
const DownloadActionButton({super.key, required this.source});
|
||||
|
||||
void _onTap(BuildContext context, WidgetRef ref) async {
|
||||
void _onTap(BuildContext context, WidgetRef ref, BackgroundSyncManager backgroundSyncManager) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).downloadAll(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
try {
|
||||
await ref.read(actionProvider.notifier).downloadAll(source);
|
||||
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
} else if (result.count > 0) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'download_action_prompt'.t(context: context, args: {'count': result.count.toString()}),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.success,
|
||||
);
|
||||
Future.delayed(const Duration(seconds: 1), () async {
|
||||
await backgroundSyncManager.syncLocal();
|
||||
await backgroundSyncManager.hashAssets();
|
||||
});
|
||||
} finally {
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final backgroundManager = ref.watch(backgroundSyncProvider);
|
||||
|
||||
return BaseActionButton(
|
||||
iconData: Icons.download,
|
||||
maxWidth: 95,
|
||||
label: "download".t(context: context),
|
||||
onPressed: () => _onTap(context, ref),
|
||||
menuItem: menuItem,
|
||||
onPressed: () => _onTap(context, ref, backgroundManager),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class DownloadStatusFloatingButton extends ConsumerWidget {
|
||||
const DownloadStatusFloatingButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final shouldShow = ref.watch(downloadStateProvider.select((state) => state.showProgress));
|
||||
final itemCount = ref.watch(downloadStateProvider.select((state) => state.taskProgress.length));
|
||||
final isDownloading = ref
|
||||
.watch(downloadStateProvider.select((state) => state.taskProgress))
|
||||
.values
|
||||
.where((element) => element.progress != 1)
|
||||
.isNotEmpty;
|
||||
|
||||
return shouldShow
|
||||
? Badge.count(
|
||||
count: itemCount,
|
||||
textColor: context.colorScheme.onPrimary,
|
||||
backgroundColor: context.colorScheme.primary,
|
||||
child: FloatingActionButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
side: BorderSide(color: context.colorScheme.outlineVariant, width: 1),
|
||||
),
|
||||
backgroundColor: context.isDarkTheme
|
||||
? context.colorScheme.surfaceContainer
|
||||
: context.colorScheme.surfaceBright,
|
||||
elevation: 2,
|
||||
onPressed: () {
|
||||
context.pushRoute(const DownloadInfoRoute());
|
||||
},
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [
|
||||
isDownloading
|
||||
? Icon(Icons.downloading_rounded, color: context.colorScheme.primary, size: 28)
|
||||
: Icon(
|
||||
Icons.download_done,
|
||||
color: context.isDarkTheme ? Colors.green[200] : Colors.green[400],
|
||||
size: 28,
|
||||
),
|
||||
if (isDownloading)
|
||||
const SizedBox(
|
||||
height: 31,
|
||||
width: 31,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
backgroundColor: Colors.transparent,
|
||||
value: null, // Indeterminate progress
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class ShareActionButton extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
showDialog(
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext buildContext) {
|
||||
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
|
||||
// so shownAlbums gets updated
|
||||
filterAlbums();
|
||||
unawaited(filterAlbums());
|
||||
}
|
||||
|
||||
Future<void> filterAlbums() async {
|
||||
@@ -711,7 +711,7 @@ class AddToAlbumHeader extends ConsumerWidget {
|
||||
|
||||
ref.read(currentRemoteAlbumProvider.notifier).setAlbum(newAlbum);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
context.pushRoute(RemoteAlbumRoute(album: newAlbum));
|
||||
unawaited(context.pushRoute(RemoteAlbumRoute(album: newAlbum)));
|
||||
}
|
||||
|
||||
return SliverPadding(
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||
@@ -633,9 +634,9 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
// Listen for control visibility changes and change system UI mode accordingly
|
||||
ref.listen(assetViewerProvider.select((value) => value.showingControls), (_, showingControls) async {
|
||||
if (showingControls) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge));
|
||||
} else {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||
unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -649,20 +650,25 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
appBar: const ViewerTopAppBar(),
|
||||
extendBody: true,
|
||||
extendBodyBehindAppBar: true,
|
||||
body: PhotoViewGallery.builder(
|
||||
gaplessPlayback: true,
|
||||
loadingBuilder: _placeholderBuilder,
|
||||
pageController: pageController,
|
||||
scrollPhysics: CurrentPlatform.isIOS
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics(), // Use heavy physics for Android
|
||||
itemCount: totalAssets,
|
||||
onPageChanged: _onPageChanged,
|
||||
onPageBuild: _onPageBuild,
|
||||
scaleStateChangedCallback: _onScaleStateChanged,
|
||||
builder: _assetBuilder,
|
||||
backgroundDecoration: BoxDecoration(color: backgroundColor),
|
||||
enablePanAlways: true,
|
||||
floatingActionButton: const DownloadStatusFloatingButton(),
|
||||
body: Stack(
|
||||
children: [
|
||||
PhotoViewGallery.builder(
|
||||
gaplessPlayback: true,
|
||||
loadingBuilder: _placeholderBuilder,
|
||||
pageController: pageController,
|
||||
scrollPhysics: CurrentPlatform.isIOS
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics(), // Use heavy physics for Android
|
||||
itemCount: totalAssets,
|
||||
onPageChanged: _onPageChanged,
|
||||
onPageBuild: _onPageBuild,
|
||||
scaleStateChangedCallback: _onScaleStateChanged,
|
||||
builder: _assetBuilder,
|
||||
backgroundDecoration: BoxDecoration(color: backgroundColor),
|
||||
enablePanAlways: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: showingBottomSheet
|
||||
? const SizedBox.shrink()
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
|
||||
@@ -56,6 +57,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||
|
||||
final actions = <Widget>[
|
||||
if (asset.hasRemote) const DownloadActionButton(source: ActionSource.viewer, menuItem: true),
|
||||
if (isCasting || (asset.hasRemote)) const CastActionButton(menuItem: true),
|
||||
if (album != null && album.isActivityEnabled && album.isShared)
|
||||
IconButton(
|
||||
|
||||
@@ -298,11 +298,13 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
nc.onPlaybackReady.addListener(onPlaybackReady);
|
||||
nc.onPlaybackEnded.addListener(onPlaybackEnded);
|
||||
|
||||
nc.loadVideoSource(source).catchError((error) {
|
||||
log.severe('Error loading video source: $error');
|
||||
});
|
||||
unawaited(
|
||||
nc.loadVideoSource(source).catchError((error) {
|
||||
log.severe('Error loading video source: $error');
|
||||
}),
|
||||
);
|
||||
final loopVideo = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.loopVideo);
|
||||
nc.setLoop(!asset.isMotionPhoto && loopVideo);
|
||||
unawaited(nc.setLoop(!asset.isMotionPhoto && loopVideo));
|
||||
|
||||
controller.value = nc;
|
||||
Timer(const Duration(milliseconds: 200), checkIfBuffering);
|
||||
@@ -373,12 +375,12 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
|
||||
useOnAppLifecycleStateChange((_, state) async {
|
||||
if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) {
|
||||
controller.value?.play();
|
||||
await controller.value?.play();
|
||||
} else if (state == AppLifecycleState.paused) {
|
||||
final videoPlaying = await controller.value?.isPlaying();
|
||||
if (videoPlaying ?? true) {
|
||||
shouldPlayOnForeground.value = true;
|
||||
controller.value?.pause();
|
||||
await controller.value?.pause();
|
||||
} else {
|
||||
shouldPlayOnForeground.value = false;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
@@ -51,14 +53,14 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
|
||||
Stream<ImageInfo> loadRequest(ImageRequest request, ImageDecoderCallback decode) async* {
|
||||
if (isCancelled) {
|
||||
this.request = null;
|
||||
evict();
|
||||
unawaited(evict());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final image = await request.load(decode);
|
||||
if (image == null || isCancelled) {
|
||||
evict();
|
||||
unawaited(evict());
|
||||
return;
|
||||
}
|
||||
yield image;
|
||||
|
||||
@@ -85,7 +85,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
||||
yield* initialImageStream();
|
||||
|
||||
if (isCancelled) {
|
||||
evict();
|
||||
unawaited(evict());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||
yield* initialImageStream();
|
||||
|
||||
if (isCancelled) {
|
||||
evict();
|
||||
unawaited(evict());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
||||
yield* loadRequest(request, decode);
|
||||
|
||||
if (isCancelled) {
|
||||
evict();
|
||||
unawaited(evict());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.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/presentation/widgets/map/map_utils.dart';
|
||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||
import 'package:immich_mobile/utils/debounce.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
@@ -114,12 +114,14 @@ class _DriftMapState extends ConsumerState<DriftMap> {
|
||||
}
|
||||
|
||||
final bounds = await controller.getVisibleRegion();
|
||||
_reloadMutex.run(() async {
|
||||
if (mounted && ref.read(mapStateProvider.notifier).setBounds(bounds)) {
|
||||
final markers = await ref.read(mapMarkerProvider(bounds).future);
|
||||
await reloadMarkers(markers);
|
||||
}
|
||||
});
|
||||
unawaited(
|
||||
_reloadMutex.run(() async {
|
||||
if (mounted && ref.read(mapStateProvider.notifier).setBounds(bounds)) {
|
||||
final markers = await ref.read(mapMarkerProvider(bounds).future);
|
||||
await reloadMarkers(markers);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reloadMarkers(Map<String, dynamic> markers) async {
|
||||
@@ -147,7 +149,7 @@ class _DriftMapState extends ConsumerState<DriftMap> {
|
||||
|
||||
final controller = mapController;
|
||||
if (controller != null && location != null) {
|
||||
controller.animateCamera(
|
||||
await controller.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(LatLng(location.latitude, location.longitude), MapUtils.mapZoomToAssetLevel),
|
||||
duration: const Duration(milliseconds: 800),
|
||||
);
|
||||
|
||||
@@ -73,7 +73,7 @@ class MapUtils {
|
||||
try {
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled && !silent) {
|
||||
showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context));
|
||||
unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context)));
|
||||
return (null, LocationPermission.deniedForever);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
@@ -15,8 +16,8 @@ import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
@@ -101,7 +102,6 @@ class _FixedSegmentRow extends ConsumerWidget {
|
||||
if (isScrubbing) {
|
||||
return _buildPlaceholder(context);
|
||||
}
|
||||
|
||||
if (timelineService.hasRange(assetIndex, assetCount)) {
|
||||
return _buildAssetRow(context, timelineService.getAssets(assetIndex, assetCount), timelineService);
|
||||
}
|
||||
@@ -157,11 +157,13 @@ class _AssetTileWidget extends ConsumerWidget {
|
||||
await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1);
|
||||
ref.read(isPlayingMotionVideoProvider.notifier).playing = false;
|
||||
AssetViewer.setAsset(ref, asset);
|
||||
ctx.pushRoute(
|
||||
AssetViewerRoute(
|
||||
initialIndex: assetIndex,
|
||||
timelineService: ref.read(timelineServiceProvider),
|
||||
heroOffset: heroOffset,
|
||||
unawaited(
|
||||
ctx.pushRoute(
|
||||
AssetViewerRoute(
|
||||
initialIndex: assetIndex,
|
||||
timelineService: ref.read(timelineServiceProvider),
|
||||
heroOffset: heroOffset,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||
import 'package:immich_mobile/utils/debounce.dart';
|
||||
import 'package:intl/intl.dart' hide TextDirection;
|
||||
|
||||
/// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged
|
||||
@@ -30,6 +31,11 @@ class Scrubber extends ConsumerStatefulWidget {
|
||||
|
||||
final double? monthSegmentSnappingOffset;
|
||||
|
||||
final bool snapToMonth;
|
||||
|
||||
/// Whether an app bar is present, affects coordinate calculations
|
||||
final bool hasAppBar;
|
||||
|
||||
Scrubber({
|
||||
super.key,
|
||||
Key? scrollThumbKey,
|
||||
@@ -38,6 +44,8 @@ class Scrubber extends ConsumerStatefulWidget {
|
||||
this.topPadding = 0,
|
||||
this.bottomPadding = 0,
|
||||
this.monthSegmentSnappingOffset,
|
||||
this.snapToMonth = true,
|
||||
this.hasAppBar = true,
|
||||
required this.child,
|
||||
}) : assert(child.scrollDirection == Axis.vertical);
|
||||
|
||||
@@ -81,6 +89,8 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
bool _isDragging = false;
|
||||
List<_Segment> _segments = [];
|
||||
int _monthCount = 0;
|
||||
DateTime? _currentScrubberDate;
|
||||
Debouncer? _scrubberDebouncer;
|
||||
|
||||
late AnimationController _thumbAnimationController;
|
||||
Timer? _fadeOutTimer;
|
||||
@@ -133,6 +143,7 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
_thumbAnimationController.dispose();
|
||||
_labelAnimationController.dispose();
|
||||
_fadeOutTimer?.cancel();
|
||||
_scrubberDebouncer?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -176,11 +187,25 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
return false;
|
||||
}
|
||||
|
||||
void _onDragStart(DragStartDetails _) {
|
||||
if (_monthCount >= kMinMonthsToEnableScrubberSnap) {
|
||||
void _onScrubberDateChanged(DateTime date) {
|
||||
if (_currentScrubberDate != date) {
|
||||
// Date changed, immediately set scrubbing to true
|
||||
_currentScrubberDate = date;
|
||||
ref.read(timelineStateProvider.notifier).setScrubbing(true);
|
||||
}
|
||||
|
||||
// Initialize debouncer if needed
|
||||
_scrubberDebouncer ??= Debouncer(interval: const Duration(milliseconds: 50));
|
||||
|
||||
// Debounce setting scrubbing to false
|
||||
_scrubberDebouncer!.run(() {
|
||||
if (_currentScrubberDate == date) {
|
||||
ref.read(timelineStateProvider.notifier).setScrubbing(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onDragStart(DragStartDetails _) {
|
||||
setState(() {
|
||||
_isDragging = true;
|
||||
_labelAnimationController.forward();
|
||||
@@ -206,10 +231,15 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
if (_lastLabel != label) {
|
||||
ref.read(hapticFeedbackProvider.notifier).selectionClick();
|
||||
_lastLabel = label;
|
||||
|
||||
// Notify timeline state of the new scrubber date position
|
||||
if (_monthCount >= kMinMonthsToEnableScrubberSnap) {
|
||||
_onScrubberDateChanged(nearestMonthSegment.date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_monthCount < kMinMonthsToEnableScrubberSnap) {
|
||||
if (_monthCount < kMinMonthsToEnableScrubberSnap || !widget.snapToMonth) {
|
||||
// If there are less than kMinMonthsToEnableScrubberSnap months, we don't need to snap to segments
|
||||
setState(() {
|
||||
_thumbTopOffset = dragPosition;
|
||||
@@ -236,14 +266,28 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
/// - If user drags to global Y position that's 100 pixels from the top
|
||||
/// - The relative position would be 100 - 50 = 50 (50 pixels into the scrubber area)
|
||||
double _calculateDragPosition(DragUpdateDetails details) {
|
||||
if (widget.hasAppBar) {
|
||||
final dragAreaTop = widget.topPadding;
|
||||
final dragAreaBottom = widget.timelineHeight - widget.bottomPadding;
|
||||
final dragAreaHeight = dragAreaBottom - dragAreaTop;
|
||||
|
||||
final relativePosition = details.globalPosition.dy - dragAreaTop;
|
||||
|
||||
// Make sure the position stays within the scrubber's bounds
|
||||
return relativePosition.clamp(0.0, dragAreaHeight);
|
||||
}
|
||||
|
||||
// Get the local position relative to the gesture detector
|
||||
final RenderBox? renderBox = context.findRenderObject() as RenderBox?;
|
||||
if (renderBox != null) {
|
||||
final localPosition = renderBox.globalToLocal(details.globalPosition);
|
||||
return localPosition.dy.clamp(0.0, _scrubberHeight);
|
||||
}
|
||||
|
||||
// Fallback to current logic if render box is not available
|
||||
final dragAreaTop = widget.topPadding;
|
||||
final dragAreaBottom = widget.timelineHeight - widget.bottomPadding;
|
||||
final dragAreaHeight = dragAreaBottom - dragAreaTop;
|
||||
|
||||
final relativePosition = details.globalPosition.dy - dragAreaTop;
|
||||
|
||||
// Make sure the position stays within the scrubber's bounds
|
||||
return relativePosition.clamp(0.0, dragAreaHeight);
|
||||
return relativePosition.clamp(0.0, _scrubberHeight);
|
||||
}
|
||||
|
||||
/// Find the segment closest to the given position
|
||||
@@ -294,12 +338,18 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
}
|
||||
|
||||
void _onDragEnd(DragEndDetails _) {
|
||||
ref.read(timelineStateProvider.notifier).setScrubbing(false);
|
||||
_labelAnimationController.reverse();
|
||||
setState(() {
|
||||
_isDragging = false;
|
||||
});
|
||||
|
||||
ref.read(timelineStateProvider.notifier).setScrubbing(false);
|
||||
|
||||
// Reset scrubber tracking when drag ends
|
||||
_currentScrubberDate = null;
|
||||
_scrubberDebouncer?.dispose();
|
||||
_scrubberDebouncer = null;
|
||||
|
||||
_resetThumbTimer();
|
||||
}
|
||||
|
||||
|
||||
@@ -72,8 +72,6 @@ class TimelineState {
|
||||
}
|
||||
|
||||
class TimelineStateNotifier extends Notifier<TimelineState> {
|
||||
TimelineStateNotifier();
|
||||
|
||||
void setScrubbing(bool isScrubbing) {
|
||||
state = state.copyWith(isScrubbing: isScrubbing);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
||||
@@ -38,6 +39,7 @@ class Timeline extends StatelessWidget {
|
||||
this.bottomSheet = const GeneralBottomSheet(minChildSize: 0.18),
|
||||
this.groupBy,
|
||||
this.withScrubber = true,
|
||||
this.snapToMonth = true,
|
||||
});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
@@ -48,11 +50,13 @@ class Timeline extends StatelessWidget {
|
||||
final bool withStack;
|
||||
final GroupAssetsBy? groupBy;
|
||||
final bool withScrubber;
|
||||
final bool snapToMonth;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
floatingActionButton: const DownloadStatusFloatingButton(),
|
||||
body: LayoutBuilder(
|
||||
builder: (_, constraints) => ProviderScope(
|
||||
overrides: [
|
||||
@@ -73,6 +77,7 @@ class Timeline extends StatelessWidget {
|
||||
appBar: appBar,
|
||||
bottomSheet: bottomSheet,
|
||||
withScrubber: withScrubber,
|
||||
snapToMonth: snapToMonth,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -87,6 +92,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
|
||||
this.appBar,
|
||||
this.bottomSheet,
|
||||
this.withScrubber = true,
|
||||
this.snapToMonth = true,
|
||||
});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
@@ -94,6 +100,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
|
||||
final Widget? appBar;
|
||||
final Widget? bottomSheet;
|
||||
final bool withScrubber;
|
||||
final bool snapToMonth;
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _SliverTimelineState();
|
||||
@@ -309,11 +316,13 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
final Widget timeline;
|
||||
if (widget.withScrubber) {
|
||||
timeline = Scrubber(
|
||||
snapToMonth: widget.snapToMonth,
|
||||
layoutSegments: segments,
|
||||
timelineHeight: maxHeight,
|
||||
topPadding: topPadding,
|
||||
bottomPadding: bottomPadding,
|
||||
monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight,
|
||||
hasAppBar: widget.appBar != null,
|
||||
child: grid,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -233,7 +233,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||
}
|
||||
|
||||
try {
|
||||
LogService.I.flush();
|
||||
await LogService.I.flush();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||
|
||||
// Flush logs before closing database
|
||||
try {
|
||||
LogService.I.flush();
|
||||
await LogService.I.flush();
|
||||
} catch (_) {}
|
||||
|
||||
// Close Isar database safely
|
||||
|
||||
@@ -98,7 +98,7 @@ class AssetNotifier extends StateNotifier<bool> {
|
||||
|
||||
Future<void> onNewAssetUploaded(Asset newAsset) async {
|
||||
// eTag on device is not valid after partially modifying the assets
|
||||
Store.delete(StoreKey.assetETag);
|
||||
await Store.delete(StoreKey.assetETag);
|
||||
await _syncService.syncNewAssetToDb(newAsset);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/download/download_state.model.dart';
|
||||
import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
|
||||
import 'package:immich_mobile/services/album.service.dart';
|
||||
import 'package:immich_mobile/services/download.service.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/services/share.service.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
import 'package:immich_mobile/widgets/common/share_dialog.dart';
|
||||
@@ -159,24 +161,26 @@ class DownloadStateNotifier extends StateNotifier<DownloadState> {
|
||||
}
|
||||
|
||||
void shareAsset(Asset asset, BuildContext context) async {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext buildContext) {
|
||||
_shareService.shareAsset(asset, context).then((bool status) {
|
||||
if (!status) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'image_viewer_page_state_provider_share_error'.tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
buildContext.pop();
|
||||
});
|
||||
return const ShareDialog();
|
||||
},
|
||||
barrierDismissible: false,
|
||||
useRootNavigator: false,
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext buildContext) {
|
||||
_shareService.shareAsset(asset, context).then((bool status) {
|
||||
if (!status) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'image_viewer_page_state_provider_share_error'.tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
buildContext.pop();
|
||||
});
|
||||
return const ShareDialog();
|
||||
},
|
||||
barrierDismissible: false,
|
||||
useRootNavigator: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ class ShareIntentUploadStateNotifier extends StateNotifier<List<ShareIntentAttac
|
||||
Future<void> upload(File file) async {
|
||||
final task = await _buildUploadTask(hash(file.path).toString(), file);
|
||||
|
||||
_uploadService.enqueueTasks([task]);
|
||||
await _uploadService.enqueueTasks([task]);
|
||||
}
|
||||
|
||||
Future<UploadTask> _buildUploadTask(String id, File file, {Map<String, String>? fields}) async {
|
||||
|
||||
@@ -380,7 +380,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
|
||||
state = state.copyWith(backgroundBackup: isEnabled);
|
||||
if (isEnabled != Store.get(StoreKey.backgroundBackup, !isEnabled)) {
|
||||
Store.put(StoreKey.backgroundBackup, isEnabled);
|
||||
await Store.put(StoreKey.backgroundBackup, isEnabled);
|
||||
}
|
||||
|
||||
if (state.backupProgress != BackUpProgressEnum.inBackground) {
|
||||
@@ -474,7 +474,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
);
|
||||
await notifyBackgroundServiceCanRun();
|
||||
} else {
|
||||
openAppSettings();
|
||||
await openAppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,10 +533,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
progressInFileSpeedUpdateTime: DateTime.now(),
|
||||
progressInFileSpeedUpdateSentBytes: 0,
|
||||
);
|
||||
_updatePersistentAlbumsSelection();
|
||||
await _updatePersistentAlbumsSelection();
|
||||
}
|
||||
|
||||
updateDiskInfo();
|
||||
await updateDiskInfo();
|
||||
}
|
||||
|
||||
void _onUploadProgress(int sent, int total) {
|
||||
|
||||
@@ -2,10 +2,10 @@ import 'dart:async';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||
import 'package:immich_mobile/services/backup_verification.service.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||
import 'package:immich_mobile/services/backup_verification.service.dart';
|
||||
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -44,7 +44,7 @@ class BackupVerification extends _$BackupVerification {
|
||||
}
|
||||
return;
|
||||
}
|
||||
WakelockPlus.enable();
|
||||
unawaited(WakelockPlus.enable());
|
||||
|
||||
const limit = 100;
|
||||
final toDelete = await ref.read(backupVerificationServiceProvider).findWronglyBackedUpAssets(limit: limit);
|
||||
@@ -73,7 +73,7 @@ class BackupVerification extends _$BackupVerification {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
WakelockPlus.disable();
|
||||
unawaited(WakelockPlus.disable());
|
||||
state = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'backup_verification.provider.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$backupVerificationHash() =>
|
||||
r'b204e43ab575d5fa5b2ee663297f32bcee9074f5';
|
||||
r'b4b34909ed1af3f28877ea457d53a4a18b6417f8';
|
||||
|
||||
/// See also [BackupVerification].
|
||||
@ProviderFor(BackupVerification)
|
||||
|
||||
@@ -235,7 +235,9 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
||||
switch (update.status) {
|
||||
case TaskStatus.complete:
|
||||
if (update.task.group == kBackupGroup) {
|
||||
state = state.copyWith(backupCount: state.backupCount + 1, remainderCount: state.remainderCount - 1);
|
||||
if (update.responseStatusCode == 201) {
|
||||
state = state.copyWith(backupCount: state.backupCount + 1, remainderCount: state.remainderCount - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the completed task from the upload items
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cancellation_token_http/http.dart';
|
||||
@@ -26,11 +27,11 @@ import 'package:immich_mobile/services/backup.service.dart';
|
||||
import 'package:immich_mobile/services/backup_album.service.dart';
|
||||
import 'package:immich_mobile/services/local_notification.service.dart';
|
||||
import 'package:immich_mobile/utils/backup_progress.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
|
||||
final manualUploadProvider = StateNotifierProvider<ManualUploadNotifier, ManualUploadState>((ref) {
|
||||
return ManualUploadNotifier(
|
||||
@@ -294,7 +295,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
openAppSettings();
|
||||
unawaited(openAppSettings());
|
||||
dPrint(() => "[_startUpload] Do not have permission to the gallery");
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
@@ -77,7 +76,7 @@ class ImmichLocalImageProvider extends ImageProvider<ImmichLocalImageProvider> {
|
||||
} catch (error, stack) {
|
||||
log.severe('Error loading local image ${asset.fileName}', error, stack);
|
||||
} finally {
|
||||
chunkEvents.close();
|
||||
unawaited(chunkEvents.close());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -64,7 +66,7 @@ class ActionNotifier extends Notifier<void> {
|
||||
void _downloadLivePhotoCallback(TaskStatusUpdate update) async {
|
||||
if (update.status == TaskStatus.complete) {
|
||||
final livePhotosId = LivePhotosMetadata.fromJson(update.task.metaData).id;
|
||||
_downloadService.saveLivePhotos(update.task, livePhotosId);
|
||||
unawaited(_downloadService.saveLivePhotos(update.task, livePhotosId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +124,7 @@ class ActionNotifier extends Notifier<void> {
|
||||
if (assets.length > 1) {
|
||||
return ActionResult(count: assets.length, success: false, error: 'Cannot troubleshoot multiple assets');
|
||||
}
|
||||
context.pushRoute(AssetTroubleshootRoute(asset: assets.first));
|
||||
unawaited(context.pushRoute(AssetTroubleshootRoute(asset: assets.first)));
|
||||
|
||||
return ActionResult(count: assets.length, success: true);
|
||||
}
|
||||
@@ -356,7 +358,6 @@ class ActionNotifier extends Notifier<void> {
|
||||
|
||||
Future<ActionResult> downloadAll(ActionSource source) async {
|
||||
final assets = _getAssets(source).whereType<RemoteAsset>().toList(growable: false);
|
||||
|
||||
try {
|
||||
final didEnqueue = await _service.downloadAll(assets);
|
||||
final enqueueCount = didEnqueue.where((e) => e).length;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
||||
import 'package:immich_mobile/services/shared_link.service.dart';
|
||||
@@ -16,7 +18,7 @@ class SharedLinksNotifier extends StateNotifier<AsyncValue<List<SharedLink>>> {
|
||||
Future<void> deleteLink(String id) async {
|
||||
await _sharedLinkService.deleteSharedLink(id);
|
||||
state = const AsyncLoading();
|
||||
fetchLinks();
|
||||
unawaited(fetchLinks());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
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/store.model.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart' as asset_entity;
|
||||
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/utils/hash.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.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';
|
||||
|
||||
final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider)));
|
||||
@@ -106,15 +107,17 @@ class AssetMediaRepository {
|
||||
|
||||
// we dont want to await the share result since the
|
||||
// "preparing" dialog will not disappear unti
|
||||
Share.shareXFiles(downloadedXFiles).then((result) async {
|
||||
for (var file in downloadedXFiles) {
|
||||
try {
|
||||
await File(file.path).delete();
|
||||
} catch (e) {
|
||||
_log.warning("Failed to delete temporary file: ${file.path}", e);
|
||||
unawaited(
|
||||
Share.shareXFiles(downloadedXFiles).then((result) async {
|
||||
for (var file in downloadedXFiles) {
|
||||
try {
|
||||
await File(file.path).delete();
|
||||
} catch (e) {
|
||||
_log.warning("Failed to delete temporary file: ${file.path}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return downloadedXFiles.length;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,11 @@ class DownloadRepository {
|
||||
final isVideo = asset.isVideo;
|
||||
final url = getOriginalUrlForRemoteId(id);
|
||||
|
||||
if (Platform.isAndroid || livePhotoVideoId == null || isVideo) {
|
||||
// on iOS it cannot link the image, check if the filename has .MP extension
|
||||
// to avoid downloading the video part
|
||||
final isAndroidMotionPhoto = asset.name.contains(".MP");
|
||||
|
||||
if (Platform.isAndroid || livePhotoVideoId == null || isVideo || isAndroidMotionPhoto) {
|
||||
tasks[taskIndex++] = DownloadTask(
|
||||
taskId: id,
|
||||
url: url,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -12,7 +14,7 @@ class AppNavigationObserver extends AutoRouterObserver {
|
||||
|
||||
@override
|
||||
Future<void> didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) async {
|
||||
Future(() => ref.read(inLockedViewProvider.notifier).state = false);
|
||||
unawaited(Future(() => ref.read(inLockedViewProvider.notifier).state = false));
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
@@ -26,18 +27,18 @@ class AuthGuard extends AutoRouteGuard {
|
||||
if (res == null || res.authStatus != true) {
|
||||
// If the access token is invalid, take user back to login
|
||||
_log.fine('User token is invalid. Redirecting to login');
|
||||
router.replaceAll([const LoginRoute()]);
|
||||
unawaited(router.replaceAll([const LoginRoute()]));
|
||||
}
|
||||
} on StoreKeyNotFoundException catch (_) {
|
||||
// If there is no access token, take us to the login page
|
||||
_log.warning('No access token in the store.');
|
||||
router.replaceAll([const LoginRoute()]);
|
||||
unawaited(router.replaceAll([const LoginRoute()]));
|
||||
return;
|
||||
} on ApiException catch (e) {
|
||||
// On an unauthorized request, take us to the login page
|
||||
if (e.code == HttpStatus.unauthorized) {
|
||||
_log.warning("Unauthorized access token.");
|
||||
router.replaceAll([const LoginRoute()]);
|
||||
unawaited(router.replaceAll([const LoginRoute()]));
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
@@ -13,7 +15,7 @@ class BackupPermissionGuard extends AutoRouteGuard {
|
||||
if (p) {
|
||||
resolver.next(true);
|
||||
} else {
|
||||
router.push(const PermissionOnboardingRoute());
|
||||
unawaited(router.push(const PermissionOnboardingRoute()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
@@ -13,12 +15,14 @@ class GalleryGuard extends AutoRouteGuard {
|
||||
// Replace instead of pushing duplicate
|
||||
final args = resolver.route.args as GalleryViewerRouteArgs;
|
||||
|
||||
router.replace(
|
||||
GalleryViewerRoute(
|
||||
renderList: args.renderList,
|
||||
initialIndex: args.initialIndex,
|
||||
heroOffset: args.heroOffset,
|
||||
showStack: args.showStack,
|
||||
unawaited(
|
||||
router.replace(
|
||||
GalleryViewerRoute(
|
||||
renderList: args.renderList,
|
||||
initialIndex: args.initialIndex,
|
||||
heroOffset: args.heroOffset,
|
||||
showStack: args.showStack,
|
||||
),
|
||||
),
|
||||
);
|
||||
// Prevent further navigation since we replaced the route
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/services/local_auth.service.dart';
|
||||
import 'package:immich_mobile/services/secure_storage.service.dart';
|
||||
@@ -30,7 +31,7 @@ class LockedGuard extends AutoRouteGuard {
|
||||
|
||||
/// Check if a pincode has been created but this user. Show the form to create if not exist
|
||||
if (!authStatus.pinCode) {
|
||||
router.push(PinAuthRoute(createPinCode: true));
|
||||
unawaited(router.push(PinAuthRoute(createPinCode: true)));
|
||||
}
|
||||
|
||||
if (authStatus.isElevated) {
|
||||
@@ -42,7 +43,7 @@ class LockedGuard extends AutoRouteGuard {
|
||||
/// the user has enabled the biometric authentication
|
||||
final securePinCode = await _secureStorageService.read(kSecuredPinCode);
|
||||
if (securePinCode == null) {
|
||||
router.push(PinAuthRoute());
|
||||
unawaited(router.push(PinAuthRoute()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,7 +75,7 @@ class LockedGuard extends AutoRouteGuard {
|
||||
} on ApiException {
|
||||
// PIN code has changed, need to re-enter to access
|
||||
await _secureStorageService.delete(kSecuredPinCode);
|
||||
router.push(PinAuthRoute());
|
||||
unawaited(router.push(PinAuthRoute()));
|
||||
} catch (error) {
|
||||
_log.severe("Failed to access locked page", error);
|
||||
resolver.next(false);
|
||||
|
||||
@@ -81,6 +81,7 @@ import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/download_info.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_activities.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_album_options.page.dart';
|
||||
@@ -345,6 +346,7 @@ class AppRouter extends RootStackRouter {
|
||||
AutoRoute(page: DriftActivitiesRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftBackupAssetDetailRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: AssetTroubleshootRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DownloadInfoRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
// required to handle all deeplinks in deep_link.service.dart
|
||||
// auto_route_library#1722
|
||||
RedirectRoute(path: '*', redirectTo: '/'),
|
||||
|
||||
@@ -688,6 +688,22 @@ class CropImageRouteArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DownloadInfoPage]
|
||||
class DownloadInfoRoute extends PageRouteInfo<void> {
|
||||
const DownloadInfoRoute({List<PageRouteInfo>? children})
|
||||
: super(DownloadInfoRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DownloadInfoRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const DownloadInfoPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftActivitiesPage]
|
||||
class DriftActivitiesRoute extends PageRouteInfo<void> {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/repositories/download.repository.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
@@ -11,6 +12,7 @@ import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||
import 'package:immich_mobile/repositories/download.repository.dart';
|
||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/common/date_time_picker.dart';
|
||||
@@ -50,7 +52,7 @@ class ActionService {
|
||||
);
|
||||
|
||||
Future<void> shareLink(List<String> remoteIds, BuildContext context) async {
|
||||
context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds));
|
||||
unawaited(context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds)));
|
||||
}
|
||||
|
||||
Future<void> favorite(List<String> remoteIds) async {
|
||||
@@ -199,14 +201,11 @@ class ActionService {
|
||||
}
|
||||
|
||||
Future<int> removeFromAlbum(List<String> remoteIds, String albumId) async {
|
||||
int removedCount = 0;
|
||||
final result = await _albumApiRepository.removeAssets(albumId, remoteIds);
|
||||
|
||||
if (result.removed.isNotEmpty) {
|
||||
removedCount = await _remoteAlbumRepository.removeAssets(albumId, result.removed);
|
||||
await _remoteAlbumRepository.removeAssets(albumId, result.removed);
|
||||
}
|
||||
|
||||
return removedCount;
|
||||
return result.removed.length;
|
||||
}
|
||||
|
||||
Future<bool> updateDescription(String assetId, String description) async {
|
||||
|
||||
@@ -83,7 +83,7 @@ class AlbumService {
|
||||
if (selectedIds.isEmpty) {
|
||||
final numLocal = await _albumRepository.count(local: true);
|
||||
if (numLocal > 0) {
|
||||
_syncService.removeAllLocalAlbumsAndAssets();
|
||||
await _syncService.removeAllLocalAlbumsAndAssets();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
import 'package:immich_mobile/utils/url_helper.dart';
|
||||
import 'package:immich_mobile/utils/user_agent.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:immich_mobile/utils/user_agent.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
|
||||
class ApiService implements Authentication {
|
||||
late ApiClient _apiClient;
|
||||
@@ -86,7 +86,7 @@ class ApiService implements Authentication {
|
||||
setEndpoint(endpoint);
|
||||
|
||||
// Save in local database for next startup
|
||||
Store.put(StoreKey.serverEndpoint, endpoint);
|
||||
unawaited(Store.put(StoreKey.serverEndpoint, endpoint));
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class AuthService {
|
||||
Future<String> validateServerUrl(String url) async {
|
||||
final validUrl = await _apiService.resolveAndSetEndpoint(url);
|
||||
await _apiService.setDeviceInfoHeader();
|
||||
Store.put(StoreKey.serverUrl, validUrl);
|
||||
await Store.put(StoreKey.serverUrl, validUrl);
|
||||
|
||||
return validUrl;
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ class BackgroundService {
|
||||
case "backgroundProcessing":
|
||||
case "onAssetsChanged":
|
||||
try {
|
||||
_clearErrorNotifications();
|
||||
unawaited(_clearErrorNotifications());
|
||||
|
||||
// iOS should time out after some threshold so it doesn't wait
|
||||
// indefinitely and can run later
|
||||
@@ -342,7 +342,7 @@ class BackgroundService {
|
||||
);
|
||||
|
||||
HttpSSLOptions.apply();
|
||||
ref.read(apiServiceProvider).setAccessToken(Store.get(StoreKey.accessToken));
|
||||
await ref.read(apiServiceProvider).setAccessToken(Store.get(StoreKey.accessToken));
|
||||
await ref.read(authServiceProvider).setOpenApiServiceEndpoint();
|
||||
dPrint(() => "[BG UPLOAD] Using endpoint: ${ref.read(apiServiceProvider).apiClient.basePath}");
|
||||
|
||||
@@ -385,7 +385,7 @@ class BackgroundService {
|
||||
await ref.read(backupAlbumRepositoryProvider).deleteAll(toDelete);
|
||||
await ref.read(backupAlbumRepositoryProvider).updateAll(toUpsert);
|
||||
} else if (Store.tryGet(StoreKey.backupFailedSince) == null) {
|
||||
Store.put(StoreKey.backupFailedSince, DateTime.now());
|
||||
await Store.put(StoreKey.backupFailedSince, DateTime.now());
|
||||
return false;
|
||||
}
|
||||
// Android should check for new assets added while performing backup
|
||||
@@ -412,9 +412,11 @@ class BackgroundService {
|
||||
try {
|
||||
toUpload = await backupService.removeAlreadyUploadedAssets(toUpload);
|
||||
} catch (e) {
|
||||
_showErrorNotification(
|
||||
title: "backup_background_service_error_title".tr(),
|
||||
content: "backup_background_service_connection_failed_message".tr(),
|
||||
unawaited(
|
||||
_showErrorNotification(
|
||||
title: "backup_background_service_error_title".tr(),
|
||||
content: "backup_background_service_connection_failed_message".tr(),
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -428,13 +430,15 @@ class BackgroundService {
|
||||
}
|
||||
_assetsToUploadCount = toUpload.length;
|
||||
_uploadedAssetsCount = 0;
|
||||
_updateNotification(
|
||||
title: "backup_background_service_in_progress_notification".tr(),
|
||||
content: notifyTotalProgress ? formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount) : null,
|
||||
progress: 0,
|
||||
max: notifyTotalProgress ? _assetsToUploadCount : 0,
|
||||
indeterminate: !notifyTotalProgress,
|
||||
onlyIfFG: !notifyTotalProgress,
|
||||
unawaited(
|
||||
_updateNotification(
|
||||
title: "backup_background_service_in_progress_notification".tr(),
|
||||
content: notifyTotalProgress ? formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount) : null,
|
||||
progress: 0,
|
||||
max: notifyTotalProgress ? _assetsToUploadCount : 0,
|
||||
indeterminate: !notifyTotalProgress,
|
||||
onlyIfFG: !notifyTotalProgress,
|
||||
),
|
||||
);
|
||||
|
||||
_cancellationToken = CancellationToken();
|
||||
@@ -452,9 +456,11 @@ class BackgroundService {
|
||||
);
|
||||
|
||||
if (!ok && !_cancellationToken!.isCancelled) {
|
||||
_showErrorNotification(
|
||||
title: "backup_background_service_error_title".tr(),
|
||||
content: "backup_background_service_backup_failed_message".tr(),
|
||||
unawaited(
|
||||
_showErrorNotification(
|
||||
title: "backup_background_service_error_title".tr(),
|
||||
content: "backup_background_service_backup_failed_message".tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class BackupVerificationService {
|
||||
await tuple.fileMediaRepository.enableBackgroundAccess();
|
||||
final ApiService apiService = ApiService();
|
||||
apiService.setEndpoint(tuple.endpoint);
|
||||
apiService.setAccessToken(tuple.auth);
|
||||
await apiService.setAccessToken(tuple.auth);
|
||||
for (int i = 0; i < tuple.deleteCandidates.length; i++) {
|
||||
if (await _compareAssets(tuple.deleteCandidates[i], tuple.originals[i], apiService)) {
|
||||
result.add(tuple.deleteCandidates[i]);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:immich_mobile/mixins/error_logger.mixin.dart';
|
||||
import 'package:immich_mobile/models/map/map_marker.model.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/user_agent.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
import 'package:immich_mobile/utils/user_agent.dart';
|
||||
|
||||
class MapService with ErrorLoggerMixin {
|
||||
final ApiService _apiService;
|
||||
@@ -16,7 +16,7 @@ class MapService with ErrorLoggerMixin {
|
||||
|
||||
Future<void> _setMapUserAgentHeader() async {
|
||||
final userAgent = await getUserAgentString();
|
||||
setHttpHeaders({'User-Agent': userAgent});
|
||||
await setHttpHeaders({'User-Agent': userAgent});
|
||||
}
|
||||
|
||||
Future<Iterable<MapMarker>> getMapMarkers({
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/response_extensions.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/extensions/response_extensions.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import 'api.service.dart';
|
||||
|
||||
final shareServiceProvider = Provider((ref) => ShareService(ref.watch(apiServiceProvider)));
|
||||
@@ -58,9 +60,11 @@ class ShareService {
|
||||
}
|
||||
|
||||
final size = MediaQuery.of(context).size;
|
||||
Share.shareXFiles(
|
||||
downloadedXFiles,
|
||||
sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)),
|
||||
unawaited(
|
||||
Share.shareXFiles(
|
||||
downloadedXFiles,
|
||||
sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)),
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
@@ -705,7 +705,7 @@ class SyncService {
|
||||
if (assets.isEmpty) return;
|
||||
|
||||
if (Platform.isAndroid && _appSettingsService.getSetting<bool>(AppSettingsEnum.manageLocalMediaAndroid)) {
|
||||
_toggleTrashStatusForAssets(assets);
|
||||
await _toggleTrashStatusForAssets(assets);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -251,7 +251,7 @@ class UploadService {
|
||||
return;
|
||||
}
|
||||
|
||||
enqueueTasks([uploadTask]);
|
||||
await enqueueTasks([uploadTask]);
|
||||
} catch (error, stackTrace) {
|
||||
dPrint(() => "Error handling live photo upload task: $error $stackTrace");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:immich_mobile/models/map/map_marker.model.dart';
|
||||
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
|
||||
@@ -68,7 +70,7 @@ class MapUtils {
|
||||
try {
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled && !silent) {
|
||||
showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog());
|
||||
unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog()));
|
||||
return (null, LocationPermission.deniedForever);
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ Future<void> handleEditDateTime(WidgetRef ref, BuildContext context, List<Asset>
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime);
|
||||
await ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime);
|
||||
}
|
||||
|
||||
Future<void> handleEditLocation(WidgetRef ref, BuildContext context, List<Asset> selection) async {
|
||||
@@ -120,7 +120,7 @@ Future<void> handleEditLocation(WidgetRef ref, BuildContext context, List<Asset>
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(assetServiceProvider).changeLocation(selection.toList(), location);
|
||||
await ref.read(assetServiceProvider).changeLocation(selection.toList(), location);
|
||||
}
|
||||
|
||||
Future<void> handleSetAssetsVisibility(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -57,7 +59,7 @@ class AlbumViewerAppbar extends HookConsumerWidget implements PreferredSizeWidge
|
||||
deleteAlbum() async {
|
||||
final bool success = await ref.watch(albumProvider.notifier).deleteAlbum(album);
|
||||
|
||||
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
|
||||
unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])));
|
||||
|
||||
if (!success) {
|
||||
ImmichToast.show(
|
||||
@@ -105,7 +107,7 @@ class AlbumViewerAppbar extends HookConsumerWidget implements PreferredSizeWidge
|
||||
bool isSuccess = await ref.watch(albumProvider.notifier).leaveAlbum(album);
|
||||
|
||||
if (isSuccess) {
|
||||
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
|
||||
unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()])));
|
||||
} else {
|
||||
context.pop();
|
||||
ImmichToast.show(
|
||||
|
||||
@@ -314,10 +314,10 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
final result = await ref.read(albumServiceProvider).createAlbumWithGeneratedName(assets);
|
||||
|
||||
if (result != null) {
|
||||
ref.watch(albumProvider.notifier).refreshRemoteAlbums();
|
||||
unawaited(ref.watch(albumProvider.notifier).refreshRemoteAlbums());
|
||||
selectionEnabledHook.value = false;
|
||||
|
||||
context.pushRoute(AlbumViewerRoute(albumId: result.id));
|
||||
unawaited(context.pushRoute(AlbumViewerRoute(albumId: result.id)));
|
||||
}
|
||||
} finally {
|
||||
processing.value = false;
|
||||
@@ -346,7 +346,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
if (remoteAssets.isNotEmpty) {
|
||||
handleEditDateTime(ref, context, remoteAssets.toList());
|
||||
unawaited(handleEditDateTime(ref, context, remoteAssets.toList()));
|
||||
}
|
||||
} finally {
|
||||
selectionEnabledHook.value = false;
|
||||
@@ -361,7 +361,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
if (remoteAssets.isNotEmpty) {
|
||||
handleEditLocation(ref, context, remoteAssets.toList());
|
||||
unawaited(handleEditLocation(ref, context, remoteAssets.toList()));
|
||||
}
|
||||
} finally {
|
||||
selectionEnabledHook.value = false;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
@@ -81,7 +82,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
// to not throw the error when the next preCache index is called
|
||||
if (totalAssets.value == 1 || assetIndex.value == totalAssets.value - 1) {
|
||||
// Handle only one asset
|
||||
context.maybePop();
|
||||
await context.maybePop();
|
||||
}
|
||||
|
||||
totalAssets.value -= 1;
|
||||
@@ -111,18 +112,20 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
}
|
||||
|
||||
// Asset is permanently removed
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext _) {
|
||||
return DeleteDialog(
|
||||
onDelete: () async {
|
||||
final isDeleted = await onDelete(true);
|
||||
if (isDeleted) {
|
||||
removeAssetFromStack();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext _) {
|
||||
return DeleteDialog(
|
||||
onDelete: () async {
|
||||
final isDeleted = await onDelete(true);
|
||||
if (isDeleted) {
|
||||
removeAssetFromStack();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -150,7 +153,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
onTap: () async {
|
||||
await unStack();
|
||||
ctx.pop();
|
||||
context.maybePop();
|
||||
await context.maybePop();
|
||||
},
|
||||
title: const Text("viewer_unstack", style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
@@ -178,9 +181,11 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
void handleEdit() async {
|
||||
final image = Image(image: ImmichImage.imageProvider(asset: asset));
|
||||
|
||||
context.navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EditImagePage(asset: asset, image: image, isEdited: false),
|
||||
unawaited(
|
||||
context.navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EditImagePage(asset: asset, image: image, isEdited: false),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -93,7 +95,7 @@ class CastDialog extends ConsumerWidget {
|
||||
}
|
||||
|
||||
if (!isCurrentDevice(deviceName)) {
|
||||
ref.read(castProvider.notifier).connect(type, deviceObj);
|
||||
unawaited(ref.read(castProvider.notifier).connect(type, deviceObj));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user