mirror of
https://github.com/immich-app/immich.git
synced 2026-07-02 02:55:01 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ca2c5effc | |||
| febc162821 | |||
| a855852ae7 | |||
| 35fdaaeed8 | |||
| 5f2f472f98 | |||
| e459c3d5a3 | |||
| 800bfab956 | |||
| 84ba31bad4 | |||
| e225f874b1 |
+3603
File diff suppressed because it is too large
Load Diff
+3660
File diff suppressed because it is too large
Load Diff
+3717
File diff suppressed because it is too large
Load Diff
@@ -34,14 +34,14 @@ void main() {
|
|||||||
server = await FakeImmichServer.start();
|
server = await FakeImmichServer.start();
|
||||||
await ApiService().resolveAndSetEndpoint(server.endpoint);
|
await ApiService().resolveAndSetEndpoint(server.endpoint);
|
||||||
await drift.delete(drift.userEntity).go();
|
await drift.delete(drift.userEntity).go();
|
||||||
await Store.delete(StoreKey.syncMigrationStatus);
|
await Store.delete(StoreKey.legacySyncMigrationStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await workerManagerPatch.dispose();
|
await workerManagerPatch.dispose();
|
||||||
await server.close();
|
await server.close();
|
||||||
await Store.delete(StoreKey.serverEndpoint);
|
await Store.delete(StoreKey.legacyServerEndpoint);
|
||||||
await Store.delete(StoreKey.syncMigrationStatus);
|
await Store.delete(StoreKey.legacySyncMigrationStatus);
|
||||||
});
|
});
|
||||||
|
|
||||||
void sendUser(SyncStream stream, String id, String name) {
|
void sendUser(SyncStream stream, String id, String name) {
|
||||||
@@ -119,7 +119,9 @@ void main() {
|
|||||||
final releaseTxn = Completer<void>();
|
final releaseTxn = Completer<void>();
|
||||||
final txnHeld = Completer<void>();
|
final txnHeld = Completer<void>();
|
||||||
final txn = drift.transaction(() async {
|
final txn = drift.transaction(() async {
|
||||||
await drift.into(drift.userEntity).insert(
|
await drift
|
||||||
|
.into(drift.userEntity)
|
||||||
|
.insert(
|
||||||
UserEntityCompanion.insert(
|
UserEntityCompanion.insert(
|
||||||
id: 'holder',
|
id: 'holder',
|
||||||
name: 'holder',
|
name: 'holder',
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/value_codec.dart';
|
||||||
|
|
||||||
|
const int kCurrentVersion = 29;
|
||||||
|
|
||||||
|
enum AppMetadataKey<T> {
|
||||||
|
version<int>(kCurrentVersion),
|
||||||
|
syncMigrationStatus<List<String>>([], codec: ListCodec(PrimitiveCodec.string)),
|
||||||
|
manageLocalMediaAndroid<bool>(false);
|
||||||
|
|
||||||
|
const AppMetadataKey(this.defaultValue, {ValueCodec<T>? codec}) : _codecOverride = codec;
|
||||||
|
|
||||||
|
final T defaultValue;
|
||||||
|
|
||||||
|
final ValueCodec<T>? _codecOverride;
|
||||||
|
ValueCodec<T> get _codec => _codecOverride ?? ValueCodec.forType(T);
|
||||||
|
|
||||||
|
String encode(T value) => _codec.encode(value);
|
||||||
|
|
||||||
|
T decode(String raw) => _codec.decode(raw);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
class AdvancedConfig {
|
||||||
|
final bool troubleshooting;
|
||||||
|
final bool enableHapticFeedback;
|
||||||
|
final bool readonlyModeEnabled;
|
||||||
|
|
||||||
|
const AdvancedConfig({
|
||||||
|
this.troubleshooting = false,
|
||||||
|
this.enableHapticFeedback = true,
|
||||||
|
this.readonlyModeEnabled = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
AdvancedConfig copyWith({bool? troubleshooting, bool? enableHapticFeedback, bool? readonlyModeEnabled}) =>
|
||||||
|
AdvancedConfig(
|
||||||
|
troubleshooting: troubleshooting ?? this.troubleshooting,
|
||||||
|
enableHapticFeedback: enableHapticFeedback ?? this.enableHapticFeedback,
|
||||||
|
readonlyModeEnabled: readonlyModeEnabled ?? this.readonlyModeEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is AdvancedConfig &&
|
||||||
|
other.troubleshooting == troubleshooting &&
|
||||||
|
other.enableHapticFeedback == enableHapticFeedback &&
|
||||||
|
other.readonlyModeEnabled == readonlyModeEnabled);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(troubleshooting, enableHapticFeedback, readonlyModeEnabled);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'AdvancedConfig(troubleshooting: $troubleshooting, enableHapticFeedback: $enableHapticFeedback, readonlyModeEnabled: $readonlyModeEnabled)';
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/config/advanced_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/album_config.dart';
|
import 'package:immich_mobile/domain/models/config/album_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/backup_config.dart';
|
import 'package:immich_mobile/domain/models/config/backup_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
|
import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
|
||||||
@@ -32,6 +33,7 @@ class AppConfig {
|
|||||||
final BackupConfig backup;
|
final BackupConfig backup;
|
||||||
final NetworkConfig network;
|
final NetworkConfig network;
|
||||||
final ShareConfig share;
|
final ShareConfig share;
|
||||||
|
final AdvancedConfig advanced;
|
||||||
|
|
||||||
const AppConfig({
|
const AppConfig({
|
||||||
this.logLevel = .info,
|
this.logLevel = .info,
|
||||||
@@ -46,6 +48,7 @@ class AppConfig {
|
|||||||
this.backup = const .new(),
|
this.backup = const .new(),
|
||||||
this.network = const .new(),
|
this.network = const .new(),
|
||||||
this.share = const .new(),
|
this.share = const .new(),
|
||||||
|
this.advanced = const .new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
AppConfig copyWith({
|
AppConfig copyWith({
|
||||||
@@ -61,6 +64,7 @@ class AppConfig {
|
|||||||
BackupConfig? backup,
|
BackupConfig? backup,
|
||||||
NetworkConfig? network,
|
NetworkConfig? network,
|
||||||
ShareConfig? share,
|
ShareConfig? share,
|
||||||
|
AdvancedConfig? advanced,
|
||||||
}) => .new(
|
}) => .new(
|
||||||
logLevel: logLevel ?? this.logLevel,
|
logLevel: logLevel ?? this.logLevel,
|
||||||
theme: theme ?? this.theme,
|
theme: theme ?? this.theme,
|
||||||
@@ -74,6 +78,7 @@ class AppConfig {
|
|||||||
backup: backup ?? this.backup,
|
backup: backup ?? this.backup,
|
||||||
network: network ?? this.network,
|
network: network ?? this.network,
|
||||||
share: share ?? this.share,
|
share: share ?? this.share,
|
||||||
|
advanced: advanced ?? this.advanced,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -91,17 +96,31 @@ class AppConfig {
|
|||||||
other.album == album &&
|
other.album == album &&
|
||||||
other.backup == backup &&
|
other.backup == backup &&
|
||||||
other.network == network &&
|
other.network == network &&
|
||||||
other.share == share);
|
other.share == share &&
|
||||||
|
other.advanced == advanced);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => Object.hash(
|
||||||
Object.hash(logLevel, theme, cleanup, map, timeline, image, viewer, slideshow, album, backup, network, share);
|
logLevel,
|
||||||
|
theme,
|
||||||
|
cleanup,
|
||||||
|
map,
|
||||||
|
timeline,
|
||||||
|
image,
|
||||||
|
viewer,
|
||||||
|
slideshow,
|
||||||
|
album,
|
||||||
|
backup,
|
||||||
|
network,
|
||||||
|
share,
|
||||||
|
advanced,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network, share: $share)';
|
'AppConfig(logLevel: $logLevel, theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup, network: $network, share: $share, advanced: $advanced)';
|
||||||
|
|
||||||
T read<T extends Object>(SettingsKey<T> key) =>
|
T read<T>(SettingsKey<T> key) =>
|
||||||
(switch (key) {
|
(switch (key) {
|
||||||
.logLevel => logLevel,
|
.logLevel => logLevel,
|
||||||
.themePrimaryColor => theme.primaryColor,
|
.themePrimaryColor => theme.primaryColor,
|
||||||
@@ -147,13 +166,16 @@ class AppConfig {
|
|||||||
.slideshowDuration => slideshow.duration,
|
.slideshowDuration => slideshow.duration,
|
||||||
.slideshowLook => slideshow.look,
|
.slideshowLook => slideshow.look,
|
||||||
.slideshowDirection => slideshow.direction,
|
.slideshowDirection => slideshow.direction,
|
||||||
|
.advancedTroubleshooting => advanced.troubleshooting,
|
||||||
|
.advancedEnableHapticFeedback => advanced.enableHapticFeedback,
|
||||||
|
.advancedReadonlyModeEnabled => advanced.readonlyModeEnabled,
|
||||||
})
|
})
|
||||||
as T;
|
as T;
|
||||||
|
|
||||||
factory AppConfig.fromEntries(Map<SettingsKey<Object>, Object> overrides) =>
|
factory AppConfig.fromEntries(Map<SettingsKey, Object?> overrides) =>
|
||||||
overrides.entries.fold(const AppConfig(), (config, entry) => config.write(entry.key, entry.value));
|
overrides.entries.fold(const AppConfig(), (config, entry) => config.write(entry.key, entry.value));
|
||||||
|
|
||||||
AppConfig write<T extends Object>(SettingsKey<T> key, T value) {
|
AppConfig write<T, U extends T>(SettingsKey<T> key, U value) {
|
||||||
return switch (key) {
|
return switch (key) {
|
||||||
.logLevel => copyWith(logLevel: value as LogLevel),
|
.logLevel => copyWith(logLevel: value as LogLevel),
|
||||||
.themePrimaryColor => copyWith(theme: theme.copyWith(primaryColor: value as ImmichColorPreset)),
|
.themePrimaryColor => copyWith(theme: theme.copyWith(primaryColor: value as ImmichColorPreset)),
|
||||||
@@ -167,8 +189,10 @@ class AppConfig {
|
|||||||
.viewerAutoPlayVideo => copyWith(viewer: viewer.copyWith(autoPlayVideo: value as bool)),
|
.viewerAutoPlayVideo => copyWith(viewer: viewer.copyWith(autoPlayVideo: value as bool)),
|
||||||
.viewerTapToNavigate => copyWith(viewer: viewer.copyWith(tapToNavigate: value as bool)),
|
.viewerTapToNavigate => copyWith(viewer: viewer.copyWith(tapToNavigate: value as bool)),
|
||||||
.networkAutoEndpointSwitching => copyWith(network: network.copyWith(autoEndpointSwitching: value as bool)),
|
.networkAutoEndpointSwitching => copyWith(network: network.copyWith(autoEndpointSwitching: value as bool)),
|
||||||
.networkPreferredWifiName => copyWith(network: network.copyWith(preferredWifiName: (value as String))),
|
.networkPreferredWifiName => copyWith(
|
||||||
.networkLocalEndpoint => copyWith(network: network.copyWith(localEndpoint: (value as String))),
|
network: network.copyWith(preferredWifiName: .fromNullable((value as String?))),
|
||||||
|
),
|
||||||
|
.networkLocalEndpoint => copyWith(network: network.copyWith(localEndpoint: .fromNullable((value as String?)))),
|
||||||
.networkExternalEndpointList => copyWith(network: network.copyWith(externalEndpointList: value as List<String>)),
|
.networkExternalEndpointList => copyWith(network: network.copyWith(externalEndpointList: value as List<String>)),
|
||||||
.networkCustomHeaders => copyWith(network: network.copyWith(customHeaders: value as Map<String, String>)),
|
.networkCustomHeaders => copyWith(network: network.copyWith(customHeaders: value as Map<String, String>)),
|
||||||
.albumSortMode => copyWith(album: album.copyWith(sortMode: value as AlbumSortMode)),
|
.albumSortMode => copyWith(album: album.copyWith(sortMode: value as AlbumSortMode)),
|
||||||
@@ -199,6 +223,9 @@ class AppConfig {
|
|||||||
.slideshowDuration => copyWith(slideshow: slideshow.copyWith(duration: value as int)),
|
.slideshowDuration => copyWith(slideshow: slideshow.copyWith(duration: value as int)),
|
||||||
.slideshowLook => copyWith(slideshow: slideshow.copyWith(look: value as SlideshowLook)),
|
.slideshowLook => copyWith(slideshow: slideshow.copyWith(look: value as SlideshowLook)),
|
||||||
.slideshowDirection => copyWith(slideshow: slideshow.copyWith(direction: value as SlideshowDirection)),
|
.slideshowDirection => copyWith(slideshow: slideshow.copyWith(direction: value as SlideshowDirection)),
|
||||||
|
.advancedTroubleshooting => copyWith(advanced: advanced.copyWith(troubleshooting: value as bool)),
|
||||||
|
.advancedEnableHapticFeedback => copyWith(advanced: advanced.copyWith(enableHapticFeedback: value as bool)),
|
||||||
|
.advancedReadonlyModeEnabled => copyWith(advanced: advanced.copyWith(readonlyModeEnabled: value as bool)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:immich_mobile/utils/option.dart';
|
||||||
|
|
||||||
class NetworkConfig {
|
class NetworkConfig {
|
||||||
final bool autoEndpointSwitching;
|
final bool autoEndpointSwitching;
|
||||||
final String preferredWifiName;
|
final String? preferredWifiName;
|
||||||
final String localEndpoint;
|
final String? localEndpoint;
|
||||||
final List<String> externalEndpointList;
|
final List<String> externalEndpointList;
|
||||||
final Map<String, String> customHeaders;
|
final Map<String, String> customHeaders;
|
||||||
|
|
||||||
const NetworkConfig({
|
const NetworkConfig({
|
||||||
this.autoEndpointSwitching = false,
|
this.autoEndpointSwitching = false,
|
||||||
this.preferredWifiName = '',
|
this.preferredWifiName,
|
||||||
this.localEndpoint = '',
|
this.localEndpoint,
|
||||||
this.externalEndpointList = const [],
|
this.externalEndpointList = const [],
|
||||||
this.customHeaders = const {},
|
this.customHeaders = const {},
|
||||||
});
|
});
|
||||||
|
|
||||||
NetworkConfig copyWith({
|
NetworkConfig copyWith({
|
||||||
bool? autoEndpointSwitching,
|
bool? autoEndpointSwitching,
|
||||||
String? preferredWifiName,
|
Option<String>? preferredWifiName,
|
||||||
String? localEndpoint,
|
Option<String>? localEndpoint,
|
||||||
List<String>? externalEndpointList,
|
List<String>? externalEndpointList,
|
||||||
Map<String, String>? customHeaders,
|
Map<String, String>? customHeaders,
|
||||||
}) => NetworkConfig(
|
}) => NetworkConfig(
|
||||||
autoEndpointSwitching: autoEndpointSwitching ?? this.autoEndpointSwitching,
|
autoEndpointSwitching: autoEndpointSwitching ?? this.autoEndpointSwitching,
|
||||||
preferredWifiName: preferredWifiName ?? this.preferredWifiName,
|
preferredWifiName: preferredWifiName.patch(this.preferredWifiName),
|
||||||
localEndpoint: localEndpoint ?? this.localEndpoint,
|
localEndpoint: localEndpoint.patch(this.localEndpoint),
|
||||||
externalEndpointList: externalEndpointList ?? this.externalEndpointList,
|
externalEndpointList: externalEndpointList ?? this.externalEndpointList,
|
||||||
customHeaders: customHeaders ?? this.customHeaders,
|
customHeaders: customHeaders ?? this.customHeaders,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/value_codec.dart';
|
||||||
|
import 'package:immich_mobile/utils/option.dart';
|
||||||
|
|
||||||
|
enum SessionKey<T> {
|
||||||
|
serverUrl<String?>(),
|
||||||
|
accessToken<String?>(),
|
||||||
|
serverEndpoint<String?>();
|
||||||
|
|
||||||
|
ValueCodec<T> get _codec => ValueCodec.forType(T);
|
||||||
|
|
||||||
|
String encode(T value) => _codec.encode(value);
|
||||||
|
|
||||||
|
T decode(String raw) => _codec.decode(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultSession = Session();
|
||||||
|
|
||||||
|
class Session {
|
||||||
|
final String? serverUrl;
|
||||||
|
final String? accessToken;
|
||||||
|
final String? serverEndpoint;
|
||||||
|
|
||||||
|
const Session({this.serverUrl, this.accessToken, this.serverEndpoint});
|
||||||
|
|
||||||
|
Session copyWith({Option<String>? serverUrl, Option<String>? accessToken, Option<String>? serverEndpoint}) => .new(
|
||||||
|
serverUrl: serverUrl.patch(this.serverUrl),
|
||||||
|
accessToken: accessToken.patch(this.accessToken),
|
||||||
|
serverEndpoint: serverEndpoint.patch(this.serverEndpoint),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is Session &&
|
||||||
|
other.serverUrl == serverUrl &&
|
||||||
|
other.accessToken == accessToken &&
|
||||||
|
other.serverEndpoint == serverEndpoint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(serverUrl, accessToken, serverEndpoint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Session(serverUrl: $serverUrl, accessToken: $accessToken, serverEndpoint: $serverEndpoint)';
|
||||||
|
|
||||||
|
T read<T>(SessionKey<T> key) =>
|
||||||
|
(switch (key) {
|
||||||
|
.serverUrl => serverUrl,
|
||||||
|
.accessToken => accessToken,
|
||||||
|
.serverEndpoint => serverEndpoint,
|
||||||
|
})
|
||||||
|
as T;
|
||||||
|
|
||||||
|
factory Session.fromEntries(Map<SessionKey, Object?> overrides) =>
|
||||||
|
overrides.entries.fold(const Session(), (session, entry) => session.write(entry.key, entry.value));
|
||||||
|
|
||||||
|
Session write<T, U extends T>(SessionKey<T> key, U value) {
|
||||||
|
return switch (key) {
|
||||||
|
.serverUrl => copyWith(serverUrl: .fromNullable(value as String?)),
|
||||||
|
.accessToken => copyWith(accessToken: .fromNullable(value as String?)),
|
||||||
|
.serverEndpoint => copyWith(serverEndpoint: .fromNullable(value as String?)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
|
|
||||||
enum Setting<T> {
|
|
||||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false);
|
|
||||||
|
|
||||||
const Setting(this.storeKey, this.defaultValue);
|
|
||||||
|
|
||||||
final StoreKey<T> storeKey;
|
|
||||||
final T defaultValue;
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/value_codec.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
|
||||||
enum SettingsKey<T extends Object> {
|
enum SettingsKey<T> {
|
||||||
// Theme
|
// Theme
|
||||||
themePrimaryColor<ImmichColorPreset>(codec: _EnumCodec(ImmichColorPreset.values)),
|
themePrimaryColor<ImmichColorPreset>(codec: EnumCodec(ImmichColorPreset.values)),
|
||||||
themeMode<ThemeMode>(codec: _EnumCodec(ThemeMode.values)),
|
themeMode<ThemeMode>(codec: EnumCodec(ThemeMode.values)),
|
||||||
themeDynamic<bool>(),
|
themeDynamic<bool>(),
|
||||||
themeColorfulInterface<bool>(),
|
themeColorfulInterface<bool>(),
|
||||||
|
|
||||||
@@ -26,13 +25,13 @@ enum SettingsKey<T extends Object> {
|
|||||||
|
|
||||||
// Network
|
// Network
|
||||||
networkAutoEndpointSwitching<bool>(),
|
networkAutoEndpointSwitching<bool>(),
|
||||||
networkPreferredWifiName<String>(),
|
networkExternalEndpointList<List<String>>(codec: ListCodec(PrimitiveCodec.string)),
|
||||||
networkLocalEndpoint<String>(),
|
networkCustomHeaders<Map<String, String>>(codec: MapCodec(PrimitiveCodec.string, PrimitiveCodec.string)),
|
||||||
networkExternalEndpointList<List<String>>(codec: _ListCodec(_PrimitiveCodec.string)),
|
networkPreferredWifiName<String?>(),
|
||||||
networkCustomHeaders<Map<String, String>>(codec: _MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string)),
|
networkLocalEndpoint<String?>(),
|
||||||
|
|
||||||
// Album
|
// Album
|
||||||
albumSortMode<AlbumSortMode>(codec: _EnumCodec(AlbumSortMode.values)),
|
albumSortMode<AlbumSortMode>(codec: EnumCodec(AlbumSortMode.values)),
|
||||||
albumIsReverse<bool>(),
|
albumIsReverse<bool>(),
|
||||||
albumIsGrid<bool>(),
|
albumIsGrid<bool>(),
|
||||||
|
|
||||||
@@ -46,175 +45,48 @@ enum SettingsKey<T extends Object> {
|
|||||||
|
|
||||||
// Timeline
|
// Timeline
|
||||||
timelineTilesPerRow<int>(),
|
timelineTilesPerRow<int>(),
|
||||||
timelineGroupAssetsBy<GroupAssetsBy>(codec: _EnumCodec(GroupAssetsBy.values)),
|
timelineGroupAssetsBy<GroupAssetsBy>(codec: EnumCodec(GroupAssetsBy.values)),
|
||||||
timelineStorageIndicator<bool>(),
|
timelineStorageIndicator<bool>(),
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
logLevel<LogLevel>(codec: _EnumCodec(LogLevel.values)),
|
logLevel<LogLevel>(codec: EnumCodec(LogLevel.values)),
|
||||||
|
|
||||||
// Map
|
// Map
|
||||||
mapShowFavoriteOnly<bool>(),
|
mapShowFavoriteOnly<bool>(),
|
||||||
mapRelativeDate<int>(),
|
mapRelativeDate<int>(),
|
||||||
mapIncludeArchived<bool>(),
|
mapIncludeArchived<bool>(),
|
||||||
mapThemeMode<ThemeMode>(codec: _EnumCodec(ThemeMode.values)),
|
mapThemeMode<ThemeMode>(codec: EnumCodec(ThemeMode.values)),
|
||||||
mapWithPartners<bool>(),
|
mapWithPartners<bool>(),
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
cleanupKeepFavorites<bool>(),
|
cleanupKeepFavorites<bool>(),
|
||||||
cleanupKeepMediaType<AssetKeepType>(codec: _EnumCodec(AssetKeepType.values)),
|
cleanupKeepMediaType<AssetKeepType>(codec: EnumCodec(AssetKeepType.values)),
|
||||||
cleanupKeepAlbumIds<List<String>>(codec: _ListCodec(_PrimitiveCodec.string)),
|
cleanupKeepAlbumIds<List<String>>(codec: ListCodec(PrimitiveCodec.string)),
|
||||||
cleanupCutoffDaysAgo<int>(),
|
cleanupCutoffDaysAgo<int>(),
|
||||||
cleanupDefaultsInitialized<bool>(),
|
cleanupDefaultsInitialized<bool>(),
|
||||||
|
|
||||||
// Share
|
// Share
|
||||||
shareFileType<ShareAssetType>(codec: _EnumCodec(ShareAssetType.values)),
|
shareFileType<ShareAssetType>(codec: EnumCodec(ShareAssetType.values)),
|
||||||
|
|
||||||
// Slideshow
|
// Slideshow
|
||||||
slideshowTransition<bool>(),
|
slideshowTransition<bool>(),
|
||||||
slideshowRepeat<bool>(),
|
slideshowRepeat<bool>(),
|
||||||
slideshowDuration<int>(),
|
slideshowDuration<int>(),
|
||||||
slideshowLook<SlideshowLook>(codec: _EnumCodec(SlideshowLook.values)),
|
slideshowLook<SlideshowLook>(codec: EnumCodec(SlideshowLook.values)),
|
||||||
slideshowDirection<SlideshowDirection>(codec: _EnumCodec(SlideshowDirection.values));
|
slideshowDirection<SlideshowDirection>(codec: EnumCodec(SlideshowDirection.values)),
|
||||||
|
|
||||||
final _SettingsCodec<T>? _codecOverride;
|
// Advanced
|
||||||
|
advancedTroubleshooting<bool>(),
|
||||||
|
advancedEnableHapticFeedback<bool>(),
|
||||||
|
advancedReadonlyModeEnabled<bool>();
|
||||||
|
|
||||||
const SettingsKey({_SettingsCodec<T>? codec}) : _codecOverride = codec;
|
final ValueCodec<T>? _codecOverride;
|
||||||
|
|
||||||
_SettingsCodec<T> get _codec => _codecOverride ?? _SettingsCodec.forType(T);
|
const SettingsKey({ValueCodec<T>? codec}) : _codecOverride = codec;
|
||||||
|
|
||||||
|
ValueCodec<T> get _codec => _codecOverride ?? ValueCodec.forType(T);
|
||||||
|
|
||||||
String encode(T value) => _codec.encode(value);
|
String encode(T value) => _codec.encode(value);
|
||||||
|
|
||||||
T decode(String raw) => _codec.decode(raw);
|
T decode(String raw) => _codec.decode(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class _SettingsCodec<T extends Object> {
|
|
||||||
const _SettingsCodec();
|
|
||||||
|
|
||||||
String encode(T value);
|
|
||||||
T decode(String raw);
|
|
||||||
|
|
||||||
static const Map<Type, _SettingsCodec<Object>> _primitives = {
|
|
||||||
int: _PrimitiveCodec.integer,
|
|
||||||
double: _PrimitiveCodec.real,
|
|
||||||
bool: _PrimitiveCodec.boolean,
|
|
||||||
String: _PrimitiveCodec.string,
|
|
||||||
DateTime: _DateTimeCodec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
static _SettingsCodec<T> forType<T extends Object>(Type runtimeType) {
|
|
||||||
final codec = _primitives[runtimeType];
|
|
||||||
if (codec == null) {
|
|
||||||
throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the SettingsKey.');
|
|
||||||
}
|
|
||||||
return codec as _SettingsCodec<T>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _EnumCodec<T extends Enum> extends _SettingsCodec<T> {
|
|
||||||
final List<T> values;
|
|
||||||
|
|
||||||
const _EnumCodec(this.values);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(T value) => value.name;
|
|
||||||
|
|
||||||
@override
|
|
||||||
T decode(String raw) => values.firstWhere((v) => v.name == raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _DateTimeCodec extends _SettingsCodec<DateTime> {
|
|
||||||
const _DateTimeCodec();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(DateTime value) => value.toIso8601String();
|
|
||||||
|
|
||||||
@override
|
|
||||||
DateTime decode(String raw) => DateTime.parse(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _MapCodec<K extends Object, V extends Object> extends _SettingsCodec<Map<K, V>> {
|
|
||||||
final _SettingsCodec<K> _keyCodec;
|
|
||||||
final _SettingsCodec<V> _valueCodec;
|
|
||||||
|
|
||||||
const _MapCodec(this._keyCodec, this._valueCodec);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(Map<K, V> value) {
|
|
||||||
final entries = <String, String>{};
|
|
||||||
value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v));
|
|
||||||
return jsonEncode(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<K, V> decode(String raw) {
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(raw);
|
|
||||||
if (decoded is! Map) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
final result = <K, V>{};
|
|
||||||
for (final entry in decoded.entries) {
|
|
||||||
final rawKey = entry.key;
|
|
||||||
final rawValue = entry.value;
|
|
||||||
if (rawKey is! String || rawValue is! String) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
final k = _keyCodec.decode(rawKey);
|
|
||||||
final v = _valueCodec.decode(rawValue);
|
|
||||||
result[k] = v;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} on FormatException {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _ListCodec<T extends Object> extends _SettingsCodec<List<T>> {
|
|
||||||
final _SettingsCodec<T> _elementCodec;
|
|
||||||
|
|
||||||
const _ListCodec(this._elementCodec);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(List<T> value) => jsonEncode(value.map(_elementCodec.encode).toList());
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<T> decode(String raw) {
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(raw);
|
|
||||||
if (decoded is! List) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final result = <T>[];
|
|
||||||
for (final item in decoded) {
|
|
||||||
if (item is! String) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final element = _elementCodec.decode(item);
|
|
||||||
result.add(element);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} on FormatException {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _PrimitiveCodec<T extends Object> extends _SettingsCodec<T> {
|
|
||||||
final T Function(String) _parse;
|
|
||||||
|
|
||||||
const _PrimitiveCodec._(this._parse);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(T value) => value.toString();
|
|
||||||
|
|
||||||
@override
|
|
||||||
T decode(String raw) => _parse(raw);
|
|
||||||
|
|
||||||
static const integer = _PrimitiveCodec<int>._(int.parse);
|
|
||||||
static const real = _PrimitiveCodec<double>._(double.parse);
|
|
||||||
static const boolean = _PrimitiveCodec<bool>._(bool.parse);
|
|
||||||
static const string = _PrimitiveCodec<String>._(_identity);
|
|
||||||
|
|
||||||
static String _identity(String s) => s;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
|
||||||
|
|
||||||
/// Key for each possible value in the `Store`.
|
/// Key for each possible value in the `Store`.
|
||||||
/// Defines the data type for each value
|
/// Defines the data type for each value
|
||||||
enum StoreKey<T> {
|
enum StoreKey<T> {
|
||||||
version<int>._(0),
|
|
||||||
currentUser<UserDto>._(2),
|
|
||||||
deviceId<String>._(4),
|
deviceId<String>._(4),
|
||||||
serverUrl<String>._(10),
|
|
||||||
accessToken<String>._(11),
|
|
||||||
serverEndpoint<String>._(12),
|
|
||||||
advancedTroubleshooting<bool>._(114),
|
|
||||||
enableHapticFeedback<bool>._(126),
|
|
||||||
|
|
||||||
manageLocalMediaAndroid<bool>._(137),
|
|
||||||
// Read-only Mode settings
|
|
||||||
readonlyModeEnabled<bool>._(138),
|
|
||||||
|
|
||||||
syncMigrationStatus<String>._(1013),
|
|
||||||
|
|
||||||
// Legacy keys that have been migrated to the new metadata store
|
// Legacy keys that have been migrated to the new metadata store
|
||||||
|
legacyVersion<int>._(0),
|
||||||
|
legacyManageLocalMediaAndroid<bool>._(137),
|
||||||
|
legacySyncMigrationStatus<String>._(1013),
|
||||||
|
legacyAdvancedTroubleshooting<bool>._(114),
|
||||||
|
legacyEnableHapticFeedback<bool>._(126),
|
||||||
|
legacyReadonlyModeEnabled<bool>._(138),
|
||||||
|
legacyServerUrl<String>._(10),
|
||||||
|
legacyAccessToken<String>._(11),
|
||||||
|
legacyServerEndpoint<String>._(12),
|
||||||
legacyBackupRequireCharging<bool>._(7),
|
legacyBackupRequireCharging<bool>._(7),
|
||||||
legacyBackupTriggerDelay<int>._(8),
|
legacyBackupTriggerDelay<int>._(8),
|
||||||
legacySyncAlbums<bool>._(131),
|
legacySyncAlbums<bool>._(131),
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
sealed class ValueCodec<T> {
|
||||||
|
const ValueCodec();
|
||||||
|
|
||||||
|
String encode(T value);
|
||||||
|
T decode(String raw);
|
||||||
|
|
||||||
|
static final Map<Type, ValueCodec<Object>> _primitives = {
|
||||||
|
..._register<int>(PrimitiveCodec.integer),
|
||||||
|
..._register<double>(PrimitiveCodec.real),
|
||||||
|
..._register<bool>(PrimitiveCodec.boolean),
|
||||||
|
..._register<String>(PrimitiveCodec.string),
|
||||||
|
..._register<DateTime>(const DateTimeCodec()),
|
||||||
|
};
|
||||||
|
|
||||||
|
static Map<Type, ValueCodec<Object>> _register<T>(ValueCodec<Object> codec) => {
|
||||||
|
T: codec,
|
||||||
|
// Reifies the nullable type T so it can be used as a key in the _primitives map
|
||||||
|
_typeOf<T?>(): codec,
|
||||||
|
};
|
||||||
|
|
||||||
|
static Type _typeOf<T>() => T;
|
||||||
|
|
||||||
|
static ValueCodec<T> forType<T>(Type runtimeType) {
|
||||||
|
final codec = _primitives[runtimeType];
|
||||||
|
if (codec == null) {
|
||||||
|
throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the key.');
|
||||||
|
}
|
||||||
|
return codec as ValueCodec<T>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class EnumCodec<T extends Enum> extends ValueCodec<T> {
|
||||||
|
final List<T> values;
|
||||||
|
|
||||||
|
const EnumCodec(this.values);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(T value) => value.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
T decode(String raw) => values.firstWhere((v) => v.name == raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DateTimeCodec extends ValueCodec<DateTime> {
|
||||||
|
const DateTimeCodec();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(DateTime value) => value.toIso8601String();
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime decode(String raw) => DateTime.parse(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MapCodec<K extends Object, V extends Object> extends ValueCodec<Map<K, V>> {
|
||||||
|
final ValueCodec<K> _keyCodec;
|
||||||
|
final ValueCodec<V> _valueCodec;
|
||||||
|
|
||||||
|
const MapCodec(this._keyCodec, this._valueCodec);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(Map<K, V> value) {
|
||||||
|
final entries = <String, String>{};
|
||||||
|
value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v));
|
||||||
|
return jsonEncode(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<K, V> decode(String raw) {
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(raw);
|
||||||
|
if (decoded is! Map) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
final result = <K, V>{};
|
||||||
|
for (final entry in decoded.entries) {
|
||||||
|
final rawKey = entry.key;
|
||||||
|
final rawValue = entry.value;
|
||||||
|
if (rawKey is! String || rawValue is! String) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final k = _keyCodec.decode(rawKey);
|
||||||
|
final v = _valueCodec.decode(rawValue);
|
||||||
|
result[k] = v;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} on FormatException {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ListCodec<T extends Object> extends ValueCodec<List<T>> {
|
||||||
|
final ValueCodec<T> _elementCodec;
|
||||||
|
|
||||||
|
const ListCodec(this._elementCodec);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(List<T> value) => jsonEncode(value.map(_elementCodec.encode).toList());
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<T> decode(String raw) {
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(raw);
|
||||||
|
if (decoded is! List) {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
final result = <T>[];
|
||||||
|
for (final item in decoded) {
|
||||||
|
if (item is! String) {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
final element = _elementCodec.decode(item);
|
||||||
|
result.add(element);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} on FormatException {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PrimitiveCodec<T extends Object> extends ValueCodec<T> {
|
||||||
|
final T Function(String) _parse;
|
||||||
|
|
||||||
|
const PrimitiveCodec._(this._parse);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(T value) => value.toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
T decode(String raw) => _parse(raw);
|
||||||
|
|
||||||
|
static const integer = PrimitiveCodec<int>._(int.parse);
|
||||||
|
static const real = PrimitiveCodec<double>._(double.parse);
|
||||||
|
static const boolean = PrimitiveCodec<bool>._(bool.parse);
|
||||||
|
static const string = PrimitiveCodec<String>._(_identity);
|
||||||
|
|
||||||
|
static String _identity(String s) => s;
|
||||||
|
}
|
||||||
@@ -5,9 +5,8 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||||
@@ -28,6 +27,7 @@ class LocalSyncService {
|
|||||||
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
||||||
final AssetMediaRepository _assetMediaRepository;
|
final AssetMediaRepository _assetMediaRepository;
|
||||||
final IPermissionRepository _permissionRepository;
|
final IPermissionRepository _permissionRepository;
|
||||||
|
final AppMetadataRepository _appMetadataRepository;
|
||||||
final Completer<void>? _cancellation;
|
final Completer<void>? _cancellation;
|
||||||
final Logger _log = Logger("DeviceSyncService");
|
final Logger _log = Logger("DeviceSyncService");
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ class LocalSyncService {
|
|||||||
required this._trashedLocalAssetRepository,
|
required this._trashedLocalAssetRepository,
|
||||||
required this._assetMediaRepository,
|
required this._assetMediaRepository,
|
||||||
required this._permissionRepository,
|
required this._permissionRepository,
|
||||||
|
required this._appMetadataRepository,
|
||||||
this._cancellation,
|
this._cancellation,
|
||||||
}) {
|
}) {
|
||||||
_cancellation?.future.then((_) => _nativeSyncApi.cancelSync().onError(_log.warning));
|
_cancellation?.future.then((_) => _nativeSyncApi.cancelSync().onError(_log.warning));
|
||||||
@@ -48,7 +49,7 @@ class LocalSyncService {
|
|||||||
Future<void> sync({bool full = false}) async {
|
Future<void> sync({bool full = false}) async {
|
||||||
final Stopwatch stopwatch = Stopwatch()..start();
|
final Stopwatch stopwatch = Stopwatch()..start();
|
||||||
try {
|
try {
|
||||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
if (CurrentPlatform.isAndroid && await _appMetadataRepository.get(.manageLocalMediaAndroid)) {
|
||||||
final hasPermission = await _permissionRepository.hasManageMediaPermission();
|
final hasPermission = await _permissionRepository.hasManageMediaPermission();
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
await _syncTrashedAssets();
|
await _syncTrashedAssets();
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
|
||||||
|
|
||||||
// Singleton instance of SettingsService, to use in places
|
|
||||||
// where reactivity is not required
|
|
||||||
// ignore: non_constant_identifier_names
|
|
||||||
final AppSetting = SettingsService(storeService: StoreService.I);
|
|
||||||
|
|
||||||
class SettingsService {
|
|
||||||
final StoreService _storeService;
|
|
||||||
|
|
||||||
const SettingsService({required this._storeService});
|
|
||||||
|
|
||||||
T get<T>(Setting<T> setting) => _storeService.get(setting.storeKey, setting.defaultValue);
|
|
||||||
|
|
||||||
Future<void> set<T>(Setting<T> setting, T value) => _storeService.put(setting.storeKey, value);
|
|
||||||
|
|
||||||
Stream<T> watch<T>(Setting<T> setting) => _storeService.watch(setting.storeKey).map((v) => v ?? setting.defaultValue);
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,12 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -18,7 +17,7 @@ final syncLinkedAlbumServiceProvider = Provider(
|
|||||||
ref.watch(localAlbumRepository),
|
ref.watch(localAlbumRepository),
|
||||||
ref.watch(remoteAlbumRepository),
|
ref.watch(remoteAlbumRepository),
|
||||||
ref.watch(driftAlbumApiRepositoryProvider),
|
ref.watch(driftAlbumApiRepositoryProvider),
|
||||||
ref.watch(storeServiceProvider),
|
ref.watch(authUserRepositoryProvider),
|
||||||
cancellation: ref.watch(cancellationProvider),
|
cancellation: ref.watch(cancellationProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -27,14 +26,14 @@ class SyncLinkedAlbumService {
|
|||||||
final DriftLocalAlbumRepository _localAlbumRepository;
|
final DriftLocalAlbumRepository _localAlbumRepository;
|
||||||
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
||||||
final DriftAlbumApiRepository _albumApiRepository;
|
final DriftAlbumApiRepository _albumApiRepository;
|
||||||
final StoreService _storeService;
|
final DriftAuthUserRepository _authUserRepository;
|
||||||
final Completer<void>? _cancellation;
|
final Completer<void>? _cancellation;
|
||||||
|
|
||||||
SyncLinkedAlbumService(
|
SyncLinkedAlbumService(
|
||||||
this._localAlbumRepository,
|
this._localAlbumRepository,
|
||||||
this._remoteAlbumRepository,
|
this._remoteAlbumRepository,
|
||||||
this._albumApiRepository,
|
this._albumApiRepository,
|
||||||
this._storeService, {
|
this._authUserRepository, {
|
||||||
this._cancellation,
|
this._cancellation,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -123,11 +122,12 @@ class SyncLinkedAlbumService {
|
|||||||
/// Creates a new remote album and links it to the local album
|
/// Creates a new remote album and links it to the local album
|
||||||
Future<void> _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async {
|
Future<void> _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async {
|
||||||
dPrint(() => "Creating new remote album for local album: ${localAlbum.name}");
|
dPrint(() => "Creating new remote album for local album: ${localAlbum.name}");
|
||||||
final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(
|
final currentUser = await _authUserRepository.get();
|
||||||
localAlbum.name,
|
if (currentUser == null) {
|
||||||
_storeService.get(StoreKey.currentUser),
|
_log.warning("No user logged in, skipping remote album creation for local album: ${localAlbum.name}");
|
||||||
assetIds: [],
|
return;
|
||||||
);
|
}
|
||||||
|
final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(localAlbum.name, currentUser, assetIds: []);
|
||||||
await _remoteAlbumRepository.create(newRemoteAlbum, []);
|
await _remoteAlbumRepository.create(newRemoteAlbum, []);
|
||||||
return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id);
|
return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
// ignore_for_file: constant_identifier_names
|
// ignore_for_file: constant_identifier_names
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart';
|
||||||
@@ -38,6 +36,7 @@ class SyncStreamService {
|
|||||||
final IPermissionRepository _permissionRepository;
|
final IPermissionRepository _permissionRepository;
|
||||||
final SyncMigrationRepository _syncMigrationRepository;
|
final SyncMigrationRepository _syncMigrationRepository;
|
||||||
final ApiService _api;
|
final ApiService _api;
|
||||||
|
final AppMetadataRepository _appMetadataRepository;
|
||||||
final Completer<void>? _cancellation;
|
final Completer<void>? _cancellation;
|
||||||
|
|
||||||
SyncStreamService({
|
SyncStreamService({
|
||||||
@@ -49,10 +48,12 @@ class SyncStreamService {
|
|||||||
required this._permissionRepository,
|
required this._permissionRepository,
|
||||||
required this._syncMigrationRepository,
|
required this._syncMigrationRepository,
|
||||||
required this._api,
|
required this._api,
|
||||||
|
required this._appMetadataRepository,
|
||||||
this._cancellation,
|
this._cancellation,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool get isCancelled => _cancellation?.isCompleted ?? false;
|
bool get isCancelled => _cancellation?.isCompleted ?? false;
|
||||||
|
bool _manageLocalMediaAndroid = false;
|
||||||
|
|
||||||
Future<bool> sync() async {
|
Future<bool> sync() async {
|
||||||
_logger.info("Remote sync request for user");
|
_logger.info("Remote sync request for user");
|
||||||
@@ -64,16 +65,17 @@ class SyncStreamService {
|
|||||||
|
|
||||||
final serverSemVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_);
|
final serverSemVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_);
|
||||||
|
|
||||||
final value = Store.get(StoreKey.syncMigrationStatus, "[]");
|
final migrations = (await _appMetadataRepository.get(.syncMigrationStatus)).toList();
|
||||||
final migrations = (jsonDecode(value) as List).cast<String>();
|
|
||||||
int previousLength = migrations.length;
|
int previousLength = migrations.length;
|
||||||
await _runPreSyncTasks(migrations, serverSemVer);
|
await _runPreSyncTasks(migrations, serverSemVer);
|
||||||
|
|
||||||
if (migrations.length != previousLength) {
|
if (migrations.length != previousLength) {
|
||||||
_logger.info("Updated pre-sync migration status: $migrations");
|
_logger.info("Updated pre-sync migration status: $migrations");
|
||||||
await Store.put(StoreKey.syncMigrationStatus, jsonEncode(migrations));
|
await _appMetadataRepository.set(.syncMigrationStatus, migrations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_manageLocalMediaAndroid = CurrentPlatform.isAndroid && await _appMetadataRepository.get(.manageLocalMediaAndroid);
|
||||||
|
|
||||||
// Start the sync stream and handle events
|
// Start the sync stream and handle events
|
||||||
bool shouldReset = false;
|
bool shouldReset = false;
|
||||||
await _syncApiRepository.streamChanges(
|
await _syncApiRepository.streamChanges(
|
||||||
@@ -96,7 +98,7 @@ class SyncStreamService {
|
|||||||
|
|
||||||
if (migrations.length != previousLength) {
|
if (migrations.length != previousLength) {
|
||||||
_logger.info("Updated pre-sync migration status: $migrations");
|
_logger.info("Updated pre-sync migration status: $migrations");
|
||||||
await Store.put(StoreKey.syncMigrationStatus, jsonEncode(migrations));
|
await _appMetadataRepository.set(.syncMigrationStatus, migrations);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -106,10 +108,10 @@ class SyncStreamService {
|
|||||||
if (!migrations.contains(SyncMigrationTask.v20260128_ResetExifV1.name)) {
|
if (!migrations.contains(SyncMigrationTask.v20260128_ResetExifV1.name)) {
|
||||||
_logger.info("Running pre-sync task: v20260128_ResetExifV1");
|
_logger.info("Running pre-sync task: v20260128_ResetExifV1");
|
||||||
await _syncApiRepository.deleteSyncAck([
|
await _syncApiRepository.deleteSyncAck([
|
||||||
SyncEntityType.assetExifV1,
|
.assetExifV1,
|
||||||
SyncEntityType.partnerAssetExifV1,
|
.partnerAssetExifV1,
|
||||||
SyncEntityType.albumAssetExifCreateV1,
|
.albumAssetExifCreateV1,
|
||||||
SyncEntityType.albumAssetExifUpdateV1,
|
.albumAssetExifUpdateV1,
|
||||||
]);
|
]);
|
||||||
migrations.add(SyncMigrationTask.v20260128_ResetExifV1.name);
|
migrations.add(SyncMigrationTask.v20260128_ResetExifV1.name);
|
||||||
}
|
}
|
||||||
@@ -117,12 +119,7 @@ class SyncStreamService {
|
|||||||
if (!migrations.contains(SyncMigrationTask.v20260128_ResetAssetV1.name) &&
|
if (!migrations.contains(SyncMigrationTask.v20260128_ResetAssetV1.name) &&
|
||||||
semVer >= const SemVer(major: 2, minor: 5, patch: 0)) {
|
semVer >= const SemVer(major: 2, minor: 5, patch: 0)) {
|
||||||
_logger.info("Running pre-sync task: v20260128_ResetAssetV1");
|
_logger.info("Running pre-sync task: v20260128_ResetAssetV1");
|
||||||
await _syncApiRepository.deleteSyncAck([
|
await _syncApiRepository.deleteSyncAck([.assetV1, .partnerAssetV1, .albumAssetCreateV1, .albumAssetUpdateV1]);
|
||||||
SyncEntityType.assetV1,
|
|
||||||
SyncEntityType.partnerAssetV1,
|
|
||||||
SyncEntityType.albumAssetCreateV1,
|
|
||||||
SyncEntityType.albumAssetUpdateV1,
|
|
||||||
]);
|
|
||||||
|
|
||||||
migrations.add(SyncMigrationTask.v20260128_ResetAssetV1.name);
|
migrations.add(SyncMigrationTask.v20260128_ResetAssetV1.name);
|
||||||
|
|
||||||
@@ -134,7 +131,7 @@ class SyncStreamService {
|
|||||||
if (!migrations.contains(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name) &&
|
if (!migrations.contains(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name) &&
|
||||||
semVer > const SemVer(major: 2, minor: 7, patch: 5)) {
|
semVer > const SemVer(major: 2, minor: 7, patch: 5)) {
|
||||||
_logger.info("Running pre-sync task: v20260597_ResetAssetV1AssetV2");
|
_logger.info("Running pre-sync task: v20260597_ResetAssetV1AssetV2");
|
||||||
await _syncApiRepository.deleteSyncAck([SyncEntityType.assetV1, SyncEntityType.assetV2]);
|
await _syncApiRepository.deleteSyncAck([.assetV1, .assetV2]);
|
||||||
migrations.add(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name);
|
migrations.add(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,20 +194,20 @@ class SyncStreamService {
|
|||||||
case SyncEntityType.assetV1:
|
case SyncEntityType.assetV1:
|
||||||
final remoteSyncAssets = data.cast<SyncAssetV1>();
|
final remoteSyncAssets = data.cast<SyncAssetV1>();
|
||||||
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
|
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
|
||||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
if (_manageLocalMediaAndroid) {
|
||||||
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SyncEntityType.assetV2:
|
case SyncEntityType.assetV2:
|
||||||
final remoteSyncAssets = data.cast<SyncAssetV2>();
|
final remoteSyncAssets = data.cast<SyncAssetV2>();
|
||||||
await _syncStreamRepository.updateAssetsV2(remoteSyncAssets);
|
await _syncStreamRepository.updateAssetsV2(remoteSyncAssets);
|
||||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
if (_manageLocalMediaAndroid) {
|
||||||
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SyncEntityType.assetDeleteV1:
|
case SyncEntityType.assetDeleteV1:
|
||||||
final remoteSyncAssets = data.cast<SyncAssetDeleteV1>();
|
final remoteSyncAssets = data.cast<SyncAssetDeleteV1>();
|
||||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
if (_manageLocalMediaAndroid) {
|
||||||
await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList());
|
await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList());
|
||||||
}
|
}
|
||||||
return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets);
|
return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets);
|
||||||
|
|||||||
@@ -1,29 +1,24 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class UserService {
|
class UserService {
|
||||||
final Logger _log = Logger("UserService");
|
final Logger _log = Logger("UserService");
|
||||||
final UserApiRepository _userApiRepository;
|
final UserApiRepository _userApiRepository;
|
||||||
final StoreService _storeService;
|
final DriftAuthUserRepository _authUserRepository;
|
||||||
|
|
||||||
UserService({required this._userApiRepository, required this._storeService});
|
UserService({required this._userApiRepository, required this._authUserRepository});
|
||||||
|
|
||||||
UserDto getMyUser() {
|
Future<UserDto?> tryGetMyUser() {
|
||||||
return _storeService.get(StoreKey.currentUser);
|
return _authUserRepository.get();
|
||||||
}
|
|
||||||
|
|
||||||
UserDto? tryGetMyUser() {
|
|
||||||
return _storeService.tryGet(StoreKey.currentUser);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<UserDto?> watchMyUser() {
|
Stream<UserDto?> watchMyUser() {
|
||||||
return _storeService.watch(StoreKey.currentUser);
|
return _authUserRepository.watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UserDto?> refreshMyUser() async {
|
Future<UserDto?> refreshMyUser() async {
|
||||||
@@ -31,15 +26,17 @@ class UserService {
|
|||||||
if (user == null) {
|
if (user == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
await _storeService.put(StoreKey.currentUser, user);
|
await _authUserRepository.upsert(user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> createProfileImage(String name, Uint8List image) async {
|
Future<String?> createProfileImage(String name, Uint8List image) async {
|
||||||
try {
|
try {
|
||||||
final path = await _userApiRepository.createProfileImage(name: name, data: image);
|
final path = await _userApiRepository.createProfileImage(name: name, data: image);
|
||||||
final updatedUser = getMyUser();
|
final updatedUser = await tryGetMyUser();
|
||||||
await _storeService.put(StoreKey.currentUser, updatedUser);
|
if (updatedUser != null) {
|
||||||
|
await _authUserRepository.upsert(updatedUser);
|
||||||
|
}
|
||||||
return path;
|
return path;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_log.warning("Failed to upload profile image", e);
|
_log.warning("Failed to upload profile image", e);
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
|
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
Future<void> syncLinkedAlbumsIsolated(ProviderContainer ref) {
|
Future<void> syncLinkedAlbumsIsolated(ProviderContainer ref) async {
|
||||||
final user = Store.tryGet(StoreKey.currentUser);
|
final user = await ref.read(authUserRepositoryProvider).get();
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
Logger("SyncLinkedAlbum").warning("No user logged in, skipping linked album sync");
|
Logger("SyncLinkedAlbum").warning("No user logged in, skipping linked album sync");
|
||||||
return Future.value();
|
return;
|
||||||
}
|
}
|
||||||
return ref.read(syncLinkedAlbumServiceProvider).syncLinkedAlbums(user.id);
|
return ref.read(syncLinkedAlbumServiceProvider).syncLinkedAlbums(user.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
class AppMetadataEntity extends Table with DriftDefaultsMixin {
|
||||||
|
const AppMetadataEntity();
|
||||||
|
|
||||||
|
TextColumn get key => text()();
|
||||||
|
|
||||||
|
TextColumn get value => text().nullable()();
|
||||||
|
|
||||||
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {key};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tableName => "app_metadata";
|
||||||
|
}
|
||||||
@@ -0,0 +1,434 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart' as i0;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.drift.dart'
|
||||||
|
as i1;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.dart'
|
||||||
|
as i2;
|
||||||
|
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
||||||
|
|
||||||
|
typedef $$AppMetadataEntityTableCreateCompanionBuilder =
|
||||||
|
i1.AppMetadataEntityCompanion Function({
|
||||||
|
required String key,
|
||||||
|
i0.Value<String?> value,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
});
|
||||||
|
typedef $$AppMetadataEntityTableUpdateCompanionBuilder =
|
||||||
|
i1.AppMetadataEntityCompanion Function({
|
||||||
|
i0.Value<String> key,
|
||||||
|
i0.Value<String?> value,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$AppMetadataEntityTableFilterComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$AppMetadataEntityTable> {
|
||||||
|
$$AppMetadataEntityTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnFilters<String> get key => $composableBuilder(
|
||||||
|
column: $table.key,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<String> get value => $composableBuilder(
|
||||||
|
column: $table.value,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$AppMetadataEntityTableOrderingComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$AppMetadataEntityTable> {
|
||||||
|
$$AppMetadataEntityTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnOrderings<String> get key => $composableBuilder(
|
||||||
|
column: $table.key,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<String> get value => $composableBuilder(
|
||||||
|
column: $table.value,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$AppMetadataEntityTableAnnotationComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$AppMetadataEntityTable> {
|
||||||
|
$$AppMetadataEntityTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.GeneratedColumn<String> get key =>
|
||||||
|
$composableBuilder(column: $table.key, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<String> get value =>
|
||||||
|
$composableBuilder(column: $table.value, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$AppMetadataEntityTableTableManager
|
||||||
|
extends
|
||||||
|
i0.RootTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$AppMetadataEntityTable,
|
||||||
|
i1.AppMetadataEntityData,
|
||||||
|
i1.$$AppMetadataEntityTableFilterComposer,
|
||||||
|
i1.$$AppMetadataEntityTableOrderingComposer,
|
||||||
|
i1.$$AppMetadataEntityTableAnnotationComposer,
|
||||||
|
$$AppMetadataEntityTableCreateCompanionBuilder,
|
||||||
|
$$AppMetadataEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.AppMetadataEntityData,
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$AppMetadataEntityTable,
|
||||||
|
i1.AppMetadataEntityData
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
i1.AppMetadataEntityData,
|
||||||
|
i0.PrefetchHooks Function()
|
||||||
|
> {
|
||||||
|
$$AppMetadataEntityTableTableManager(
|
||||||
|
i0.GeneratedDatabase db,
|
||||||
|
i1.$AppMetadataEntityTable table,
|
||||||
|
) : super(
|
||||||
|
i0.TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
i1.$$AppMetadataEntityTableFilterComposer($db: db, $table: table),
|
||||||
|
createOrderingComposer: () => i1
|
||||||
|
.$$AppMetadataEntityTableOrderingComposer($db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
i1.$$AppMetadataEntityTableAnnotationComposer(
|
||||||
|
$db: db,
|
||||||
|
$table: table,
|
||||||
|
),
|
||||||
|
updateCompanionCallback:
|
||||||
|
({
|
||||||
|
i0.Value<String> key = const i0.Value.absent(),
|
||||||
|
i0.Value<String?> value = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
}) => i1.AppMetadataEntityCompanion(
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
),
|
||||||
|
createCompanionCallback:
|
||||||
|
({
|
||||||
|
required String key,
|
||||||
|
i0.Value<String?> value = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
}) => i1.AppMetadataEntityCompanion.insert(
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$AppMetadataEntityTableProcessedTableManager =
|
||||||
|
i0.ProcessedTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$AppMetadataEntityTable,
|
||||||
|
i1.AppMetadataEntityData,
|
||||||
|
i1.$$AppMetadataEntityTableFilterComposer,
|
||||||
|
i1.$$AppMetadataEntityTableOrderingComposer,
|
||||||
|
i1.$$AppMetadataEntityTableAnnotationComposer,
|
||||||
|
$$AppMetadataEntityTableCreateCompanionBuilder,
|
||||||
|
$$AppMetadataEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.AppMetadataEntityData,
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$AppMetadataEntityTable,
|
||||||
|
i1.AppMetadataEntityData
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
i1.AppMetadataEntityData,
|
||||||
|
i0.PrefetchHooks Function()
|
||||||
|
>;
|
||||||
|
|
||||||
|
class $AppMetadataEntityTable extends i2.AppMetadataEntity
|
||||||
|
with i0.TableInfo<$AppMetadataEntityTable, i1.AppMetadataEntityData> {
|
||||||
|
@override
|
||||||
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$AppMetadataEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const i0.VerificationMeta _keyMeta = const i0.VerificationMeta('key');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> key = i0.GeneratedColumn<String>(
|
||||||
|
'key',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _valueMeta = const i0.VerificationMeta(
|
||||||
|
'value',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> value = i0.GeneratedColumn<String>(
|
||||||
|
'value',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta(
|
||||||
|
'updatedAt',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<DateTime> updatedAt =
|
||||||
|
i0.GeneratedColumn<DateTime>(
|
||||||
|
'updated_at',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: i3.currentDateAndTime,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> get $columns => [key, value, updatedAt];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'app_metadata';
|
||||||
|
@override
|
||||||
|
i0.VerificationContext validateIntegrity(
|
||||||
|
i0.Insertable<i1.AppMetadataEntityData> instance, {
|
||||||
|
bool isInserting = false,
|
||||||
|
}) {
|
||||||
|
final context = i0.VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('key')) {
|
||||||
|
context.handle(
|
||||||
|
_keyMeta,
|
||||||
|
key.isAcceptableOrUnknown(data['key']!, _keyMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_keyMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('value')) {
|
||||||
|
context.handle(
|
||||||
|
_valueMeta,
|
||||||
|
value.isAcceptableOrUnknown(data['value']!, _valueMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('updated_at')) {
|
||||||
|
context.handle(
|
||||||
|
_updatedAtMeta,
|
||||||
|
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<i0.GeneratedColumn> get $primaryKey => {key};
|
||||||
|
@override
|
||||||
|
i1.AppMetadataEntityData map(
|
||||||
|
Map<String, dynamic> data, {
|
||||||
|
String? tablePrefix,
|
||||||
|
}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return i1.AppMetadataEntityData(
|
||||||
|
key: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}key'],
|
||||||
|
)!,
|
||||||
|
value: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}value'],
|
||||||
|
),
|
||||||
|
updatedAt: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.dateTime,
|
||||||
|
data['${effectivePrefix}updated_at'],
|
||||||
|
)!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$AppMetadataEntityTable createAlias(String alias) {
|
||||||
|
return $AppMetadataEntityTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get withoutRowId => true;
|
||||||
|
@override
|
||||||
|
bool get isStrict => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppMetadataEntityData extends i0.DataClass
|
||||||
|
implements i0.Insertable<i1.AppMetadataEntityData> {
|
||||||
|
final String key;
|
||||||
|
final String? value;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
const AppMetadataEntityData({
|
||||||
|
required this.key,
|
||||||
|
this.value,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['key'] = i0.Variable<String>(key);
|
||||||
|
if (!nullToAbsent || value != null) {
|
||||||
|
map['value'] = i0.Variable<String>(value);
|
||||||
|
}
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory AppMetadataEntityData.fromJson(
|
||||||
|
Map<String, dynamic> json, {
|
||||||
|
i0.ValueSerializer? serializer,
|
||||||
|
}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return AppMetadataEntityData(
|
||||||
|
key: serializer.fromJson<String>(json['key']),
|
||||||
|
value: serializer.fromJson<String?>(json['value']),
|
||||||
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'key': serializer.toJson<String>(key),
|
||||||
|
'value': serializer.toJson<String?>(value),
|
||||||
|
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.AppMetadataEntityData copyWith({
|
||||||
|
String? key,
|
||||||
|
i0.Value<String?> value = const i0.Value.absent(),
|
||||||
|
DateTime? updatedAt,
|
||||||
|
}) => i1.AppMetadataEntityData(
|
||||||
|
key: key ?? this.key,
|
||||||
|
value: value.present ? value.value : this.value,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
);
|
||||||
|
AppMetadataEntityData copyWithCompanion(i1.AppMetadataEntityCompanion data) {
|
||||||
|
return AppMetadataEntityData(
|
||||||
|
key: data.key.present ? data.key.value : this.key,
|
||||||
|
value: data.value.present ? data.value.value : this.value,
|
||||||
|
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('AppMetadataEntityData(')
|
||||||
|
..write('key: $key, ')
|
||||||
|
..write('value: $value, ')
|
||||||
|
..write('updatedAt: $updatedAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(key, value, updatedAt);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is i1.AppMetadataEntityData &&
|
||||||
|
other.key == this.key &&
|
||||||
|
other.value == this.value &&
|
||||||
|
other.updatedAt == this.updatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppMetadataEntityCompanion
|
||||||
|
extends i0.UpdateCompanion<i1.AppMetadataEntityData> {
|
||||||
|
final i0.Value<String> key;
|
||||||
|
final i0.Value<String?> value;
|
||||||
|
final i0.Value<DateTime> updatedAt;
|
||||||
|
const AppMetadataEntityCompanion({
|
||||||
|
this.key = const i0.Value.absent(),
|
||||||
|
this.value = const i0.Value.absent(),
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
});
|
||||||
|
AppMetadataEntityCompanion.insert({
|
||||||
|
required String key,
|
||||||
|
this.value = const i0.Value.absent(),
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
}) : key = i0.Value(key);
|
||||||
|
static i0.Insertable<i1.AppMetadataEntityData> custom({
|
||||||
|
i0.Expression<String>? key,
|
||||||
|
i0.Expression<String>? value,
|
||||||
|
i0.Expression<DateTime>? updatedAt,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (key != null) 'key': key,
|
||||||
|
if (value != null) 'value': value,
|
||||||
|
if (updatedAt != null) 'updated_at': updatedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.AppMetadataEntityCompanion copyWith({
|
||||||
|
i0.Value<String>? key,
|
||||||
|
i0.Value<String?>? value,
|
||||||
|
i0.Value<DateTime>? updatedAt,
|
||||||
|
}) {
|
||||||
|
return i1.AppMetadataEntityCompanion(
|
||||||
|
key: key ?? this.key,
|
||||||
|
value: value ?? this.value,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (key.present) {
|
||||||
|
map['key'] = i0.Variable<String>(key.value);
|
||||||
|
}
|
||||||
|
if (value.present) {
|
||||||
|
map['value'] = i0.Variable<String>(value.value);
|
||||||
|
}
|
||||||
|
if (updatedAt.present) {
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('AppMetadataEntityCompanion(')
|
||||||
|
..write('key: $key, ')
|
||||||
|
..write('value: $value, ')
|
||||||
|
..write('updatedAt: $updatedAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
class SessionEntity extends Table with DriftDefaultsMixin {
|
||||||
|
const SessionEntity();
|
||||||
|
|
||||||
|
TextColumn get key => text()();
|
||||||
|
|
||||||
|
TextColumn get value => text().nullable()();
|
||||||
|
|
||||||
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {key};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tableName => "session";
|
||||||
|
}
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart' as i0;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/session.entity.drift.dart'
|
||||||
|
as i1;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/session.entity.dart'
|
||||||
|
as i2;
|
||||||
|
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
||||||
|
|
||||||
|
typedef $$SessionEntityTableCreateCompanionBuilder =
|
||||||
|
i1.SessionEntityCompanion Function({
|
||||||
|
required String key,
|
||||||
|
i0.Value<String?> value,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
});
|
||||||
|
typedef $$SessionEntityTableUpdateCompanionBuilder =
|
||||||
|
i1.SessionEntityCompanion Function({
|
||||||
|
i0.Value<String> key,
|
||||||
|
i0.Value<String?> value,
|
||||||
|
i0.Value<DateTime> updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$SessionEntityTableFilterComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$SessionEntityTable> {
|
||||||
|
$$SessionEntityTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnFilters<String> get key => $composableBuilder(
|
||||||
|
column: $table.key,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<String> get value => $composableBuilder(
|
||||||
|
column: $table.value,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$SessionEntityTableOrderingComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$SessionEntityTable> {
|
||||||
|
$$SessionEntityTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnOrderings<String> get key => $composableBuilder(
|
||||||
|
column: $table.key,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<String> get value => $composableBuilder(
|
||||||
|
column: $table.value,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
|
||||||
|
column: $table.updatedAt,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$SessionEntityTableAnnotationComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$SessionEntityTable> {
|
||||||
|
$$SessionEntityTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.GeneratedColumn<String> get key =>
|
||||||
|
$composableBuilder(column: $table.key, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<String> get value =>
|
||||||
|
$composableBuilder(column: $table.value, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<DateTime> get updatedAt =>
|
||||||
|
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$SessionEntityTableTableManager
|
||||||
|
extends
|
||||||
|
i0.RootTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$SessionEntityTable,
|
||||||
|
i1.SessionEntityData,
|
||||||
|
i1.$$SessionEntityTableFilterComposer,
|
||||||
|
i1.$$SessionEntityTableOrderingComposer,
|
||||||
|
i1.$$SessionEntityTableAnnotationComposer,
|
||||||
|
$$SessionEntityTableCreateCompanionBuilder,
|
||||||
|
$$SessionEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.SessionEntityData,
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$SessionEntityTable,
|
||||||
|
i1.SessionEntityData
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
i1.SessionEntityData,
|
||||||
|
i0.PrefetchHooks Function()
|
||||||
|
> {
|
||||||
|
$$SessionEntityTableTableManager(
|
||||||
|
i0.GeneratedDatabase db,
|
||||||
|
i1.$SessionEntityTable table,
|
||||||
|
) : super(
|
||||||
|
i0.TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
i1.$$SessionEntityTableFilterComposer($db: db, $table: table),
|
||||||
|
createOrderingComposer: () =>
|
||||||
|
i1.$$SessionEntityTableOrderingComposer($db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
i1.$$SessionEntityTableAnnotationComposer($db: db, $table: table),
|
||||||
|
updateCompanionCallback:
|
||||||
|
({
|
||||||
|
i0.Value<String> key = const i0.Value.absent(),
|
||||||
|
i0.Value<String?> value = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
}) => i1.SessionEntityCompanion(
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
),
|
||||||
|
createCompanionCallback:
|
||||||
|
({
|
||||||
|
required String key,
|
||||||
|
i0.Value<String?> value = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
|
}) => i1.SessionEntityCompanion.insert(
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$SessionEntityTableProcessedTableManager =
|
||||||
|
i0.ProcessedTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$SessionEntityTable,
|
||||||
|
i1.SessionEntityData,
|
||||||
|
i1.$$SessionEntityTableFilterComposer,
|
||||||
|
i1.$$SessionEntityTableOrderingComposer,
|
||||||
|
i1.$$SessionEntityTableAnnotationComposer,
|
||||||
|
$$SessionEntityTableCreateCompanionBuilder,
|
||||||
|
$$SessionEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.SessionEntityData,
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$SessionEntityTable,
|
||||||
|
i1.SessionEntityData
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
i1.SessionEntityData,
|
||||||
|
i0.PrefetchHooks Function()
|
||||||
|
>;
|
||||||
|
|
||||||
|
class $SessionEntityTable extends i2.SessionEntity
|
||||||
|
with i0.TableInfo<$SessionEntityTable, i1.SessionEntityData> {
|
||||||
|
@override
|
||||||
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$SessionEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const i0.VerificationMeta _keyMeta = const i0.VerificationMeta('key');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> key = i0.GeneratedColumn<String>(
|
||||||
|
'key',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _valueMeta = const i0.VerificationMeta(
|
||||||
|
'value',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> value = i0.GeneratedColumn<String>(
|
||||||
|
'value',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta(
|
||||||
|
'updatedAt',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<DateTime> updatedAt =
|
||||||
|
i0.GeneratedColumn<DateTime>(
|
||||||
|
'updated_at',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: i3.currentDateAndTime,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> get $columns => [key, value, updatedAt];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'session';
|
||||||
|
@override
|
||||||
|
i0.VerificationContext validateIntegrity(
|
||||||
|
i0.Insertable<i1.SessionEntityData> instance, {
|
||||||
|
bool isInserting = false,
|
||||||
|
}) {
|
||||||
|
final context = i0.VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('key')) {
|
||||||
|
context.handle(
|
||||||
|
_keyMeta,
|
||||||
|
key.isAcceptableOrUnknown(data['key']!, _keyMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_keyMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('value')) {
|
||||||
|
context.handle(
|
||||||
|
_valueMeta,
|
||||||
|
value.isAcceptableOrUnknown(data['value']!, _valueMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('updated_at')) {
|
||||||
|
context.handle(
|
||||||
|
_updatedAtMeta,
|
||||||
|
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<i0.GeneratedColumn> get $primaryKey => {key};
|
||||||
|
@override
|
||||||
|
i1.SessionEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return i1.SessionEntityData(
|
||||||
|
key: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}key'],
|
||||||
|
)!,
|
||||||
|
value: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}value'],
|
||||||
|
),
|
||||||
|
updatedAt: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.dateTime,
|
||||||
|
data['${effectivePrefix}updated_at'],
|
||||||
|
)!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SessionEntityTable createAlias(String alias) {
|
||||||
|
return $SessionEntityTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get withoutRowId => true;
|
||||||
|
@override
|
||||||
|
bool get isStrict => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionEntityData extends i0.DataClass
|
||||||
|
implements i0.Insertable<i1.SessionEntityData> {
|
||||||
|
final String key;
|
||||||
|
final String? value;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
const SessionEntityData({
|
||||||
|
required this.key,
|
||||||
|
this.value,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['key'] = i0.Variable<String>(key);
|
||||||
|
if (!nullToAbsent || value != null) {
|
||||||
|
map['value'] = i0.Variable<String>(value);
|
||||||
|
}
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SessionEntityData.fromJson(
|
||||||
|
Map<String, dynamic> json, {
|
||||||
|
i0.ValueSerializer? serializer,
|
||||||
|
}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return SessionEntityData(
|
||||||
|
key: serializer.fromJson<String>(json['key']),
|
||||||
|
value: serializer.fromJson<String?>(json['value']),
|
||||||
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'key': serializer.toJson<String>(key),
|
||||||
|
'value': serializer.toJson<String?>(value),
|
||||||
|
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.SessionEntityData copyWith({
|
||||||
|
String? key,
|
||||||
|
i0.Value<String?> value = const i0.Value.absent(),
|
||||||
|
DateTime? updatedAt,
|
||||||
|
}) => i1.SessionEntityData(
|
||||||
|
key: key ?? this.key,
|
||||||
|
value: value.present ? value.value : this.value,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
);
|
||||||
|
SessionEntityData copyWithCompanion(i1.SessionEntityCompanion data) {
|
||||||
|
return SessionEntityData(
|
||||||
|
key: data.key.present ? data.key.value : this.key,
|
||||||
|
value: data.value.present ? data.value.value : this.value,
|
||||||
|
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SessionEntityData(')
|
||||||
|
..write('key: $key, ')
|
||||||
|
..write('value: $value, ')
|
||||||
|
..write('updatedAt: $updatedAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(key, value, updatedAt);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is i1.SessionEntityData &&
|
||||||
|
other.key == this.key &&
|
||||||
|
other.value == this.value &&
|
||||||
|
other.updatedAt == this.updatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionEntityCompanion extends i0.UpdateCompanion<i1.SessionEntityData> {
|
||||||
|
final i0.Value<String> key;
|
||||||
|
final i0.Value<String?> value;
|
||||||
|
final i0.Value<DateTime> updatedAt;
|
||||||
|
const SessionEntityCompanion({
|
||||||
|
this.key = const i0.Value.absent(),
|
||||||
|
this.value = const i0.Value.absent(),
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
});
|
||||||
|
SessionEntityCompanion.insert({
|
||||||
|
required String key,
|
||||||
|
this.value = const i0.Value.absent(),
|
||||||
|
this.updatedAt = const i0.Value.absent(),
|
||||||
|
}) : key = i0.Value(key);
|
||||||
|
static i0.Insertable<i1.SessionEntityData> custom({
|
||||||
|
i0.Expression<String>? key,
|
||||||
|
i0.Expression<String>? value,
|
||||||
|
i0.Expression<DateTime>? updatedAt,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (key != null) 'key': key,
|
||||||
|
if (value != null) 'value': value,
|
||||||
|
if (updatedAt != null) 'updated_at': updatedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.SessionEntityCompanion copyWith({
|
||||||
|
i0.Value<String>? key,
|
||||||
|
i0.Value<String?>? value,
|
||||||
|
i0.Value<DateTime>? updatedAt,
|
||||||
|
}) {
|
||||||
|
return i1.SessionEntityCompanion(
|
||||||
|
key: key ?? this.key,
|
||||||
|
value: value ?? this.value,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (key.present) {
|
||||||
|
map['key'] = i0.Variable<String>(key.value);
|
||||||
|
}
|
||||||
|
if (value.present) {
|
||||||
|
map['value'] = i0.Variable<String>(value.value);
|
||||||
|
}
|
||||||
|
if (updatedAt.present) {
|
||||||
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('SessionEntityCompanion(')
|
||||||
|
..write('key: $key, ')
|
||||||
|
..write('value: $value, ')
|
||||||
|
..write('updatedAt: $updatedAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ class SettingsEntity extends Table with DriftDefaultsMixin {
|
|||||||
|
|
||||||
TextColumn get key => text()();
|
TextColumn get key => text()();
|
||||||
|
|
||||||
TextColumn get value => text()();
|
TextColumn get value => text().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
|||||||
+20
-21
@@ -10,13 +10,13 @@ import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
|||||||
typedef $$SettingsEntityTableCreateCompanionBuilder =
|
typedef $$SettingsEntityTableCreateCompanionBuilder =
|
||||||
i1.SettingsEntityCompanion Function({
|
i1.SettingsEntityCompanion Function({
|
||||||
required String key,
|
required String key,
|
||||||
required String value,
|
i0.Value<String?> value,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
});
|
});
|
||||||
typedef $$SettingsEntityTableUpdateCompanionBuilder =
|
typedef $$SettingsEntityTableUpdateCompanionBuilder =
|
||||||
i1.SettingsEntityCompanion Function({
|
i1.SettingsEntityCompanion Function({
|
||||||
i0.Value<String> key,
|
i0.Value<String> key,
|
||||||
i0.Value<String> value,
|
i0.Value<String?> value,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ class $$SettingsEntityTableTableManager
|
|||||||
updateCompanionCallback:
|
updateCompanionCallback:
|
||||||
({
|
({
|
||||||
i0.Value<String> key = const i0.Value.absent(),
|
i0.Value<String> key = const i0.Value.absent(),
|
||||||
i0.Value<String> value = const i0.Value.absent(),
|
i0.Value<String?> value = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
}) => i1.SettingsEntityCompanion(
|
}) => i1.SettingsEntityCompanion(
|
||||||
key: key,
|
key: key,
|
||||||
@@ -137,7 +137,7 @@ class $$SettingsEntityTableTableManager
|
|||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
required String key,
|
required String key,
|
||||||
required String value,
|
i0.Value<String?> value = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
}) => i1.SettingsEntityCompanion.insert(
|
}) => i1.SettingsEntityCompanion.insert(
|
||||||
key: key,
|
key: key,
|
||||||
@@ -196,9 +196,9 @@ class $SettingsEntityTable extends i2.SettingsEntity
|
|||||||
late final i0.GeneratedColumn<String> value = i0.GeneratedColumn<String>(
|
late final i0.GeneratedColumn<String> value = i0.GeneratedColumn<String>(
|
||||||
'value',
|
'value',
|
||||||
aliasedName,
|
aliasedName,
|
||||||
false,
|
true,
|
||||||
type: i0.DriftSqlType.string,
|
type: i0.DriftSqlType.string,
|
||||||
requiredDuringInsert: true,
|
requiredDuringInsert: false,
|
||||||
);
|
);
|
||||||
static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta(
|
static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta(
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
@@ -240,8 +240,6 @@ class $SettingsEntityTable extends i2.SettingsEntity
|
|||||||
_valueMeta,
|
_valueMeta,
|
||||||
value.isAcceptableOrUnknown(data['value']!, _valueMeta),
|
value.isAcceptableOrUnknown(data['value']!, _valueMeta),
|
||||||
);
|
);
|
||||||
} else if (isInserting) {
|
|
||||||
context.missing(_valueMeta);
|
|
||||||
}
|
}
|
||||||
if (data.containsKey('updated_at')) {
|
if (data.containsKey('updated_at')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
@@ -265,7 +263,7 @@ class $SettingsEntityTable extends i2.SettingsEntity
|
|||||||
value: attachedDatabase.typeMapping.read(
|
value: attachedDatabase.typeMapping.read(
|
||||||
i0.DriftSqlType.string,
|
i0.DriftSqlType.string,
|
||||||
data['${effectivePrefix}value'],
|
data['${effectivePrefix}value'],
|
||||||
)!,
|
),
|
||||||
updatedAt: attachedDatabase.typeMapping.read(
|
updatedAt: attachedDatabase.typeMapping.read(
|
||||||
i0.DriftSqlType.dateTime,
|
i0.DriftSqlType.dateTime,
|
||||||
data['${effectivePrefix}updated_at'],
|
data['${effectivePrefix}updated_at'],
|
||||||
@@ -287,18 +285,20 @@ class $SettingsEntityTable extends i2.SettingsEntity
|
|||||||
class SettingsEntityData extends i0.DataClass
|
class SettingsEntityData extends i0.DataClass
|
||||||
implements i0.Insertable<i1.SettingsEntityData> {
|
implements i0.Insertable<i1.SettingsEntityData> {
|
||||||
final String key;
|
final String key;
|
||||||
final String value;
|
final String? value;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
const SettingsEntityData({
|
const SettingsEntityData({
|
||||||
required this.key,
|
required this.key,
|
||||||
required this.value,
|
this.value,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, i0.Expression>{};
|
final map = <String, i0.Expression>{};
|
||||||
map['key'] = i0.Variable<String>(key);
|
map['key'] = i0.Variable<String>(key);
|
||||||
map['value'] = i0.Variable<String>(value);
|
if (!nullToAbsent || value != null) {
|
||||||
|
map['value'] = i0.Variable<String>(value);
|
||||||
|
}
|
||||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
@@ -310,7 +310,7 @@ class SettingsEntityData extends i0.DataClass
|
|||||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
return SettingsEntityData(
|
return SettingsEntityData(
|
||||||
key: serializer.fromJson<String>(json['key']),
|
key: serializer.fromJson<String>(json['key']),
|
||||||
value: serializer.fromJson<String>(json['value']),
|
value: serializer.fromJson<String?>(json['value']),
|
||||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -319,18 +319,18 @@ class SettingsEntityData extends i0.DataClass
|
|||||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'key': serializer.toJson<String>(key),
|
'key': serializer.toJson<String>(key),
|
||||||
'value': serializer.toJson<String>(value),
|
'value': serializer.toJson<String?>(value),
|
||||||
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
i1.SettingsEntityData copyWith({
|
i1.SettingsEntityData copyWith({
|
||||||
String? key,
|
String? key,
|
||||||
String? value,
|
i0.Value<String?> value = const i0.Value.absent(),
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
}) => i1.SettingsEntityData(
|
}) => i1.SettingsEntityData(
|
||||||
key: key ?? this.key,
|
key: key ?? this.key,
|
||||||
value: value ?? this.value,
|
value: value.present ? value.value : this.value,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
);
|
);
|
||||||
SettingsEntityData copyWithCompanion(i1.SettingsEntityCompanion data) {
|
SettingsEntityData copyWithCompanion(i1.SettingsEntityCompanion data) {
|
||||||
@@ -365,7 +365,7 @@ class SettingsEntityData extends i0.DataClass
|
|||||||
class SettingsEntityCompanion
|
class SettingsEntityCompanion
|
||||||
extends i0.UpdateCompanion<i1.SettingsEntityData> {
|
extends i0.UpdateCompanion<i1.SettingsEntityData> {
|
||||||
final i0.Value<String> key;
|
final i0.Value<String> key;
|
||||||
final i0.Value<String> value;
|
final i0.Value<String?> value;
|
||||||
final i0.Value<DateTime> updatedAt;
|
final i0.Value<DateTime> updatedAt;
|
||||||
const SettingsEntityCompanion({
|
const SettingsEntityCompanion({
|
||||||
this.key = const i0.Value.absent(),
|
this.key = const i0.Value.absent(),
|
||||||
@@ -374,10 +374,9 @@ class SettingsEntityCompanion
|
|||||||
});
|
});
|
||||||
SettingsEntityCompanion.insert({
|
SettingsEntityCompanion.insert({
|
||||||
required String key,
|
required String key,
|
||||||
required String value,
|
this.value = const i0.Value.absent(),
|
||||||
this.updatedAt = const i0.Value.absent(),
|
this.updatedAt = const i0.Value.absent(),
|
||||||
}) : key = i0.Value(key),
|
}) : key = i0.Value(key);
|
||||||
value = i0.Value(value);
|
|
||||||
static i0.Insertable<i1.SettingsEntityData> custom({
|
static i0.Insertable<i1.SettingsEntityData> custom({
|
||||||
i0.Expression<String>? key,
|
i0.Expression<String>? key,
|
||||||
i0.Expression<String>? value,
|
i0.Expression<String>? value,
|
||||||
@@ -392,7 +391,7 @@ class SettingsEntityCompanion
|
|||||||
|
|
||||||
i1.SettingsEntityCompanion copyWith({
|
i1.SettingsEntityCompanion copyWith({
|
||||||
i0.Value<String>? key,
|
i0.Value<String>? key,
|
||||||
i0.Value<String>? value,
|
i0.Value<String?>? value,
|
||||||
i0.Value<DateTime>? updatedAt,
|
i0.Value<DateTime>? updatedAt,
|
||||||
}) {
|
}) {
|
||||||
return i1.SettingsEntityCompanion(
|
return i1.SettingsEntityCompanion(
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
|
||||||
|
class AppMetadataRepository {
|
||||||
|
final Drift _db;
|
||||||
|
|
||||||
|
const AppMetadataRepository(this._db);
|
||||||
|
|
||||||
|
Future<T> get<T>(AppMetadataKey<T> key) async {
|
||||||
|
final row = await (_db.select(_db.appMetadataEntity)..where((row) => row.key.equals(key.name))).getSingleOrNull();
|
||||||
|
final value = row?.value;
|
||||||
|
return value == null ? key.defaultValue : key.decode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> set<T, U extends T>(AppMetadataKey<T> key, U value) async {
|
||||||
|
await _db
|
||||||
|
.into(_db.appMetadataEntity)
|
||||||
|
.insertOnConflictUpdate(
|
||||||
|
AppMetadataEntityCompanion.insert(
|
||||||
|
key: key.name,
|
||||||
|
value: Value(key.encode(value)),
|
||||||
|
updatedAt: Value(DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
// ignore: depend_on_referenced_packages
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
abstract class CachedKeyValueRepository<K extends Enum, S> {
|
||||||
|
CachedKeyValueRepository(this._snapshot);
|
||||||
|
|
||||||
|
S _snapshot;
|
||||||
|
S get snapshot => _snapshot;
|
||||||
|
@protected
|
||||||
|
set snapshot(S value) => _snapshot = value;
|
||||||
|
|
||||||
|
List<K> get keys;
|
||||||
|
|
||||||
|
Object decodeValue(K key, String raw);
|
||||||
|
|
||||||
|
S buildSnapshot(Map<K, Object?> overrides);
|
||||||
|
|
||||||
|
Selectable<({String key, String? value})> selectable();
|
||||||
|
|
||||||
|
Future<void> refresh() async => _snapshot = _build(await selectable().get());
|
||||||
|
|
||||||
|
@protected
|
||||||
|
Stream<S> watchSnapshot() => selectable().watch().map((rows) => _snapshot = _build(rows));
|
||||||
|
|
||||||
|
S _build(List<({String key, String? value})> rows) => buildSnapshot(
|
||||||
|
rows.fold({}, (overrides, row) {
|
||||||
|
final key = keys.firstWhereOrNull((key) => key.name == row.key);
|
||||||
|
if (key == null) {
|
||||||
|
return overrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object? decodedValue;
|
||||||
|
if (row.value != null) {
|
||||||
|
decodedValue = decodeValue(key, row.value!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {...overrides, key: decodedValue};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import 'package:drift/drift.dart';
|
|||||||
import 'package:drift/src/runtime/executor/stream_queries.dart' show StreamQueryStore;
|
import 'package:drift/src/runtime/executor/stream_queries.dart' show StreamQueryStore;
|
||||||
import 'package:drift_sqlite_async/drift_sqlite_async.dart';
|
import 'package:drift_sqlite_async/drift_sqlite_async.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.dart';
|
||||||
@@ -25,6 +26,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.d
|
|||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/session.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/settings.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/settings.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
@@ -67,6 +69,8 @@ import 'package:sqlite_async/sqlite_async.dart';
|
|||||||
AssetEditEntity,
|
AssetEditEntity,
|
||||||
SettingsEntity,
|
SettingsEntity,
|
||||||
AssetOcrEntity,
|
AssetOcrEntity,
|
||||||
|
SessionEntity,
|
||||||
|
AppMetadataEntity,
|
||||||
],
|
],
|
||||||
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
||||||
)
|
)
|
||||||
@@ -120,7 +124,7 @@ class Drift extends $Drift {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 29;
|
int get schemaVersion => 32;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -308,6 +312,20 @@ class Drift extends $Drift {
|
|||||||
await m.createTable(v29.assetOcrEntity);
|
await m.createTable(v29.assetOcrEntity);
|
||||||
await m.createIndex(v29.idxAssetOcrAssetId);
|
await m.createIndex(v29.idxAssetOcrAssetId);
|
||||||
},
|
},
|
||||||
|
from29To30: (m, v30) async {
|
||||||
|
await m.alterTable(TableMigration(v30.settings));
|
||||||
|
},
|
||||||
|
from30To31: (m, v31) async {
|
||||||
|
await m.createTable(v31.session);
|
||||||
|
},
|
||||||
|
from31To32: (m, v32) async {
|
||||||
|
await m.createTable(v32.appMetadata);
|
||||||
|
await customStatement(
|
||||||
|
"INSERT INTO app_metadata (key, value) "
|
||||||
|
"SELECT 'version', CAST(int_value AS TEXT) FROM store_entity "
|
||||||
|
"WHERE id = 0 AND int_value IS NOT NULL",
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+19
-4
@@ -47,9 +47,13 @@ import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart
|
|||||||
as i22;
|
as i22;
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart'
|
||||||
as i23;
|
as i23;
|
||||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/session.entity.drift.dart'
|
||||||
as i24;
|
as i24;
|
||||||
import 'package:drift/internal/modular.dart' as i25;
|
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.drift.dart'
|
||||||
|
as i25;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||||
|
as i26;
|
||||||
|
import 'package:drift/internal/modular.dart' as i27;
|
||||||
|
|
||||||
abstract class $Drift extends i0.GeneratedDatabase {
|
abstract class $Drift extends i0.GeneratedDatabase {
|
||||||
$Drift(i0.QueryExecutor e) : super(e);
|
$Drift(i0.QueryExecutor e) : super(e);
|
||||||
@@ -99,9 +103,14 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
late final i23.$AssetOcrEntityTable assetOcrEntity = i23.$AssetOcrEntityTable(
|
late final i23.$AssetOcrEntityTable assetOcrEntity = i23.$AssetOcrEntityTable(
|
||||||
this,
|
this,
|
||||||
);
|
);
|
||||||
i24.MergedAssetDrift get mergedAssetDrift => i25.ReadDatabaseContainer(
|
late final i24.$SessionEntityTable sessionEntity = i24.$SessionEntityTable(
|
||||||
this,
|
this,
|
||||||
).accessor<i24.MergedAssetDrift>(i24.MergedAssetDrift.new);
|
);
|
||||||
|
late final i25.$AppMetadataEntityTable appMetadataEntity = i25
|
||||||
|
.$AppMetadataEntityTable(this);
|
||||||
|
i26.MergedAssetDrift get mergedAssetDrift => i27.ReadDatabaseContainer(
|
||||||
|
this,
|
||||||
|
).accessor<i26.MergedAssetDrift>(i26.MergedAssetDrift.new);
|
||||||
@override
|
@override
|
||||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||||
@@ -140,6 +149,8 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
assetEditEntity,
|
assetEditEntity,
|
||||||
settingsEntity,
|
settingsEntity,
|
||||||
assetOcrEntity,
|
assetOcrEntity,
|
||||||
|
sessionEntity,
|
||||||
|
appMetadataEntity,
|
||||||
i10.idxPartnerSharedWithId,
|
i10.idxPartnerSharedWithId,
|
||||||
i11.idxLatLng,
|
i11.idxLatLng,
|
||||||
i11.idxRemoteExifCity,
|
i11.idxRemoteExifCity,
|
||||||
@@ -414,4 +425,8 @@ class $DriftManager {
|
|||||||
i22.$$SettingsEntityTableTableManager(_db, _db.settingsEntity);
|
i22.$$SettingsEntityTableTableManager(_db, _db.settingsEntity);
|
||||||
i23.$$AssetOcrEntityTableTableManager get assetOcrEntity =>
|
i23.$$AssetOcrEntityTableTableManager get assetOcrEntity =>
|
||||||
i23.$$AssetOcrEntityTableTableManager(_db, _db.assetOcrEntity);
|
i23.$$AssetOcrEntityTableTableManager(_db, _db.assetOcrEntity);
|
||||||
|
i24.$$SessionEntityTableTableManager get sessionEntity =>
|
||||||
|
i24.$$SessionEntityTableTableManager(_db, _db.sessionEntity);
|
||||||
|
i25.$$AppMetadataEntityTableTableManager get appMetadataEntity =>
|
||||||
|
i25.$$AppMetadataEntityTableTableManager(_db, _db.appMetadataEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/session.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/cached_key_value_repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
|
||||||
|
class SessionRepository extends CachedKeyValueRepository<SessionKey, Session> {
|
||||||
|
final Drift _db;
|
||||||
|
|
||||||
|
SessionRepository._(this._db) : super(const .new());
|
||||||
|
|
||||||
|
static SessionRepository? _instance;
|
||||||
|
|
||||||
|
static SessionRepository get instance {
|
||||||
|
final instance = _instance;
|
||||||
|
if (instance == null) {
|
||||||
|
throw StateError('SessionRepository not initialized. Call ensureInitialized() first');
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<SessionRepository> ensureInitialized(Drift db) async {
|
||||||
|
if (_instance == null) {
|
||||||
|
final instance = SessionRepository._(db);
|
||||||
|
await instance.refresh();
|
||||||
|
_instance = instance;
|
||||||
|
}
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SessionKey> get keys => SessionKey.values;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object decodeValue(SessionKey key, String raw) => key.decode(raw);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Session buildSnapshot(Map<SessionKey, Object?> overrides) => Session.fromEntries(overrides);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@protected
|
||||||
|
Selectable<({String key, String? value})> selectable() =>
|
||||||
|
_db.select(_db.sessionEntity).map((row) => (key: row.key, value: row.value));
|
||||||
|
|
||||||
|
Session get session => snapshot;
|
||||||
|
|
||||||
|
Future<void> clear(Iterable<SessionKey> keys) async {
|
||||||
|
if (keys.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final names = keys.map((key) => key.name).toList();
|
||||||
|
await (_db.delete(_db.sessionEntity)..where((row) => row.key.isIn(names))).go();
|
||||||
|
|
||||||
|
var session = snapshot;
|
||||||
|
for (final key in keys) {
|
||||||
|
session = session.write(key, defaultSession.read(key));
|
||||||
|
}
|
||||||
|
snapshot = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> write<T, U extends T>(SessionKey<T> key, U value) async {
|
||||||
|
if (value == snapshot.read(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? resolvedValue;
|
||||||
|
if (value != null) {
|
||||||
|
resolvedValue = key.encode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _db
|
||||||
|
.into(_db.sessionEntity)
|
||||||
|
.insertOnConflictUpdate(
|
||||||
|
SessionEntityCompanion.insert(key: key.name, value: .new(resolvedValue), updatedAt: .new(DateTime.now())),
|
||||||
|
);
|
||||||
|
snapshot = snapshot.write(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Session> watch() => watchSnapshot();
|
||||||
|
}
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/settings_key.dart';
|
import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/cached_key_value_repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
|
||||||
class SettingsRepository extends DriftDatabaseRepository {
|
class SettingsRepository extends CachedKeyValueRepository<SettingsKey, AppConfig> {
|
||||||
final Drift _db;
|
final Drift _db;
|
||||||
|
|
||||||
SettingsRepository._(this._db) : super(_db);
|
SettingsRepository._(this._db) : super(const .new());
|
||||||
|
|
||||||
static SettingsRepository? _instance;
|
static SettingsRepository? _instance;
|
||||||
|
|
||||||
@@ -20,9 +21,6 @@ class SettingsRepository extends DriftDatabaseRepository {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig _appConfig = const .new();
|
|
||||||
AppConfig get appConfig => _appConfig;
|
|
||||||
|
|
||||||
static Future<SettingsRepository> ensureInitialized(Drift db) async {
|
static Future<SettingsRepository> ensureInitialized(Drift db) async {
|
||||||
if (_instance == null) {
|
if (_instance == null) {
|
||||||
final instance = SettingsRepository._(db);
|
final instance = SettingsRepository._(db);
|
||||||
@@ -32,7 +30,21 @@ class SettingsRepository extends DriftDatabaseRepository {
|
|||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refresh() async => _applyOverrides(await _db.select(_db.settingsEntity).get());
|
@override
|
||||||
|
List<SettingsKey> get keys => SettingsKey.values;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object decodeValue(SettingsKey key, String raw) => key.decode(raw);
|
||||||
|
|
||||||
|
@override
|
||||||
|
AppConfig buildSnapshot(Map<SettingsKey, Object?> overrides) => AppConfig.fromEntries(overrides);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@protected
|
||||||
|
Selectable<({String key, String? value})> selectable() =>
|
||||||
|
_db.select(_db.settingsEntity).map((row) => (key: row.key, value: row.value));
|
||||||
|
|
||||||
|
AppConfig get appConfig => snapshot;
|
||||||
|
|
||||||
Future<void> clear(Iterable<SettingsKey> keys) async {
|
Future<void> clear(Iterable<SettingsKey> keys) async {
|
||||||
if (keys.isEmpty) {
|
if (keys.isEmpty) {
|
||||||
@@ -42,13 +54,15 @@ class SettingsRepository extends DriftDatabaseRepository {
|
|||||||
final names = keys.map((key) => key.name).toList();
|
final names = keys.map((key) => key.name).toList();
|
||||||
await (_db.delete(_db.settingsEntity)..where((row) => row.key.isIn(names))).go();
|
await (_db.delete(_db.settingsEntity)..where((row) => row.key.isIn(names))).go();
|
||||||
|
|
||||||
|
var config = snapshot;
|
||||||
for (final key in keys) {
|
for (final key in keys) {
|
||||||
_appConfig = _appConfig.write(key, defaultConfig.read(key));
|
config = config.write(key, defaultConfig.read(key));
|
||||||
}
|
}
|
||||||
|
snapshot = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> write<T extends Object, U extends T>(SettingsKey<T> key, U value) async {
|
Future<void> write<T, U extends T>(SettingsKey<T> key, U value) async {
|
||||||
if (value == _appConfig.read(key)) {
|
if (value == snapshot.read(key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,29 +70,18 @@ class SettingsRepository extends DriftDatabaseRepository {
|
|||||||
return clear([key]);
|
return clear([key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? resolvedValue;
|
||||||
|
if (value != null) {
|
||||||
|
resolvedValue = key.encode(value);
|
||||||
|
}
|
||||||
|
|
||||||
await _db
|
await _db
|
||||||
.into(_db.settingsEntity)
|
.into(_db.settingsEntity)
|
||||||
.insertOnConflictUpdate(
|
.insertOnConflictUpdate(
|
||||||
SettingsEntityCompanion.insert(key: key.name, value: key.encode(value), updatedAt: Value(DateTime.now())),
|
SettingsEntityCompanion.insert(key: key.name, value: .new(resolvedValue), updatedAt: .new(DateTime.now())),
|
||||||
);
|
);
|
||||||
_appConfig = _appConfig.write(key, value);
|
snapshot = snapshot.write(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<AppConfig> watchConfig() => _db.select(_db.settingsEntity).watch().map((rows) {
|
Stream<AppConfig> watch() => watchSnapshot();
|
||||||
_applyOverrides(rows);
|
|
||||||
return _appConfig;
|
|
||||||
});
|
|
||||||
|
|
||||||
void _applyOverrides(List<SettingsEntityData> rows) {
|
|
||||||
_appConfig = AppConfig.fromEntries(
|
|
||||||
rows.fold({}, (overrides, row) {
|
|
||||||
final metadataKey = SettingsKey.values.firstWhereOrNull((key) => key.name == row.key);
|
|
||||||
if (metadataKey == null) {
|
|
||||||
return overrides;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {...overrides, metadataKey: metadataKey.decode(row.value)};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
|
||||||
|
|
||||||
class DriftStoreRepository extends DriftDatabaseRepository {
|
class DriftStoreRepository extends DriftDatabaseRepository {
|
||||||
final Drift _db;
|
final Drift _db;
|
||||||
@@ -63,8 +61,6 @@ class DriftStoreRepository extends DriftDatabaseRepository {
|
|||||||
const (String) => entity.stringValue,
|
const (String) => entity.stringValue,
|
||||||
const (bool) => entity.intValue == 1,
|
const (bool) => entity.intValue == 1,
|
||||||
const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
||||||
const (UserDto) =>
|
|
||||||
entity.stringValue == null ? null : await DriftAuthUserRepository(_db).get(entity.stringValue!),
|
|
||||||
_ => null,
|
_ => null,
|
||||||
}
|
}
|
||||||
as T?;
|
as T?;
|
||||||
@@ -75,7 +71,6 @@ class DriftStoreRepository extends DriftDatabaseRepository {
|
|||||||
const (String) => (null, value as String),
|
const (String) => (null, value as String),
|
||||||
const (bool) => ((value as bool) ? 1 : 0, null),
|
const (bool) => ((value as bool) ? 1 : 0, null),
|
||||||
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
||||||
const (UserDto) => (null, (await DriftAuthUserRepository(_db).upsert(value as UserDto)).id),
|
|
||||||
_ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
|
_ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
|
||||||
};
|
};
|
||||||
return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue));
|
return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue));
|
||||||
|
|||||||
@@ -17,16 +17,15 @@ class DriftAuthUserRepository extends DriftDatabaseRepository {
|
|||||||
final Drift _db;
|
final Drift _db;
|
||||||
const DriftAuthUserRepository(super.db) : _db = db;
|
const DriftAuthUserRepository(super.db) : _db = db;
|
||||||
|
|
||||||
Future<UserDto?> get(String id) async {
|
Selectable<UserDto?> get _authUserQuery => (_db.authUserEntity.select()..limit(1)).asyncMap(_toDto);
|
||||||
final user = await _db.managers.authUserEntity.filter((user) => user.id.equals(id)).getSingleOrNull();
|
|
||||||
|
|
||||||
if (user == null) {
|
Future<UserDto?> get() => _authUserQuery.getSingleOrNull();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(id));
|
Stream<UserDto?> watch() => _authUserQuery.watchSingleOrNull();
|
||||||
|
|
||||||
|
Future<UserDto> _toDto(AuthUserEntityData user) async {
|
||||||
|
final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(user.id));
|
||||||
final metadata = await query.map((row) => row.toDto()).get();
|
final metadata = await query.map((row) => row.toDto()).get();
|
||||||
|
|
||||||
return user.toDto(metadata);
|
return user.toDto(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
@@ -85,7 +83,7 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
final backupSyncManager = ref.read(backgroundSyncProvider);
|
final backupSyncManager = ref.read(backgroundSyncProvider);
|
||||||
|
|
||||||
Future<void> startBackup() async {
|
Future<void> startBackup() async {
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
final currentUser = ref.read(currentUserProvider);
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/locales.dart';
|
import 'package:immich_mobile/constants/locales.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
import 'package:immich_mobile/generated/translations.g.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart';
|
import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
@@ -306,9 +307,10 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void resumeSession() async {
|
void resumeSession() async {
|
||||||
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
final session = ref.read(sessionProvider);
|
||||||
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
final serverUrl = session.serverUrl;
|
||||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
final endpoint = session.serverEndpoint;
|
||||||
|
final accessToken = session.accessToken;
|
||||||
|
|
||||||
if (accessToken != null && serverUrl != null && endpoint != null) {
|
if (accessToken != null && serverUrl != null && endpoint != null) {
|
||||||
final infoProvider = ref.read(serverInfoProvider.notifier);
|
final infoProvider = ref.read(serverInfoProvider.notifier);
|
||||||
@@ -316,6 +318,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
final backgroundManager = ref.read(backgroundSyncProvider);
|
final backgroundManager = ref.read(backgroundSyncProvider);
|
||||||
final backupProvider = ref.read(driftBackupProvider.notifier);
|
final backupProvider = ref.read(driftBackupProvider.notifier);
|
||||||
final viewIntentHandler = ref.read(viewIntentHandlerProvider);
|
final viewIntentHandler = ref.read(viewIntentHandlerProvider);
|
||||||
|
final authUserRepository = ref.read(authUserRepositoryProvider);
|
||||||
|
|
||||||
unawaited(
|
unawaited(
|
||||||
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
||||||
@@ -335,9 +338,9 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
if (syncSuccess) {
|
if (syncSuccess) {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
backgroundManager.hashAssets().then((_) {
|
backgroundManager.hashAssets().then((_) {
|
||||||
_resumeBackup(backupProvider);
|
_resumeBackup(backupProvider, authUserRepository);
|
||||||
}),
|
}),
|
||||||
_resumeBackup(backupProvider),
|
_resumeBackup(backupProvider, authUserRepository),
|
||||||
// TODO: Bring back when the soft freeze issue is addressed
|
// TODO: Bring back when the soft freeze issue is addressed
|
||||||
// backgroundManager.syncCloudIds(),
|
// backgroundManager.syncCloudIds(),
|
||||||
]);
|
]);
|
||||||
@@ -373,11 +376,11 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
|
Future<void> _resumeBackup(DriftBackupNotifier notifier, DriftAuthUserRepository authUserRepository) async {
|
||||||
final isEnableBackup = SettingsRepository.instance.appConfig.backup.enabled;
|
final isEnableBackup = SettingsRepository.instance.appConfig.backup.enabled;
|
||||||
|
|
||||||
if (isEnableBackup) {
|
if (isEnableBackup) {
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
final currentUser = await authUserRepository.get();
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
unawaited(notifier.startForegroundBackup(currentUser.id));
|
unawaited(notifier.startForegroundBackup(currentUser.id));
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -1,9 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ class OpenInBrowserActionButton extends ConsumerWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
void _onTap() async {
|
void _onTap() async {
|
||||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint).replaceFirst('/api', '');
|
final serverEndpoint = SessionRepository.instance.session.serverEndpoint!.replaceFirst('/api', '');
|
||||||
|
|
||||||
String originPath = '';
|
String originPath = '';
|
||||||
switch (origin) {
|
switch (origin) {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import 'dart:io';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
@@ -13,6 +11,7 @@ import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.pro
|
|||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -143,7 +142,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
|||||||
|
|
||||||
final remoteId = (videoAsset as RemoteAsset).id;
|
final remoteId = (videoAsset as RemoteAsset).id;
|
||||||
|
|
||||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
final serverEndpoint = ref.read(sessionProvider).serverEndpoint!;
|
||||||
final isOriginalVideo = ref.read(appConfigProvider).viewer.loadOriginalVideo;
|
final isOriginalVideo = ref.read(appConfigProvider).viewer.loadOriginalVideo;
|
||||||
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
||||||
final String videoUrl = videoAsset.livePhotoVideoId != null
|
final String videoUrl = videoAsset.livePhotoVideoId != null
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
@@ -33,7 +33,7 @@ class ViewerKebabMenu extends ConsumerWidget {
|
|||||||
final isInLockedView = ref.watch(inLockedViewProvider);
|
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||||
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
|
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
|
||||||
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
|
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
|
||||||
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(.advancedTroubleshooting);
|
final advancedTroubleshooting = ref.watch(appConfigProvider.select((c) => c.advanced.troubleshooting));
|
||||||
|
|
||||||
final actionContext = ActionButtonContext(
|
final actionContext = ActionButtonContext(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/bulk_tag_assets_action_button.widget.dart';
|
||||||
@@ -24,7 +23,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_
|
|||||||
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/user_metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/user_metadata.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
@@ -56,7 +55,7 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final multiselect = ref.watch(multiSelectProvider);
|
final multiselect = ref.watch(multiSelectProvider);
|
||||||
final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash));
|
final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash));
|
||||||
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting);
|
final advancedTroubleshooting = ref.watch(appConfigProvider.select((c) => c.advanced.troubleshooting));
|
||||||
final tagsEnabled = ref.watch(
|
final tagsEnabled = ref.watch(
|
||||||
userMetadataPreferencesProvider.select((value) => value.valueOrNull?.tagsEnabled ?? false),
|
userMetadataPreferencesProvider.select((value) => value.valueOrNull?.tagsEnabled ?? false),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -72,9 +72,6 @@ class MapStateNotifier extends Notifier<MapState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void switchTheme(ThemeMode mode) {
|
void switchTheme(ThemeMode mode) {
|
||||||
// TODO: Remove this line when map theme provider is removed
|
|
||||||
// Until then, keep both in sync as MapThemeOverride uses map state provider
|
|
||||||
// ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapThemeMode, mode.index);
|
|
||||||
ref.read(mapStateNotifierProvider.notifier).switchTheme(mode);
|
ref.read(mapStateNotifierProvider.notifier).switchTheme(mode);
|
||||||
state = state.copyWith(themeMode: mode);
|
state = state.copyWith(themeMode: mode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||||
|
|
||||||
class PartnerUserAvatar extends StatelessWidget {
|
class PartnerUserAvatar extends ConsumerWidget {
|
||||||
const PartnerUserAvatar({super.key, required this.userId, required this.name});
|
const PartnerUserAvatar({super.key, required this.userId, required this.name});
|
||||||
|
|
||||||
final String userId;
|
final String userId;
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final url = "${Store.get(StoreKey.serverEndpoint)}/users/$userId/profile-image";
|
final url = "${ref.read(sessionProvider).serverEndpoint}/users/$userId/profile-image";
|
||||||
final nameFirstLetter = name.isNotEmpty ? name[0] : "";
|
final nameFirstLetter = name.isNotEmpty ? name[0] : "";
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
@@ -13,6 +11,7 @@ import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/permission.provider.dart';
|
import 'package:immich_mobile/providers/permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
@@ -140,7 +139,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
final isEnableBackup = _ref.read(appConfigProvider).backup.enabled;
|
final isEnableBackup = _ref.read(appConfigProvider).backup.enabled;
|
||||||
|
|
||||||
if (isEnableBackup) {
|
if (isEnableBackup) {
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
final currentUser = _ref.read(currentUserProvider);
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
await _safeRun(
|
await _safeRun(
|
||||||
_ref.read(driftBackupProvider.notifier).startForegroundBackup(currentUser.id),
|
_ref.read(driftBackupProvider.notifier).startForegroundBackup(currentUser.id),
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
|
|
||||||
final appSettingsServiceProvider = Provider((_) => const AppSettingsService());
|
|
||||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'package:flutter_udid/flutter_udid.dart';
|
import 'package:flutter_udid/flutter_udid.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||||
@@ -10,6 +11,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
||||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
@@ -125,10 +127,10 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> saveAuthInfo({required String accessToken}) async {
|
Future<bool> saveAuthInfo({required String accessToken}) async {
|
||||||
await Store.put(StoreKey.accessToken, accessToken);
|
await _ref.read(sessionRepository).write(SessionKey.accessToken, accessToken);
|
||||||
await _apiService.updateHeaders();
|
await _apiService.updateHeaders();
|
||||||
|
|
||||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
final serverEndpoint = _ref.read(sessionProvider).serverEndpoint!;
|
||||||
final headerMap = _ref.read(appConfigProvider).network.customHeaders;
|
final headerMap = _ref.read(appConfigProvider).network.customHeaders;
|
||||||
final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap);
|
final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap);
|
||||||
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
|
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
|
||||||
@@ -136,7 +138,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
// Get the deviceid from the store if it exists, otherwise generate a new one
|
// Get the deviceid from the store if it exists, otherwise generate a new one
|
||||||
String deviceId = Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;
|
String deviceId = Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;
|
||||||
|
|
||||||
UserDto? user = _userService.tryGetMyUser();
|
UserDto? user = await _userService.tryGetMyUser();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final serverUser = await _userService.refreshMyUser().timeout(_timeoutDuration);
|
final serverUser = await _userService.refreshMyUser().timeout(_timeoutDuration);
|
||||||
@@ -193,9 +195,9 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
return _ref.read(appConfigProvider).network.localEndpoint;
|
return _ref.read(appConfigProvider).network.localEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current server endpoint (with /api) URL from the store
|
/// Returns the current server endpoint (with /api) URL from the session
|
||||||
String? getServerEndpoint() {
|
String? getServerEndpoint() {
|
||||||
return Store.tryGet(StoreKey.serverEndpoint);
|
return _ref.read(sessionProvider).serverEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> setOpenApiServiceEndpoint() {
|
Future<String?> setOpenApiServiceEndpoint() {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
|
|
||||||
final hapticFeedbackProvider = StateNotifierProvider<HapticNotifier, void>((ref) {
|
final hapticFeedbackProvider = StateNotifierProvider<HapticNotifier, void>((ref) {
|
||||||
return HapticNotifier(ref);
|
return HapticNotifier(ref);
|
||||||
@@ -14,31 +13,31 @@ class HapticNotifier extends StateNotifier<void> {
|
|||||||
HapticNotifier(this._ref) : super(null);
|
HapticNotifier(this._ref) : super(null);
|
||||||
|
|
||||||
selectionClick() {
|
selectionClick() {
|
||||||
if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) {
|
if (_ref.read(appConfigProvider).advanced.enableHapticFeedback) {
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lightImpact() {
|
lightImpact() {
|
||||||
if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) {
|
if (_ref.read(appConfigProvider).advanced.enableHapticFeedback) {
|
||||||
HapticFeedback.lightImpact();
|
HapticFeedback.lightImpact();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mediumImpact() {
|
mediumImpact() {
|
||||||
if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) {
|
if (_ref.read(appConfigProvider).advanced.enableHapticFeedback) {
|
||||||
HapticFeedback.mediumImpact();
|
HapticFeedback.mediumImpact();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
heavyImpact() {
|
heavyImpact() {
|
||||||
if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) {
|
if (_ref.read(appConfigProvider).advanced.enableHapticFeedback) {
|
||||||
HapticFeedback.heavyImpact();
|
HapticFeedback.heavyImpact();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vibrate() {
|
vibrate() {
|
||||||
if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) {
|
if (_ref.read(appConfigProvider).advanced.enableHapticFeedback) {
|
||||||
HapticFeedback.vibrate();
|
HapticFeedback.vibrate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
||||||
|
final appMetadataRepositoryProvider = Provider<AppMetadataRepository>(
|
||||||
|
(ref) => AppMetadataRepository(ref.watch(driftProvider)),
|
||||||
|
);
|
||||||
@@ -1,22 +1,19 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
|
|
||||||
class ReadOnlyModeNotifier extends Notifier<bool> {
|
class ReadOnlyModeNotifier extends Notifier<bool> {
|
||||||
late AppSettingsService _appSettingService;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool build() {
|
bool build() {
|
||||||
_appSettingService = ref.read(appSettingsServiceProvider);
|
return ref.read(appConfigProvider).advanced.readonlyModeEnabled;
|
||||||
final readonlyMode = _appSettingService.getSetting(AppSettingsEnum.readonlyModeEnabled);
|
|
||||||
return readonlyMode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMode(bool value) {
|
void setMode(bool value) {
|
||||||
final isLoggedIn = ref.read(authProvider).isAuthenticated;
|
final isLoggedIn = ref.read(authProvider).isAuthenticated;
|
||||||
_appSettingService.setSetting(AppSettingsEnum.readonlyModeEnabled, value);
|
unawaited(ref.read(settingsProvider).write(.advancedReadonlyModeEnabled, value));
|
||||||
state = value;
|
state = value;
|
||||||
|
|
||||||
if (value && isLoggedIn) {
|
if (value && isLoggedIn) {
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
|
|
||||||
|
final sessionRepository = Provider.autoDispose<SessionRepository>((_) => SessionRepository.instance);
|
||||||
|
|
||||||
|
final sessionProvider = Provider.autoDispose<Session>((ref) {
|
||||||
|
final repo = ref.watch(sessionRepository);
|
||||||
|
final subscription = repo.watch().listen((event) => ref.state = event);
|
||||||
|
ref.onDispose(subscription.cancel);
|
||||||
|
return repo.session;
|
||||||
|
});
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/setting.service.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
|
||||||
|
|
||||||
class SettingsNotifier extends Notifier<SettingsService> {
|
|
||||||
@override
|
|
||||||
SettingsService build() => SettingsService(storeService: ref.read(storeServiceProvider));
|
|
||||||
|
|
||||||
T get<T>(Setting<T> setting) => state.get(setting);
|
|
||||||
|
|
||||||
Future<void> set<T>(Setting<T> setting, T value) async {
|
|
||||||
await state.set(setting, value);
|
|
||||||
ref.invalidateSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<T> watch<T>(Setting<T> setting) => state.watch(setting);
|
|
||||||
}
|
|
||||||
|
|
||||||
final settingsProvider = NotifierProvider<SettingsNotifier, SettingsService>(SettingsNotifier.new);
|
|
||||||
@@ -6,7 +6,7 @@ final settingsProvider = Provider.autoDispose<SettingsRepository>((_) => Setting
|
|||||||
|
|
||||||
final appConfigProvider = Provider.autoDispose<AppConfig>((ref) {
|
final appConfigProvider = Provider.autoDispose<AppConfig>((ref) {
|
||||||
final repo = ref.watch(settingsProvider);
|
final repo = ref.watch(settingsProvider);
|
||||||
final subscription = repo.watchConfig().listen((event) => ref.state = event);
|
final subscription = repo.watch().listen((event) => ref.state = event);
|
||||||
ref.onDispose(subscription.cancel);
|
ref.onDispose(subscription.cancel);
|
||||||
return repo.appConfig;
|
return repo.appConfig;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:immich_mobile/infrastructure/repositories/sync_migration.reposit
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
@@ -26,6 +27,7 @@ final syncStreamServiceProvider = Provider(
|
|||||||
permissionRepository: ref.watch(permissionRepositoryProvider),
|
permissionRepository: ref.watch(permissionRepositoryProvider),
|
||||||
syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider),
|
syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider),
|
||||||
api: ref.watch(apiServiceProvider),
|
api: ref.watch(apiServiceProvider),
|
||||||
|
appMetadataRepository: ref.watch(appMetadataRepositoryProvider),
|
||||||
cancellation: ref.watch(cancellationProvider),
|
cancellation: ref.watch(cancellationProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -42,6 +44,7 @@ final localSyncServiceProvider = Provider(
|
|||||||
assetMediaRepository: ref.watch(assetMediaRepositoryProvider),
|
assetMediaRepository: ref.watch(assetMediaRepositoryProvider),
|
||||||
permissionRepository: ref.watch(permissionRepositoryProvider),
|
permissionRepository: ref.watch(permissionRepositoryProvider),
|
||||||
nativeSyncApi: ref.watch(nativeSyncApiProvider),
|
nativeSyncApi: ref.watch(nativeSyncApiProvider),
|
||||||
|
appMetadataRepository: ref.watch(appMetadataRepositoryProvider),
|
||||||
cancellation: ref.watch(cancellationProvider),
|
cancellation: ref.watch(cancellationProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,17 +6,18 @@ import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
|
||||||
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
||||||
|
|
||||||
final userRepositoryProvider = Provider((ref) => UserRepository(ref.watch(driftProvider)));
|
final userRepositoryProvider = Provider((ref) => UserRepository(ref.watch(driftProvider)));
|
||||||
|
|
||||||
|
final authUserRepositoryProvider = Provider((ref) => DriftAuthUserRepository(ref.watch(driftProvider)));
|
||||||
|
|
||||||
final userApiRepositoryProvider = Provider((ref) => UserApiRepository(ref.watch(apiServiceProvider).usersApi));
|
final userApiRepositoryProvider = Provider((ref) => UserApiRepository(ref.watch(apiServiceProvider).usersApi));
|
||||||
|
|
||||||
final userServiceProvider = Provider(
|
final userServiceProvider = Provider(
|
||||||
(ref) => UserService(
|
(ref) => UserService(
|
||||||
userApiRepository: ref.watch(userApiRepositoryProvider),
|
userApiRepository: ref.watch(userApiRepositoryProvider),
|
||||||
storeService: ref.watch(storeServiceProvider),
|
authUserRepository: ref.watch(authUserRepositoryProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
|||||||
|
|
||||||
class CurrentUserProvider extends StateNotifier<UserDto?> {
|
class CurrentUserProvider extends StateNotifier<UserDto?> {
|
||||||
CurrentUserProvider(this._userService) : super(null) {
|
CurrentUserProvider(this._userService) : super(null) {
|
||||||
state = _userService.tryGetMyUser();
|
_userService.tryGetMyUser().then((user) => state = user ?? state);
|
||||||
streamSub = _userService.watchMyUser().listen((user) => state = user ?? state);
|
streamSub = _userService.watchMyUser().listen((user) => state = user ?? state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/utils/debounce.dart';
|
import 'package:immich_mobile/utils/debounce.dart';
|
||||||
@@ -68,7 +67,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
|
|
||||||
if (authenticationState.isAuthenticated) {
|
if (authenticationState.isAuthenticated) {
|
||||||
try {
|
try {
|
||||||
final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint));
|
final endpoint = Uri.parse(_ref.read(sessionProvider).serverEndpoint!);
|
||||||
dPrint(() => "Attempting to connect to websocket");
|
dPrint(() => "Attempting to connect to websocket");
|
||||||
// Configure socket transports must be specified
|
// Configure socket transports must be specified
|
||||||
Socket socket = io(
|
Socket socket = io(
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import 'dart:io';
|
|||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
@@ -96,7 +95,7 @@ class UploadRepository {
|
|||||||
void Function(int bytes, int totalBytes)? onProgress,
|
void Function(int bytes, int totalBytes)? onProgress,
|
||||||
required String logContext,
|
required String logContext,
|
||||||
}) async {
|
}) async {
|
||||||
final String savedEndpoint = Store.get(StoreKey.serverEndpoint);
|
final String savedEndpoint = SessionRepository.instance.session.serverEndpoint!;
|
||||||
final baseRequest = ProgressMultipartRequest(
|
final baseRequest = ProgressMultipartRequest(
|
||||||
'POST',
|
'POST',
|
||||||
Uri.parse('$savedEndpoint/assets'),
|
Uri.parse('$savedEndpoint/assets'),
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/services/auth.service.dart';
|
import 'package:immich_mobile/services/auth.service.dart';
|
||||||
@@ -23,10 +21,8 @@ class AuthGuard extends AutoRouteGuard {
|
|||||||
// guards, so we keep this function fully sync and validate the token in
|
// guards, so we keep this function fully sync and validate the token in
|
||||||
// the background — otherwise a slow validateAccessToken() request would
|
// the background — otherwise a slow validateAccessToken() request would
|
||||||
// block the route transition for as long as the OS-level HTTP timeout.
|
// block the route transition for as long as the OS-level HTTP timeout.
|
||||||
try {
|
if (SessionRepository.instance.session.accessToken == null) {
|
||||||
Store.get(StoreKey.accessToken);
|
_log.warning('No access token in the session.');
|
||||||
} on StoreKeyNotFoundException catch (_) {
|
|
||||||
_log.warning('No access token in the store.');
|
|
||||||
resolver.next(false);
|
resolver.next(false);
|
||||||
unawaited(router.replaceAll([const LoginRoute()]));
|
unawaited(router.replaceAll([const LoginRoute()]));
|
||||||
return;
|
return;
|
||||||
@@ -40,7 +36,7 @@ class AuthGuard extends AutoRouteGuard {
|
|||||||
if (_validateInFlight) {
|
if (_validateInFlight) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final token = Store.tryGet(StoreKey.accessToken);
|
final token = SessionRepository.instance.session.accessToken;
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -50,7 +46,7 @@ class AuthGuard extends AutoRouteGuard {
|
|||||||
if (res == null || res.authStatus != true) {
|
if (res == null || res.authStatus != true) {
|
||||||
// Token may have changed during validation (user logged out + logged in
|
// Token may have changed during validation (user logged out + logged in
|
||||||
// again); only act if it still applies to the current session.
|
// again); only act if it still applies to the current session.
|
||||||
if (Store.tryGet(StoreKey.accessToken) != token) {
|
if (SessionRepository.instance.session.accessToken != token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_log.fine('User token is invalid. Redirecting to login');
|
_log.fine('User token is invalid. Redirecting to login');
|
||||||
@@ -61,7 +57,7 @@ class AuthGuard extends AutoRouteGuard {
|
|||||||
if (e.code != HttpStatus.unauthorized) {
|
if (e.code != HttpStatus.unauthorized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Store.tryGet(StoreKey.accessToken) != token) {
|
if (SessionRepository.instance.session.accessToken != token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_log.warning("Unauthorized access token.");
|
_log.warning("Unauthorized access token.");
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset_edit.model.dart';
|
import 'package:immich_mobile/domain/models/asset_edit.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/tag.service.dart';
|
import 'package:immich_mobile/domain/services/tag.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.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_api.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||||
@@ -38,6 +38,7 @@ final actionServiceProvider = Provider<ActionService>(
|
|||||||
ref.watch(assetMediaRepositoryProvider),
|
ref.watch(assetMediaRepositoryProvider),
|
||||||
ref.watch(downloadRepositoryProvider),
|
ref.watch(downloadRepositoryProvider),
|
||||||
ref.watch(tagServiceProvider),
|
ref.watch(tagServiceProvider),
|
||||||
|
ref.watch(appMetadataRepositoryProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ class ActionService {
|
|||||||
final AssetMediaRepository _assetMediaRepository;
|
final AssetMediaRepository _assetMediaRepository;
|
||||||
final DownloadRepository _downloadRepository;
|
final DownloadRepository _downloadRepository;
|
||||||
final TagService _tagService;
|
final TagService _tagService;
|
||||||
|
final AppMetadataRepository _appMetadataRepository;
|
||||||
|
|
||||||
const ActionService(
|
const ActionService(
|
||||||
this._assetApiRepository,
|
this._assetApiRepository,
|
||||||
@@ -62,6 +64,7 @@ class ActionService {
|
|||||||
this._assetMediaRepository,
|
this._assetMediaRepository,
|
||||||
this._downloadRepository,
|
this._downloadRepository,
|
||||||
this._tagService,
|
this._tagService,
|
||||||
|
this._appMetadataRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> shareLink(List<String> remoteIds, BuildContext context) async {
|
Future<void> shareLink(List<String> remoteIds, BuildContext context) async {
|
||||||
@@ -318,7 +321,7 @@ class ActionService {
|
|||||||
if (deletedIds.isEmpty) {
|
if (deletedIds.isEmpty) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
if (CurrentPlatform.isAndroid && await _appMetadataRepository.get(.manageLocalMediaAndroid)) {
|
||||||
await _trashedLocalAssetRepository.applyTrashedAssets(deletedIds);
|
await _trashedLocalAssetRepository.applyTrashedAssets(deletedIds);
|
||||||
} else {
|
} else {
|
||||||
await _localAssetRepository.delete(deletedIds);
|
await _localAssetRepository.delete(deletedIds);
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -41,7 +41,7 @@ class ApiService {
|
|||||||
// The below line ensures that the api clients are initialized when the service is instantiated
|
// The below line ensures that the api clients are initialized when the service is instantiated
|
||||||
// This is required to avoid late initialization errors when the clients are access before the endpoint is resolved
|
// This is required to avoid late initialization errors when the clients are access before the endpoint is resolved
|
||||||
setEndpoint('');
|
setEndpoint('');
|
||||||
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
final endpoint = SessionRepository.instance.session.serverEndpoint;
|
||||||
if (endpoint != null && endpoint.isNotEmpty) {
|
if (endpoint != null && endpoint.isNotEmpty) {
|
||||||
setEndpoint(endpoint);
|
setEndpoint(endpoint);
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ class ApiService {
|
|||||||
setEndpoint(endpoint);
|
setEndpoint(endpoint);
|
||||||
|
|
||||||
// Save in local database for next startup
|
// Save in local database for next startup
|
||||||
await Store.put(StoreKey.serverEndpoint, endpoint);
|
await SessionRepository.instance.write(SessionKey.serverEndpoint, endpoint);
|
||||||
return endpoint;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,13 +173,13 @@ class ApiService {
|
|||||||
|
|
||||||
static List<String> getServerUrls() {
|
static List<String> getServerUrls() {
|
||||||
final urls = <String>[];
|
final urls = <String>[];
|
||||||
final serverEndpoint = Store.tryGet(StoreKey.serverEndpoint);
|
final serverEndpoint = SessionRepository.instance.session.serverEndpoint;
|
||||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
||||||
urls.add(serverEndpoint);
|
urls.add(serverEndpoint);
|
||||||
}
|
}
|
||||||
final network = SettingsRepository.instance.appConfig.network;
|
final network = SettingsRepository.instance.appConfig.network;
|
||||||
final localEndpoint = network.localEndpoint;
|
final localEndpoint = network.localEndpoint;
|
||||||
if (localEndpoint.isNotEmpty) {
|
if (localEndpoint != null && localEndpoint.isNotEmpty) {
|
||||||
urls.add(localEndpoint);
|
urls.add(localEndpoint);
|
||||||
}
|
}
|
||||||
for (final url in network.externalEndpointList) {
|
for (final url in network.externalEndpointList) {
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
|
|
||||||
enum AppSettingsEnum<T> {
|
|
||||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
|
|
||||||
manageLocalMediaAndroid<bool>(StoreKey.manageLocalMediaAndroid, null, false),
|
|
||||||
enableHapticFeedback<bool>(StoreKey.enableHapticFeedback, null, true),
|
|
||||||
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false);
|
|
||||||
|
|
||||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
|
||||||
|
|
||||||
final StoreKey<T> storeKey;
|
|
||||||
final String? hiveKey;
|
|
||||||
final T defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppSettingsService {
|
|
||||||
const AppSettingsService();
|
|
||||||
T getSetting<T>(AppSettingsEnum<T> setting) {
|
|
||||||
return Store.get(setting.storeKey, setting.defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setSetting<T>(AppSettingsEnum<T> setting, T value) {
|
|
||||||
return Store.put(setting.storeKey, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/settings_key.dart';
|
import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
@@ -55,7 +55,7 @@ class AuthService {
|
|||||||
Future<String> validateServerUrl(String url) async {
|
Future<String> validateServerUrl(String url) async {
|
||||||
final validUrl = await _apiService.resolveAndSetEndpoint(url);
|
final validUrl = await _apiService.resolveAndSetEndpoint(url);
|
||||||
await _apiService.setDeviceInfoHeader();
|
await _apiService.setDeviceInfoHeader();
|
||||||
await Store.put(StoreKey.serverUrl, validUrl);
|
await SessionRepository.instance.write(SessionKey.serverUrl, validUrl);
|
||||||
|
|
||||||
return validUrl;
|
return validUrl;
|
||||||
}
|
}
|
||||||
@@ -118,8 +118,7 @@ class AuthService {
|
|||||||
await _backgroundSyncManager.cancel();
|
await _backgroundSyncManager.cancel();
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
_authRepository.clearLocalData(),
|
_authRepository.clearLocalData(),
|
||||||
Store.delete(StoreKey.currentUser),
|
SessionRepository.instance.clear([SessionKey.accessToken]),
|
||||||
Store.delete(StoreKey.accessToken),
|
|
||||||
SettingsRepository.instance.clear(const [
|
SettingsRepository.instance.clear(const [
|
||||||
.networkAutoEndpointSwitching,
|
.networkAutoEndpointSwitching,
|
||||||
.networkPreferredWifiName,
|
.networkPreferredWifiName,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
@@ -386,7 +387,7 @@ class BackgroundUploadService {
|
|||||||
String? latitude,
|
String? latitude,
|
||||||
String? longitude,
|
String? longitude,
|
||||||
}) async {
|
}) async {
|
||||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
final serverEndpoint = SessionRepository.instance.session.serverEndpoint!;
|
||||||
final url = Uri.parse('$serverEndpoint/assets').toString();
|
final url = Uri.parse('$serverEndpoint/assets').toString();
|
||||||
final headers = ApiService.getRequestHeaders();
|
final headers = ApiService.getRequestHeaders();
|
||||||
final deviceId = Store.get(StoreKey.deviceId);
|
final deviceId = Store.get(StoreKey.deviceId);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
@@ -51,6 +52,8 @@ abstract final class Bootstrap {
|
|||||||
|
|
||||||
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
|
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
|
||||||
|
|
||||||
|
await SessionRepository.ensureInitialized(drift);
|
||||||
|
|
||||||
final settingsRepo = await SettingsRepository.ensureInitialized(drift);
|
final settingsRepo = await SettingsRepository.ensureInitialized(drift);
|
||||||
|
|
||||||
await LogService.init(
|
await LogService.init(
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
|
|
||||||
ValueNotifier<T> useAppSettingsState<T>(AppSettingsEnum<T> key) {
|
|
||||||
final notifier = useState<T>(Store.get(key.storeKey, key.defaultValue));
|
|
||||||
|
|
||||||
// Listen to changes to the notifier and update app settings
|
|
||||||
useValueChanged(notifier.value, (_, __) => Store.put(key.storeKey, notifier.value));
|
|
||||||
|
|
||||||
return notifier;
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
String getOriginalUrlForRemoteId(final String id, {bool edited = true}) {
|
String getOriginalUrlForRemoteId(final String id, {bool edited = true}) {
|
||||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/original?edited=$edited';
|
return '${SessionRepository.instance.session.serverEndpoint!}/assets/$id/original?edited=$edited';
|
||||||
}
|
}
|
||||||
|
|
||||||
String getThumbnailUrlForRemoteId(
|
String getThumbnailUrlForRemoteId(
|
||||||
@@ -12,14 +11,15 @@ String getThumbnailUrlForRemoteId(
|
|||||||
bool edited = true,
|
bool edited = true,
|
||||||
String? thumbhash,
|
String? thumbhash,
|
||||||
}) {
|
}) {
|
||||||
final url = '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}&edited=$edited';
|
final url =
|
||||||
|
'${SessionRepository.instance.session.serverEndpoint!}/assets/$id/thumbnail?size=${type.value}&edited=$edited';
|
||||||
return thumbhash != null ? '$url&c=${Uri.encodeComponent(thumbhash)}' : url;
|
return thumbhash != null ? '$url&c=${Uri.encodeComponent(thumbhash)}' : url;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPlaybackUrlForRemoteId(final String id) {
|
String getPlaybackUrlForRemoteId(final String id) {
|
||||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/video/playback?';
|
return '${SessionRepository.instance.session.serverEndpoint!}/assets/$id/video/playback?';
|
||||||
}
|
}
|
||||||
|
|
||||||
String getFaceThumbnailUrl(final String personId) {
|
String getFaceThumbnailUrl(final String personId) {
|
||||||
return '${Store.get(StoreKey.serverEndpoint)}/people/$personId/thumbnail';
|
return '${SessionRepository.instance.session.serverEndpoint!}/people/$personId/thumbnail';
|
||||||
}
|
}
|
||||||
|
|||||||
+174
-93
@@ -6,22 +6,29 @@ import 'package:drift/drift.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/settings_key.dart';
|
import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/session.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
|
||||||
const int targetVersion = 26;
|
|
||||||
|
|
||||||
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||||
final int version = Store.get(StoreKey.version, targetVersion);
|
final metadataRepository = AppMetadataRepository(drift);
|
||||||
|
|
||||||
|
final int version = await metadataRepository.get(AppMetadataKey.version);
|
||||||
|
|
||||||
if (version < 25) {
|
if (version < 25) {
|
||||||
await _migrateTo25();
|
await _migrateTo25();
|
||||||
@@ -31,26 +38,38 @@ Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
|||||||
await _migrateTo26(drift);
|
await _migrateTo26(drift);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Store.put(StoreKey.version, targetVersion);
|
if (version < 27) {
|
||||||
|
await _migrateTo27(drift);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 28) {
|
||||||
|
await _migrateTo28(drift);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version < 29) {
|
||||||
|
await _migrateTo29(drift);
|
||||||
|
}
|
||||||
|
|
||||||
|
await metadataRepository.set(AppMetadataKey.version, kCurrentVersion);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateTo25() async {
|
Future<void> _migrateTo25() async {
|
||||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
final accessToken = Store.tryGet(.legacyAccessToken);
|
||||||
if (accessToken == null || accessToken.isEmpty) {
|
if (accessToken == null || accessToken.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final urls = <String>[];
|
final urls = <String>[];
|
||||||
final serverEndpoint = Store.tryGet(StoreKey.serverEndpoint);
|
final serverEndpoint = Store.tryGet(.legacyServerEndpoint);
|
||||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
||||||
urls.add(serverEndpoint);
|
urls.add(serverEndpoint);
|
||||||
}
|
}
|
||||||
final localEndpoint = Store.tryGet(StoreKey.legacyLocalEndpoint);
|
final localEndpoint = Store.tryGet(.legacyLocalEndpoint);
|
||||||
if (localEndpoint != null && localEndpoint.isNotEmpty) {
|
if (localEndpoint != null && localEndpoint.isNotEmpty) {
|
||||||
urls.add(localEndpoint);
|
urls.add(localEndpoint);
|
||||||
}
|
}
|
||||||
final externalJson = Store.tryGet(StoreKey.legacyExternalEndpointList);
|
final externalJson = Store.tryGet(.legacyExternalEndpointList);
|
||||||
if (externalJson != null) {
|
if (externalJson != null) {
|
||||||
final List<dynamic> list = jsonDecode(externalJson);
|
final List<dynamic> list = jsonDecode(externalJson);
|
||||||
for (final entry in list) {
|
for (final entry in list) {
|
||||||
@@ -64,7 +83,7 @@ Future<void> _migrateTo25() async {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final customHeadersStr = Store.get(StoreKey.legacyCustomHeaders, "");
|
final customHeadersStr = Store.get(.legacyCustomHeaders, "");
|
||||||
final headers = customHeadersStr.isEmpty
|
final headers = customHeadersStr.isEmpty
|
||||||
? const <String, String>{}
|
? const <String, String>{}
|
||||||
: (jsonDecode(customHeadersStr) as Map).cast<String, String>();
|
: (jsonDecode(customHeadersStr) as Map).cast<String, String>();
|
||||||
@@ -73,81 +92,109 @@ Future<void> _migrateTo25() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateTo26(Drift drift) async {
|
Future<void> _migrateTo26(Drift drift) async {
|
||||||
final migrator = _StoreMigrator(drift);
|
final migrator = _StoreMigrator.settings(drift);
|
||||||
await migrator.migrateEnumIndex(StoreKey.legacyLogLevel, SettingsKey.logLevel, LogLevel.values);
|
await migrator.migrateEnumIndex(.legacyLogLevel, .logLevel, LogLevel.values);
|
||||||
// Theme
|
// Theme
|
||||||
await migrator.migrateEnumName(StoreKey.legacyThemeMode, SettingsKey.themeMode, ThemeMode.values);
|
await migrator.migrateEnumName(.legacyThemeMode, .themeMode, ThemeMode.values);
|
||||||
await migrator.migrateEnumName(StoreKey.legacyPrimaryColor, SettingsKey.themePrimaryColor, ImmichColorPreset.values);
|
await migrator.migrateEnumName(.legacyPrimaryColor, .themePrimaryColor, ImmichColorPreset.values);
|
||||||
await migrator.migrateBool(StoreKey.legacyDynamicTheme, SettingsKey.themeDynamic);
|
await migrator.migrateBool(.legacyDynamicTheme, .themeDynamic);
|
||||||
await migrator.migrateBool(StoreKey.legacyColorfulInterface, SettingsKey.themeColorfulInterface);
|
await migrator.migrateBool(.legacyColorfulInterface, .themeColorfulInterface);
|
||||||
// Cleanup
|
// Cleanup
|
||||||
final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(StoreKey.legacyCleanupKeepAlbumIds.id);
|
final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(.legacyCleanupKeepAlbumIds);
|
||||||
if (cleanupKeepAlbumIds != null) {
|
if (cleanupKeepAlbumIds != null) {
|
||||||
final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList();
|
final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList();
|
||||||
migrator.stage(StoreKey.legacyCleanupKeepAlbumIds, SettingsKey.cleanupKeepAlbumIds, ids);
|
migrator.stage(.legacyCleanupKeepAlbumIds, .cleanupKeepAlbumIds, ids);
|
||||||
}
|
}
|
||||||
await migrator.migrateBool(StoreKey.legacyCleanupKeepFavorites, SettingsKey.cleanupKeepFavorites);
|
await migrator.migrateBool(.legacyCleanupKeepFavorites, .cleanupKeepFavorites);
|
||||||
await migrator.migrateEnumIndex(
|
await migrator.migrateEnumIndex(.legacyCleanupKeepMediaType, .cleanupKeepMediaType, AssetKeepType.values);
|
||||||
StoreKey.legacyCleanupKeepMediaType,
|
await migrator.migrateInt(.legacyCleanupCutoffDaysAgo, .cleanupCutoffDaysAgo);
|
||||||
SettingsKey.cleanupKeepMediaType,
|
await migrator.migrateBool(.legacyCleanupDefaultsInitialized, .cleanupDefaultsInitialized);
|
||||||
AssetKeepType.values,
|
|
||||||
);
|
|
||||||
await migrator.migrateInt(StoreKey.legacyCleanupCutoffDaysAgo, SettingsKey.cleanupCutoffDaysAgo);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyCleanupDefaultsInitialized, SettingsKey.cleanupDefaultsInitialized);
|
|
||||||
// Map
|
// Map
|
||||||
await migrator.migrateBool(StoreKey.legacyMapShowFavoriteOnly, SettingsKey.mapShowFavoriteOnly);
|
await migrator.migrateBool(.legacyMapShowFavoriteOnly, .mapShowFavoriteOnly);
|
||||||
await migrator.migrateInt(StoreKey.legacyMapRelativeDate, SettingsKey.mapRelativeDate);
|
await migrator.migrateInt(.legacyMapRelativeDate, .mapRelativeDate);
|
||||||
await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, SettingsKey.mapIncludeArchived);
|
await migrator.migrateBool(.legacyMapIncludeArchived, .mapIncludeArchived);
|
||||||
await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, SettingsKey.mapThemeMode, ThemeMode.values);
|
await migrator.migrateEnumIndex(.legacyMapThemeMode, .mapThemeMode, ThemeMode.values);
|
||||||
await migrator.migrateBool(StoreKey.legacyMapwithPartners, SettingsKey.mapWithPartners);
|
await migrator.migrateBool(.legacyMapwithPartners, .mapWithPartners);
|
||||||
// Timeline
|
// Timeline
|
||||||
await migrator.migrateInt(StoreKey.legacyTilesPerRow, SettingsKey.timelineTilesPerRow);
|
await migrator.migrateInt(.legacyTilesPerRow, .timelineTilesPerRow);
|
||||||
await migrator.migrateEnumIndex(
|
await migrator.migrateEnumIndex(.legacyGroupAssetsBy, .timelineGroupAssetsBy, GroupAssetsBy.values);
|
||||||
StoreKey.legacyGroupAssetsBy,
|
await migrator.migrateBool(.legacyStorageIndicator, .timelineStorageIndicator);
|
||||||
SettingsKey.timelineGroupAssetsBy,
|
|
||||||
GroupAssetsBy.values,
|
|
||||||
);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyStorageIndicator, SettingsKey.timelineStorageIndicator);
|
|
||||||
// Image
|
// Image
|
||||||
await migrator.migrateBool(StoreKey.legacyPreferRemoteImage, SettingsKey.imagePreferRemote);
|
await migrator.migrateBool(.legacyPreferRemoteImage, .imagePreferRemote);
|
||||||
await migrator.migrateBool(StoreKey.legacyLoadOriginal, SettingsKey.imageLoadOriginal);
|
await migrator.migrateBool(.legacyLoadOriginal, .imageLoadOriginal);
|
||||||
// Viewer
|
// Viewer
|
||||||
await migrator.migrateBool(StoreKey.legacyLoopVideo, SettingsKey.viewerLoopVideo);
|
await migrator.migrateBool(.legacyLoopVideo, .viewerLoopVideo);
|
||||||
await migrator.migrateBool(StoreKey.legacyLoadOriginalVideo, SettingsKey.viewerLoadOriginalVideo);
|
await migrator.migrateBool(.legacyLoadOriginalVideo, .viewerLoadOriginalVideo);
|
||||||
await migrator.migrateBool(StoreKey.legacyAutoPlayVideo, SettingsKey.viewerAutoPlayVideo);
|
await migrator.migrateBool(.legacyAutoPlayVideo, .viewerAutoPlayVideo);
|
||||||
await migrator.migrateBool(StoreKey.legacyTapToNavigate, SettingsKey.viewerTapToNavigate);
|
await migrator.migrateBool(.legacyTapToNavigate, .viewerTapToNavigate);
|
||||||
// Network
|
// Network
|
||||||
await migrator.migrateBool(StoreKey.legacyAutoEndpointSwitching, SettingsKey.networkAutoEndpointSwitching);
|
await migrator.migrateBool(.legacyAutoEndpointSwitching, .networkAutoEndpointSwitching);
|
||||||
await migrator.migrateString(StoreKey.legacyPreferredWifiName, SettingsKey.networkPreferredWifiName);
|
final preferredWifiName = await migrator.readLegacyStoreString(.legacyPreferredWifiName);
|
||||||
await migrator.migrateString(StoreKey.legacyLocalEndpoint, SettingsKey.networkLocalEndpoint);
|
migrator.stage(.legacyPreferredWifiName, .networkPreferredWifiName, preferredWifiName);
|
||||||
|
final localEndpoint = await migrator.readLegacyStoreString(.legacyLocalEndpoint);
|
||||||
|
migrator.stage(.legacyLocalEndpoint, .networkLocalEndpoint, localEndpoint);
|
||||||
await _migrateExternalEndpointList(migrator);
|
await _migrateExternalEndpointList(migrator);
|
||||||
await _migrateCustomHeaders(migrator);
|
await _migrateCustomHeaders(migrator);
|
||||||
// Album
|
// Album
|
||||||
await _migrateAlbumSortMode(migrator);
|
await _migrateAlbumSortMode(migrator);
|
||||||
await migrator.migrateBool(StoreKey.legacySelectedAlbumSortReverse, SettingsKey.albumIsReverse);
|
await migrator.migrateBool(.legacySelectedAlbumSortReverse, .albumIsReverse);
|
||||||
await migrator.migrateBool(StoreKey.legacyAlbumGridView, SettingsKey.albumIsGrid);
|
await migrator.migrateBool(.legacyAlbumGridView, .albumIsGrid);
|
||||||
// Backup
|
// Backup
|
||||||
await migrator.migrateBool(StoreKey.legacyEnableBackup, SettingsKey.backupEnabled);
|
await migrator.migrateBool(.legacyEnableBackup, .backupEnabled);
|
||||||
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadVideos, SettingsKey.backupUseCellularForVideos);
|
await migrator.migrateBool(.legacyUseWifiForUploadVideos, .backupUseCellularForVideos);
|
||||||
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadPhotos, SettingsKey.backupUseCellularForPhotos);
|
await migrator.migrateBool(.legacyUseWifiForUploadPhotos, .backupUseCellularForPhotos);
|
||||||
await migrator.migrateBool(StoreKey.legacyBackupRequireCharging, SettingsKey.backupRequireCharging);
|
await migrator.migrateBool(.legacyBackupRequireCharging, .backupRequireCharging);
|
||||||
await migrator.migrateInt(StoreKey.legacyBackupTriggerDelay, SettingsKey.backupTriggerDelay);
|
await migrator.migrateInt(.legacyBackupTriggerDelay, .backupTriggerDelay);
|
||||||
await migrator.migrateBool(StoreKey.legacySyncAlbums, SettingsKey.backupSyncAlbums);
|
await migrator.migrateBool(.legacySyncAlbums, .backupSyncAlbums);
|
||||||
await migrator.complete();
|
await migrator.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateAlbumSortMode(_StoreMigrator migrator) async {
|
Future<void> _migrateTo27(Drift drift) async {
|
||||||
final raw = await migrator.readLegacyStoreInt(StoreKey.legacySelectedAlbumSortOrder.id);
|
final migrator = _StoreMigrator.session(drift);
|
||||||
|
await migrator.migrateString(.legacyServerUrl, .serverUrl);
|
||||||
|
await migrator.migrateString(.legacyAccessToken, .accessToken);
|
||||||
|
await migrator.migrateString(.legacyServerEndpoint, .serverEndpoint);
|
||||||
|
await migrator.complete();
|
||||||
|
|
||||||
|
await SessionRepository.instance.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _migrateTo28(Drift drift) async {
|
||||||
|
final migrator = _StoreMigrator.settings(drift);
|
||||||
|
await migrator.migrateBool(.legacyAdvancedTroubleshooting, .advancedTroubleshooting);
|
||||||
|
await migrator.migrateBool(.legacyEnableHapticFeedback, .advancedEnableHapticFeedback);
|
||||||
|
await migrator.migrateBool(.legacyReadonlyModeEnabled, .advancedReadonlyModeEnabled);
|
||||||
|
await migrator.complete();
|
||||||
|
|
||||||
|
await SettingsRepository.instance.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _migrateTo29(Drift drift) async {
|
||||||
|
final migrator = _StoreMigrator.appMetadata(drift);
|
||||||
|
|
||||||
|
final rawStatus = await migrator.readLegacyStoreString(.legacySyncMigrationStatus);
|
||||||
|
if (rawStatus != null) {
|
||||||
|
final decoded = jsonDecode(rawStatus);
|
||||||
|
final migrations = decoded is List ? decoded.whereType<String>().toList() : <String>[];
|
||||||
|
migrator.stage(.legacySyncMigrationStatus, .syncMigrationStatus, migrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
await migrator.migrateBool(.legacyManageLocalMediaAndroid, .manageLocalMediaAndroid);
|
||||||
|
await migrator.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _migrateAlbumSortMode(_StoreMigrator<SettingsKey> migrator) async {
|
||||||
|
final raw = await migrator.readLegacyStoreInt(.legacySelectedAlbumSortOrder);
|
||||||
final mode = AlbumSortMode.values.firstWhereOrNull((e) => raw != null && e.storeIndex == raw);
|
final mode = AlbumSortMode.values.firstWhereOrNull((e) => raw != null && e.storeIndex == raw);
|
||||||
if (mode == null) {
|
if (mode == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
migrator.stage(StoreKey.legacySelectedAlbumSortOrder, SettingsKey.albumSortMode, mode);
|
migrator.stage(.legacySelectedAlbumSortOrder, .albumSortMode, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateExternalEndpointList(_StoreMigrator migrator) async {
|
Future<void> _migrateExternalEndpointList(_StoreMigrator<SettingsKey> migrator) async {
|
||||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyExternalEndpointList.id);
|
final raw = await migrator.readLegacyStoreString(.legacyExternalEndpointList);
|
||||||
if (raw == null) {
|
if (raw == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -170,8 +217,8 @@ Future<void> _migrateExternalEndpointList(_StoreMigrator migrator) async {
|
|||||||
migrator.stage(StoreKey.legacyExternalEndpointList, SettingsKey.networkExternalEndpointList, urls);
|
migrator.stage(StoreKey.legacyExternalEndpointList, SettingsKey.networkExternalEndpointList, urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateCustomHeaders(_StoreMigrator migrator) async {
|
Future<void> _migrateCustomHeaders(_StoreMigrator<SettingsKey> migrator) async {
|
||||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyCustomHeaders.id);
|
final raw = await migrator.readLegacyStoreString(.legacyCustomHeaders);
|
||||||
if (raw == null) {
|
if (raw == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -193,15 +240,51 @@ Future<void> _migrateCustomHeaders(_StoreMigrator migrator) async {
|
|||||||
migrator.stage(StoreKey.legacyCustomHeaders, SettingsKey.networkCustomHeaders, headers);
|
migrator.stage(StoreKey.legacyCustomHeaders, SettingsKey.networkCustomHeaders, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StoreMigrator {
|
class _StoreMigrator<K extends Enum> {
|
||||||
|
_StoreMigrator._(this._db, {required this.encode, required this.readDefault, required this.insertRow});
|
||||||
|
|
||||||
|
static _StoreMigrator<SettingsKey> settings(Drift db) => _StoreMigrator<SettingsKey>._(
|
||||||
|
db,
|
||||||
|
encode: (key, value) => key.encode(value),
|
||||||
|
readDefault: (key) => defaultConfig.read(key),
|
||||||
|
insertRow: (batch, name, value) => batch.insert(
|
||||||
|
db.settingsEntity,
|
||||||
|
SettingsEntityCompanion(key: Value(name), value: Value(value)),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
static _StoreMigrator<SessionKey> session(Drift db) => _StoreMigrator<SessionKey>._(
|
||||||
|
db,
|
||||||
|
encode: (key, value) => key.encode(value),
|
||||||
|
readDefault: (key) => defaultSession.read(key),
|
||||||
|
insertRow: (batch, name, value) => batch.insert(
|
||||||
|
db.sessionEntity,
|
||||||
|
SessionEntityCompanion(key: Value(name), value: Value(value)),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
static _StoreMigrator<AppMetadataKey> appMetadata(Drift db) => _StoreMigrator<AppMetadataKey>._(
|
||||||
|
db,
|
||||||
|
encode: (key, value) => key.encode(value),
|
||||||
|
readDefault: (_) => null,
|
||||||
|
insertRow: (batch, name, value) => batch.insert(
|
||||||
|
db.appMetadataEntity,
|
||||||
|
AppMetadataEntityCompanion(key: Value(name), value: Value(value)),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final Drift _db;
|
final Drift _db;
|
||||||
final Map<SettingsKey<Object>, Object> _cache = {};
|
final String Function(K key, Object value) encode;
|
||||||
|
final Object? Function(K key) readDefault;
|
||||||
|
final void Function(Batch batch, String name, String? value) insertRow;
|
||||||
|
final Map<K, Object?> _cache = {};
|
||||||
final List<int> _migratedStoreIds = [];
|
final List<int> _migratedStoreIds = [];
|
||||||
|
|
||||||
_StoreMigrator(this._db);
|
Future<void> migrateEnumIndex<T extends Enum>(StoreKey<int> legacyKey, K newKey, List<T> values) async {
|
||||||
|
final index = await readLegacyStoreInt(legacyKey);
|
||||||
Future<void> migrateEnumIndex<T extends Enum>(StoreKey<int> legacyKey, SettingsKey<T> newKey, List<T> values) async {
|
|
||||||
final index = await readLegacyStoreInt(legacyKey.id);
|
|
||||||
if (index == null) {
|
if (index == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -215,12 +298,8 @@ class _StoreMigrator {
|
|||||||
_migratedStoreIds.add(legacyKey.id);
|
_migratedStoreIds.add(legacyKey.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> migrateEnumName<T extends Enum>(
|
Future<void> migrateEnumName<T extends Enum>(StoreKey<String> legacyKey, K newKey, List<T> values) async {
|
||||||
StoreKey<String> legacyKey,
|
final name = await readLegacyStoreString(legacyKey);
|
||||||
SettingsKey<T> newKey,
|
|
||||||
List<T> values,
|
|
||||||
) async {
|
|
||||||
final name = await readLegacyStoreString(legacyKey.id);
|
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -234,19 +313,18 @@ class _StoreMigrator {
|
|||||||
_migratedStoreIds.add(legacyKey.id);
|
_migratedStoreIds.add(legacyKey.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> migrateBool(StoreKey<bool> legacyKey, SettingsKey<bool> newKey) async {
|
Future<void> migrateBool(StoreKey<bool> legacyKey, K newKey) async {
|
||||||
final intValue = await readLegacyStoreInt(legacyKey.id);
|
final intValue = await readLegacyStoreInt(legacyKey);
|
||||||
if (intValue == null) {
|
if (intValue == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolValue = intValue != 0;
|
_cache[newKey] = intValue != 0;
|
||||||
_cache[newKey] = boolValue;
|
|
||||||
_migratedStoreIds.add(legacyKey.id);
|
_migratedStoreIds.add(legacyKey.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> migrateInt(StoreKey<int> legacyKey, SettingsKey<int> newKey) async {
|
Future<void> migrateInt(StoreKey<int> legacyKey, K newKey) async {
|
||||||
final intValue = await readLegacyStoreInt(legacyKey.id);
|
final intValue = await readLegacyStoreInt(legacyKey);
|
||||||
if (intValue == null) {
|
if (intValue == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -255,9 +333,9 @@ class _StoreMigrator {
|
|||||||
_migratedStoreIds.add(legacyKey.id);
|
_migratedStoreIds.add(legacyKey.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> migrateString(StoreKey<String> legacyKey, SettingsKey<String> newKey) async {
|
Future<void> migrateString(StoreKey<String> legacyKey, K newKey) async {
|
||||||
final value = await readLegacyStoreString(legacyKey.id);
|
final value = await readLegacyStoreString(legacyKey);
|
||||||
if (value == null) {
|
if (value == null || value.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +343,12 @@ class _StoreMigrator {
|
|||||||
_migratedStoreIds.add(legacyKey.id);
|
_migratedStoreIds.add(legacyKey.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stage<T extends Object>(StoreKey legacyKey, SettingsKey<T> newKey, T value) {
|
Future<void> migrateNullableString(StoreKey<String> legacyKey, K newKey) async {
|
||||||
|
_cache[newKey] = await readLegacyStoreString(legacyKey);
|
||||||
|
_migratedStoreIds.add(legacyKey.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stage(StoreKey legacyKey, K newKey, Object? value) {
|
||||||
_cache[newKey] = value;
|
_cache[newKey] = value;
|
||||||
_migratedStoreIds.add(legacyKey.id);
|
_migratedStoreIds.add(legacyKey.id);
|
||||||
}
|
}
|
||||||
@@ -273,26 +356,24 @@ class _StoreMigrator {
|
|||||||
Future<void> complete() async {
|
Future<void> complete() async {
|
||||||
await _db.batch((batch) {
|
await _db.batch((batch) {
|
||||||
for (final entry in _cache.entries) {
|
for (final entry in _cache.entries) {
|
||||||
if (entry.value == defaultConfig.read(entry.key)) {
|
if (entry.value == readDefault(entry.key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
batch.insert(
|
|
||||||
_db.settingsEntity,
|
final value = entry.value;
|
||||||
SettingsEntityCompanion(key: Value(entry.key.name), value: Value(entry.key.encode(entry.value))),
|
insertRow(batch, entry.key.name, value == null ? null : encode(entry.key, value));
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await deleteLegacyStoreRows(_migratedStoreIds);
|
await deleteLegacyStoreRows(_migratedStoreIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> readLegacyStoreString(int id) async {
|
Future<String?> readLegacyStoreString(StoreKey key) async {
|
||||||
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull();
|
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(key.id))).getSingleOrNull();
|
||||||
return row?.stringValue;
|
return row?.stringValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int?> readLegacyStoreInt(int id) async {
|
Future<int?> readLegacyStoreInt(StoreKey key) async {
|
||||||
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull();
|
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(key.id))).getSingleOrNull();
|
||||||
return row?.intValue;
|
return row?.intValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ final class None<T> extends Option<T> {
|
|||||||
int get hashCode => 0;
|
int get hashCode => 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ObjectOptionExtension<T> on T? {
|
extension NullableOptionExtension<T> on Option<T>? {
|
||||||
Option<T> toOption() => Option.fromNullable(this);
|
T? patch(T? current) => this == null ? current : this!.unwrapOrNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OptionToOptional<T> on Option<T> {
|
extension OptionToOptional<T> on Option<T> {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:punycode/punycode.dart';
|
import 'package:punycode/punycode.dart';
|
||||||
|
|
||||||
String sanitizeUrl(String url) {
|
String sanitizeUrl(String url) {
|
||||||
@@ -11,7 +10,7 @@ String sanitizeUrl(String url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String? getServerUrl() {
|
String? getServerUrl() {
|
||||||
final serverUrl = punycodeDecodeUrl(Store.tryGet(StoreKey.serverEndpoint));
|
final serverUrl = punycodeDecodeUrl(SessionRepository.instance.session.serverEndpoint);
|
||||||
final serverUri = serverUrl != null ? Uri.tryParse(serverUrl) : null;
|
final serverUri = serverUrl != null ? Uri.tryParse(serverUrl) : null;
|
||||||
if (serverUri == null) {
|
if (serverUri == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||||
|
|
||||||
Widget userAvatar(BuildContext context, UserDto u, {double? radius}) {
|
Widget userAvatar(BuildContext context, UserDto u, {double? radius}) {
|
||||||
final url = "${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image";
|
final url = "${SessionRepository.instance.session.serverEndpoint!}/users/${u.id}/profile-image";
|
||||||
final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : "";
|
final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : "";
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
radius: radius,
|
radius: radius,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/session.provider.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class UserCircleAvatar extends ConsumerWidget {
|
class UserCircleAvatar extends ConsumerWidget {
|
||||||
@@ -18,7 +17,7 @@ class UserCircleAvatar extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final userAvatarColor = user.avatarColor.toColor().withValues(alpha: opacity);
|
final userAvatarColor = user.avatarColor.toColor().withValues(alpha: opacity);
|
||||||
final profileImageUrl =
|
final profileImageUrl =
|
||||||
'${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${user.profileChangedAt.millisecondsSinceEpoch}';
|
'${ref.read(sessionProvider).serverEndpoint}/users/${user.id}/profile-image?d=${user.profileChangedAt.millisecondsSinceEpoch}';
|
||||||
|
|
||||||
final textColor = (user.avatarColor.toColor().computeLuminance() > 0.5 ? Colors.black : Colors.white).withValues(
|
final textColor = (user.avatarColor.toColor().computeLuminance() > 0.5 ? Colors.black : Colors.white).withValues(
|
||||||
alpha: opacity,
|
alpha: opacity,
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||||
import 'package:immich_mobile/providers/oauth.provider.dart';
|
import 'package:immich_mobile/providers/oauth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart';
|
import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart';
|
||||||
@@ -242,7 +242,8 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSyncRemoteDeletionsMode() => Platform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false);
|
Future<bool> isSyncRemoteDeletionsMode() async =>
|
||||||
|
Platform.isAndroid && await ref.read(appMetadataRepositoryProvider).get(AppMetadataKey.manageLocalMediaAndroid);
|
||||||
|
|
||||||
login() async {
|
login() async {
|
||||||
TextInput.finishAutofillContext();
|
TextInput.finishAutofillContext();
|
||||||
@@ -257,7 +258,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
unawaited(context.pushRoute(const ChangePasswordRoute()));
|
unawaited(context.pushRoute(const ChangePasswordRoute()));
|
||||||
} else {
|
} else {
|
||||||
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||||
if (isSyncRemoteDeletionsMode()) {
|
if (await isSyncRemoteDeletionsMode()) {
|
||||||
await getManageMediaPermission();
|
await getManageMediaPermission();
|
||||||
}
|
}
|
||||||
unawaited(handleSyncFlow());
|
unawaited(handleSyncFlow());
|
||||||
@@ -345,7 +346,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||||
if (isSyncRemoteDeletionsMode()) {
|
if (await isSyncRemoteDeletionsMode()) {
|
||||||
await getManageMediaPermission();
|
await getManageMediaPermission();
|
||||||
}
|
}
|
||||||
unawaited(handleSyncFlow());
|
unawaited(handleSyncFlow());
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/permission.repository.dart';
|
import 'package:immich_mobile/repositories/permission.repository.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||||
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
|
||||||
import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart';
|
import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_action_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_action_tile.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
||||||
@@ -27,8 +27,12 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final advancedTroubleshooting = useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
|
final advancedTroubleshooting = useState(ref.read(appConfigProvider).advanced.troubleshooting);
|
||||||
final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
|
useValueChanged(
|
||||||
|
advancedTroubleshooting.value,
|
||||||
|
(_, __) => ref.read(settingsProvider).write(.advancedTroubleshooting, advancedTroubleshooting.value),
|
||||||
|
);
|
||||||
|
final manageLocalMediaAndroid = useState(false);
|
||||||
final isManageMediaSupported = useState(false);
|
final isManageMediaSupported = useState(false);
|
||||||
final manageMediaAndroidPermission = useState(false);
|
final manageMediaAndroidPermission = useState(false);
|
||||||
final levelId = useState<int>(ref.read(appConfigProvider).logLevel.index);
|
final levelId = useState<int>(ref.read(appConfigProvider).logLevel.index);
|
||||||
@@ -37,7 +41,7 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
preferRemote.value,
|
preferRemote.value,
|
||||||
(_, __) => ref.read(settingsProvider).write(.imagePreferRemote, preferRemote.value),
|
(_, __) => ref.read(settingsProvider).write(.imagePreferRemote, preferRemote.value),
|
||||||
);
|
);
|
||||||
final readonlyModeEnabled = useAppSettingsState(AppSettingsEnum.readonlyModeEnabled);
|
final readonlyModeEnabled = useState(ref.read(appConfigProvider).advanced.readonlyModeEnabled);
|
||||||
|
|
||||||
final logLevel = Level.LEVELS[levelId.value].name;
|
final logLevel = Level.LEVELS[levelId.value].name;
|
||||||
|
|
||||||
@@ -57,6 +61,9 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
() async {
|
() async {
|
||||||
isManageMediaSupported.value = await checkAndroidVersion();
|
isManageMediaSupported.value = await checkAndroidVersion();
|
||||||
if (isManageMediaSupported.value) {
|
if (isManageMediaSupported.value) {
|
||||||
|
manageLocalMediaAndroid.value = await ref
|
||||||
|
.read(appMetadataRepositoryProvider)
|
||||||
|
.get(AppMetadataKey.manageLocalMediaAndroid);
|
||||||
manageMediaAndroidPermission.value = await ref.read(permissionRepositoryProvider).hasManageMediaPermission();
|
manageMediaAndroidPermission.value = await ref.read(permissionRepositoryProvider).hasManageMediaPermission();
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
@@ -83,6 +90,9 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
final result = await ref.read(permissionRepositoryProvider).requestManageMediaPermission();
|
final result = await ref.read(permissionRepositoryProvider).requestManageMediaPermission();
|
||||||
manageLocalMediaAndroid.value = result;
|
manageLocalMediaAndroid.value = result;
|
||||||
manageMediaAndroidPermission.value = result;
|
manageMediaAndroidPermission.value = result;
|
||||||
|
await ref.read(appMetadataRepositoryProvider).set(AppMetadataKey.manageLocalMediaAndroid, result);
|
||||||
|
} else {
|
||||||
|
await ref.read(appMetadataRepositoryProvider).set(AppMetadataKey.manageLocalMediaAndroid, false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
@@ -20,7 +19,6 @@ class GroupSettings extends HookConsumerWidget {
|
|||||||
|
|
||||||
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
|
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
|
||||||
await ref.read(settingsProvider).write(.timelineGroupAssetsBy, groupBy);
|
await ref.read(settingsProvider).write(.timelineGroupAssetsBy, groupBy);
|
||||||
ref.invalidate(appSettingsServiceProvider);
|
|
||||||
ref.invalidate(timelineServiceProvider);
|
ref.invalidate(timelineServiceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
||||||
@@ -32,9 +31,6 @@ class LayoutSettings extends HookConsumerWidget {
|
|||||||
maxValue: 6,
|
maxValue: 6,
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
noDivisons: 4,
|
noDivisons: 4,
|
||||||
onChangeEnd: (value) {
|
|
||||||
ref.invalidate(appSettingsServiceProvider);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart';
|
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart';
|
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart';
|
||||||
@@ -22,7 +21,6 @@ class AssetListSettings extends HookConsumerWidget {
|
|||||||
title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
|
title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
ref.read(settingsProvider).write(.timelineStorageIndicator, value);
|
ref.read(settingsProvider).write(.timelineStorageIndicator, value);
|
||||||
ref.invalidate(appSettingsServiceProvider);
|
|
||||||
ref.invalidate(settingsProvider);
|
ref.invalidate(settingsProvider);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||||
@@ -29,7 +28,6 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
|
|||||||
valueNotifier: isOriginal,
|
valueNotifier: isOriginal,
|
||||||
title: "setting_image_viewer_original_title".t(context: context),
|
title: "setting_image_viewer_original_title".t(context: context),
|
||||||
subtitle: "setting_image_viewer_original_subtitle".t(context: context),
|
subtitle: "setting_image_viewer_original_subtitle".t(context: context),
|
||||||
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
import 'package:immich_mobile/generated/translations.g.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/app_metadata.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
|
||||||
@@ -17,7 +18,6 @@ import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart';
|
import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/setting_list_tile.dart';
|
||||||
@@ -219,7 +219,7 @@ class _SyncStatsCounts extends ConsumerWidget {
|
|||||||
final localAlbumService = ref.watch(localAlbumServiceProvider);
|
final localAlbumService = ref.watch(localAlbumServiceProvider);
|
||||||
final remoteAlbumService = ref.watch(remoteAlbumServiceProvider);
|
final remoteAlbumService = ref.watch(remoteAlbumServiceProvider);
|
||||||
final memoryService = ref.watch(driftMemoryServiceProvider);
|
final memoryService = ref.watch(driftMemoryServiceProvider);
|
||||||
final appSettingsService = ref.watch(appSettingsServiceProvider);
|
final appMetadataRepository = ref.watch(appMetadataRepositoryProvider);
|
||||||
|
|
||||||
Future<List<dynamic>> loadCounts() async {
|
Future<List<dynamic>> loadCounts() async {
|
||||||
final assetCounts = assetService.getAssetCounts();
|
final assetCounts = assetService.getAssetCounts();
|
||||||
@@ -227,8 +227,16 @@ class _SyncStatsCounts extends ConsumerWidget {
|
|||||||
final remoteAlbumCounts = remoteAlbumService.getCount();
|
final remoteAlbumCounts = remoteAlbumService.getCount();
|
||||||
final memoryCount = memoryService.getCount();
|
final memoryCount = memoryService.getCount();
|
||||||
final getLocalHashedCount = assetService.getLocalHashedCount();
|
final getLocalHashedCount = assetService.getLocalHashedCount();
|
||||||
|
final manageLocalMediaAndroid = appMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid);
|
||||||
|
|
||||||
return await Future.wait([assetCounts, localAlbumCounts, remoteAlbumCounts, memoryCount, getLocalHashedCount]);
|
return await Future.wait([
|
||||||
|
assetCounts,
|
||||||
|
localAlbumCounts,
|
||||||
|
remoteAlbumCounts,
|
||||||
|
memoryCount,
|
||||||
|
getLocalHashedCount,
|
||||||
|
manageLocalMediaAndroid,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
@@ -254,14 +262,15 @@ class _SyncStatsCounts extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final assetCounts = snapshot.data![0]! as (int, int);
|
final assetCounts = snapshot.data![0] as (int, int);
|
||||||
final localAssetCount = assetCounts.$1;
|
final localAssetCount = assetCounts.$1;
|
||||||
final remoteAssetCount = assetCounts.$2;
|
final remoteAssetCount = assetCounts.$2;
|
||||||
|
|
||||||
final localAlbumCount = snapshot.data![1]! as int;
|
final localAlbumCount = snapshot.data![1] as int;
|
||||||
final remoteAlbumCount = snapshot.data![2]! as int;
|
final remoteAlbumCount = snapshot.data![2] as int;
|
||||||
final memoryCount = snapshot.data![3]! as int;
|
final memoryCount = snapshot.data![3] as int;
|
||||||
final localHashedCount = snapshot.data![4]! as int;
|
final localHashedCount = snapshot.data![4] as int;
|
||||||
|
final manageLocalMediaAndroid = snapshot.data![5] as bool;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
@@ -354,8 +363,7 @@ class _SyncStatsCounts extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// To be removed once the experimental feature is stable
|
// To be removed once the experimental feature is stable
|
||||||
if (CurrentPlatform.isAndroid &&
|
if (CurrentPlatform.isAndroid && manageLocalMediaAndroid) ...[
|
||||||
appSettingsService.getSetting<bool>(AppSettingsEnum.manageLocalMediaAndroid)) ...[
|
|
||||||
SettingGroupTitle(title: "trash".t(context: context)),
|
SettingGroupTitle(title: "trash".t(context: context)),
|
||||||
Consumer(
|
Consumer(
|
||||||
builder: (context, ref, _) {
|
builder: (context, ref, _) {
|
||||||
|
|||||||
@@ -2,21 +2,23 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||||
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
|
||||||
|
|
||||||
class HapticSetting extends HookConsumerWidget {
|
class HapticSetting extends HookConsumerWidget {
|
||||||
const HapticSetting({super.key});
|
const HapticSetting({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final hapticFeedbackSetting = useAppSettingsState(AppSettingsEnum.enableHapticFeedback);
|
final isHapticFeedbackEnabled = useState(ref.read(appConfigProvider).advanced.enableHapticFeedback);
|
||||||
final isHapticFeedbackEnabled = useValueNotifier(hapticFeedbackSetting.value);
|
useValueChanged(
|
||||||
|
isHapticFeedbackEnabled.value,
|
||||||
|
(_, __) => ref.read(settingsProvider).write(.advancedEnableHapticFeedback, isHapticFeedbackEnabled.value),
|
||||||
|
);
|
||||||
|
|
||||||
onHapticFeedbackChange(bool isEnabled) {
|
onHapticFeedbackChange(bool isEnabled) {
|
||||||
hapticFeedbackSetting.value = isEnabled;
|
isHapticFeedbackEnabled.value = isEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:immich_mobile/domain/services/partner.service.dart';
|
|||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class MockStoreService extends Mock implements StoreService {}
|
class MockStoreService extends Mock implements StoreService {}
|
||||||
@@ -11,6 +10,4 @@ class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {}
|
|||||||
|
|
||||||
class MockNativeSyncApi extends Mock implements NativeSyncApi {}
|
class MockNativeSyncApi extends Mock implements NativeSyncApi {}
|
||||||
|
|
||||||
class MockAppSettingsService extends Mock implements AppSettingsService {}
|
|
||||||
|
|
||||||
class MockPartnerService extends Mock implements PartnerService {}
|
class MockPartnerService extends Mock implements PartnerService {}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import 'package:drift/drift.dart' as drift;
|
|||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/local_sync.service.dart';
|
import 'package:immich_mobile/domain/services/local_sync.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
@@ -29,6 +29,7 @@ void main() {
|
|||||||
late AssetMediaRepository mockAssetMediaRepository;
|
late AssetMediaRepository mockAssetMediaRepository;
|
||||||
late MockPermissionRepository mockPermissionRepository;
|
late MockPermissionRepository mockPermissionRepository;
|
||||||
late MockNativeSyncApi mockNativeSyncApi;
|
late MockNativeSyncApi mockNativeSyncApi;
|
||||||
|
late MockAppMetadataRepository mockAppMetadataRepository;
|
||||||
late Drift db;
|
late Drift db;
|
||||||
|
|
||||||
setUpAll(() async {
|
setUpAll(() async {
|
||||||
@@ -52,6 +53,7 @@ void main() {
|
|||||||
mockAssetMediaRepository = MockAssetMediaRepository();
|
mockAssetMediaRepository = MockAssetMediaRepository();
|
||||||
mockPermissionRepository = MockPermissionRepository();
|
mockPermissionRepository = MockPermissionRepository();
|
||||||
mockNativeSyncApi = MockNativeSyncApi();
|
mockNativeSyncApi = MockNativeSyncApi();
|
||||||
|
mockAppMetadataRepository = MockAppMetadataRepository();
|
||||||
|
|
||||||
when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false);
|
when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false);
|
||||||
when(() => mockNativeSyncApi.getMediaChanges()).thenAnswer(
|
when(() => mockNativeSyncApi.getMediaChanges()).thenAnswer(
|
||||||
@@ -75,15 +77,16 @@ void main() {
|
|||||||
assetMediaRepository: mockAssetMediaRepository,
|
assetMediaRepository: mockAssetMediaRepository,
|
||||||
permissionRepository: mockPermissionRepository,
|
permissionRepository: mockPermissionRepository,
|
||||||
nativeSyncApi: mockNativeSyncApi,
|
nativeSyncApi: mockNativeSyncApi,
|
||||||
|
appMetadataRepository: mockAppMetadataRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false);
|
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('LocalSyncService - syncTrashedAssets gating', () {
|
group('LocalSyncService - syncTrashedAssets gating', () {
|
||||||
test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async {
|
test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
||||||
|
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
@@ -93,7 +96,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('skips syncTrashedAssets when store flag disabled', () async {
|
test('skips syncTrashedAssets when store flag disabled', () async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
||||||
|
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
@@ -102,7 +105,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async {
|
test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false);
|
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false);
|
||||||
|
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
@@ -114,7 +117,7 @@ void main() {
|
|||||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||||
addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android);
|
addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android);
|
||||||
|
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||||
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true);
|
||||||
|
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
|
|||||||
@@ -21,15 +21,15 @@ void main() {
|
|||||||
controller = StreamController<List<StoreDto<Object>>>.broadcast();
|
controller = StreamController<List<StoreDto<Object>>>.broadcast();
|
||||||
mockDriftStoreRepo = MockDriftStoreRepository();
|
mockDriftStoreRepo = MockDriftStoreRepository();
|
||||||
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
|
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
|
||||||
registerFallbackValue(StoreKey.accessToken);
|
registerFallbackValue(StoreKey.legacyAccessToken);
|
||||||
registerFallbackValue(StoreKey.version);
|
registerFallbackValue(StoreKey.legacyVersion);
|
||||||
registerFallbackValue(StoreKey.advancedTroubleshooting);
|
registerFallbackValue(StoreKey.legacyAdvancedTroubleshooting);
|
||||||
|
|
||||||
when(() => mockDriftStoreRepo.getAll()).thenAnswer(
|
when(() => mockDriftStoreRepo.getAll()).thenAnswer(
|
||||||
(_) async => [
|
(_) async => [
|
||||||
const StoreDto(StoreKey.accessToken, _kAccessToken),
|
const StoreDto(StoreKey.legacyAccessToken, _kAccessToken),
|
||||||
const StoreDto(StoreKey.advancedTroubleshooting, _kAdvancedTroubleshooting),
|
const StoreDto(StoreKey.legacyAdvancedTroubleshooting, _kAdvancedTroubleshooting),
|
||||||
const StoreDto(StoreKey.version, _kVersion),
|
const StoreDto(StoreKey.legacyVersion, _kVersion),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
when(() => mockDriftStoreRepo.watchAll()).thenAnswer((_) => controller.stream);
|
when(() => mockDriftStoreRepo.watchAll()).thenAnswer((_) => controller.stream);
|
||||||
@@ -45,35 +45,35 @@ void main() {
|
|||||||
group("Store Service Init:", () {
|
group("Store Service Init:", () {
|
||||||
test('Populates the internal cache on init', () {
|
test('Populates the internal cache on init', () {
|
||||||
verify(() => mockDriftStoreRepo.getAll()).called(1);
|
verify(() => mockDriftStoreRepo.getAll()).called(1);
|
||||||
expect(sut.tryGet(StoreKey.accessToken), _kAccessToken);
|
expect(sut.tryGet(StoreKey.legacyAccessToken), _kAccessToken);
|
||||||
expect(sut.tryGet(StoreKey.advancedTroubleshooting), _kAdvancedTroubleshooting);
|
expect(sut.tryGet(StoreKey.legacyAdvancedTroubleshooting), _kAdvancedTroubleshooting);
|
||||||
expect(sut.tryGet(StoreKey.version), _kVersion);
|
expect(sut.tryGet(StoreKey.legacyVersion), _kVersion);
|
||||||
// Other keys should be null
|
// Other keys should be null
|
||||||
expect(sut.tryGet(StoreKey.currentUser), isNull);
|
expect(sut.tryGet(StoreKey.deviceId), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Listens to stream of store updates', () async {
|
test('Listens to stream of store updates', () async {
|
||||||
final event = StoreDto(StoreKey.accessToken, _kAccessToken.toUpperCase());
|
final event = StoreDto(StoreKey.legacyAccessToken, _kAccessToken.toUpperCase());
|
||||||
controller.add([event]);
|
controller.add([event]);
|
||||||
|
|
||||||
await pumpEventQueue();
|
await pumpEventQueue();
|
||||||
|
|
||||||
verify(() => mockDriftStoreRepo.watchAll()).called(1);
|
verify(() => mockDriftStoreRepo.watchAll()).called(1);
|
||||||
expect(sut.tryGet(StoreKey.accessToken), _kAccessToken.toUpperCase());
|
expect(sut.tryGet(StoreKey.legacyAccessToken), _kAccessToken.toUpperCase());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Store Service get:', () {
|
group('Store Service get:', () {
|
||||||
test('Returns the stored value for the given key', () {
|
test('Returns the stored value for the given key', () {
|
||||||
expect(sut.get(StoreKey.accessToken), _kAccessToken);
|
expect(sut.get(StoreKey.legacyAccessToken), _kAccessToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Throws StoreKeyNotFoundException for nonexistent keys', () {
|
test('Throws StoreKeyNotFoundException for nonexistent keys', () {
|
||||||
expect(() => sut.get(StoreKey.currentUser), throwsA(isA<StoreKeyNotFoundException>()));
|
expect(() => sut.get(StoreKey.deviceId), throwsA(isA<StoreKeyNotFoundException>()));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Returns the stored value for the given key or the defaultValue', () {
|
test('Returns the stored value for the given key or the defaultValue', () {
|
||||||
expect(sut.get(StoreKey.currentUser, 5), 5);
|
expect(sut.get(StoreKey.legacyBackupTriggerDelay, 5), 5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -83,15 +83,15 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Skip insert when value is not modified', () async {
|
test('Skip insert when value is not modified', () async {
|
||||||
await sut.put(StoreKey.accessToken, _kAccessToken);
|
await sut.put(StoreKey.legacyAccessToken, _kAccessToken);
|
||||||
verifyNever(() => mockDriftStoreRepo.upsert<String>(StoreKey.accessToken, any()));
|
verifyNever(() => mockDriftStoreRepo.upsert<String>(StoreKey.legacyAccessToken, any()));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Insert value when modified', () async {
|
test('Insert value when modified', () async {
|
||||||
final newAccessToken = _kAccessToken.toUpperCase();
|
final newAccessToken = _kAccessToken.toUpperCase();
|
||||||
await sut.put(StoreKey.accessToken, newAccessToken);
|
await sut.put(StoreKey.legacyAccessToken, newAccessToken);
|
||||||
verify(() => mockDriftStoreRepo.upsert<String>(StoreKey.accessToken, newAccessToken)).called(1);
|
verify(() => mockDriftStoreRepo.upsert<String>(StoreKey.legacyAccessToken, newAccessToken)).called(1);
|
||||||
expect(sut.tryGet(StoreKey.accessToken), newAccessToken);
|
expect(sut.tryGet(StoreKey.legacyAccessToken), newAccessToken);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Watches a specific key for changes', () async {
|
test('Watches a specific key for changes', () async {
|
||||||
final stream = sut.watch(StoreKey.accessToken);
|
final stream = sut.watch(StoreKey.legacyAccessToken);
|
||||||
final events = <String?>[_kAccessToken, _kAccessToken.toUpperCase(), null, _kAccessToken.toLowerCase()];
|
final events = <String?>[_kAccessToken, _kAccessToken.toUpperCase(), null, _kAccessToken.toLowerCase()];
|
||||||
|
|
||||||
unawaited(expectLater(stream, emitsInOrder(events)));
|
unawaited(expectLater(stream, emitsInOrder(events)));
|
||||||
@@ -118,7 +118,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await pumpEventQueue();
|
await pumpEventQueue();
|
||||||
verify(() => mockDriftStoreRepo.watch<String>(StoreKey.accessToken)).called(1);
|
verify(() => mockDriftStoreRepo.watch<String>(StoreKey.legacyAccessToken)).called(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,13 +128,13 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Removes the value from the DB', () async {
|
test('Removes the value from the DB', () async {
|
||||||
await sut.delete(StoreKey.accessToken);
|
await sut.delete(StoreKey.legacyAccessToken);
|
||||||
verify(() => mockDriftStoreRepo.delete<String>(StoreKey.accessToken)).called(1);
|
verify(() => mockDriftStoreRepo.delete<String>(StoreKey.legacyAccessToken)).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Removes the value from the cache', () async {
|
test('Removes the value from the cache', () async {
|
||||||
await sut.delete(StoreKey.accessToken);
|
await sut.delete(StoreKey.legacyAccessToken);
|
||||||
expect(sut.tryGet(StoreKey.accessToken), isNull);
|
expect(sut.tryGet(StoreKey.legacyAccessToken), isNull);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -146,9 +146,9 @@ void main() {
|
|||||||
test('Clears all values from the store', () async {
|
test('Clears all values from the store', () async {
|
||||||
await sut.clear();
|
await sut.clear();
|
||||||
verify(() => mockDriftStoreRepo.deleteAll()).called(1);
|
verify(() => mockDriftStoreRepo.deleteAll()).called(1);
|
||||||
expect(sut.tryGet(StoreKey.accessToken), isNull);
|
expect(sut.tryGet(StoreKey.legacyAccessToken), isNull);
|
||||||
expect(sut.tryGet(StoreKey.advancedTroubleshooting), isNull);
|
expect(sut.tryGet(StoreKey.legacyAdvancedTroubleshooting), isNull);
|
||||||
expect(sut.tryGet(StoreKey.version), isNull);
|
expect(sut.tryGet(StoreKey.legacyVersion), isNull);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import 'package:drift/drift.dart' as drift;
|
|||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
import 'package:immich_mobile/domain/services/sync_stream.service.dart';
|
||||||
@@ -36,7 +36,6 @@ class _AbortCallbackWrapper {
|
|||||||
|
|
||||||
class _MockAbortCallbackWrapper extends Mock implements _AbortCallbackWrapper {}
|
class _MockAbortCallbackWrapper extends Mock implements _AbortCallbackWrapper {}
|
||||||
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late SyncStreamService sut;
|
late SyncStreamService sut;
|
||||||
late SyncStreamRepository mockSyncStreamRepo;
|
late SyncStreamRepository mockSyncStreamRepo;
|
||||||
@@ -51,6 +50,7 @@ void main() {
|
|||||||
late Future<void> Function(List<SyncEvent>, Function(), Function()) handleEventsCallback;
|
late Future<void> Function(List<SyncEvent>, Function(), Function()) handleEventsCallback;
|
||||||
late _MockAbortCallbackWrapper mockAbortCallbackWrapper;
|
late _MockAbortCallbackWrapper mockAbortCallbackWrapper;
|
||||||
late _MockAbortCallbackWrapper mockResetCallbackWrapper;
|
late _MockAbortCallbackWrapper mockResetCallbackWrapper;
|
||||||
|
late MockAppMetadataRepository mockAppMetadataRepository;
|
||||||
late Drift db;
|
late Drift db;
|
||||||
late bool hasManageMediaPermission;
|
late bool hasManageMediaPermission;
|
||||||
|
|
||||||
@@ -59,6 +59,8 @@ void main() {
|
|||||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||||
registerFallbackValue(LocalAssetStub.image1);
|
registerFallbackValue(LocalAssetStub.image1);
|
||||||
registerFallbackValue(const SemVer(major: 2, minor: 5, patch: 0));
|
registerFallbackValue(const SemVer(major: 2, minor: 5, patch: 0));
|
||||||
|
registerFallbackValue(AppMetadataKey.syncMigrationStatus);
|
||||||
|
registerFallbackValue(const <String>[]);
|
||||||
|
|
||||||
db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||||
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
||||||
@@ -84,6 +86,7 @@ void main() {
|
|||||||
mockApi = MockApiService();
|
mockApi = MockApiService();
|
||||||
mockServerApi = MockServerApi();
|
mockServerApi = MockServerApi();
|
||||||
mockSyncMigrationRepo = MockSyncMigrationRepository();
|
mockSyncMigrationRepo = MockSyncMigrationRepository();
|
||||||
|
mockAppMetadataRepository = MockAppMetadataRepository();
|
||||||
|
|
||||||
when(() => mockAbortCallbackWrapper()).thenReturn(false);
|
when(() => mockAbortCallbackWrapper()).thenReturn(false);
|
||||||
|
|
||||||
@@ -159,6 +162,7 @@ void main() {
|
|||||||
permissionRepository: mockPermissionRepo,
|
permissionRepository: mockPermissionRepo,
|
||||||
api: mockApi,
|
api: mockApi,
|
||||||
syncMigrationRepository: mockSyncMigrationRepo,
|
syncMigrationRepository: mockSyncMigrationRepo,
|
||||||
|
appMetadataRepository: mockAppMetadataRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((_) async => {});
|
when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((_) async => {});
|
||||||
@@ -172,7 +176,11 @@ void main() {
|
|||||||
return ids;
|
return ids;
|
||||||
});
|
});
|
||||||
when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []);
|
when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []);
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||||
|
when(
|
||||||
|
() => mockAppMetadataRepository.get(AppMetadataKey.syncMigrationStatus),
|
||||||
|
).thenAnswer((_) async => const <String>[]);
|
||||||
|
when(() => mockAppMetadataRepository.set<List<String>, List<String>>(any(), any())).thenAnswer((_) async {});
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> simulateEvents(List<SyncEvent> events) async {
|
Future<void> simulateEvents(List<SyncEvent> events) async {
|
||||||
@@ -243,6 +251,7 @@ void main() {
|
|||||||
cancellation: cancellation,
|
cancellation: cancellation,
|
||||||
api: mockApi,
|
api: mockApi,
|
||||||
syncMigrationRepository: mockSyncMigrationRepo,
|
syncMigrationRepository: mockSyncMigrationRepo,
|
||||||
|
appMetadataRepository: mockAppMetadataRepository,
|
||||||
);
|
);
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
|
|
||||||
@@ -283,6 +292,7 @@ void main() {
|
|||||||
cancellation: cancellation,
|
cancellation: cancellation,
|
||||||
api: mockApi,
|
api: mockApi,
|
||||||
syncMigrationRepository: mockSyncMigrationRepo,
|
syncMigrationRepository: mockSyncMigrationRepo,
|
||||||
|
appMetadataRepository: mockAppMetadataRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
await sut.sync();
|
await sut.sync();
|
||||||
@@ -394,12 +404,12 @@ void main() {
|
|||||||
|
|
||||||
group("SyncStreamService - remote trash & restore", () {
|
group("SyncStreamService - remote trash & restore", () {
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||||
hasManageMediaPermission = true;
|
hasManageMediaPermission = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
when(() => mockAppMetadataRepository.get(AppMetadataKey.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||||
hasManageMediaPermission = false;
|
hasManageMediaPermission = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -552,7 +562,9 @@ void main() {
|
|||||||
|
|
||||||
group('SyncStreamService - Sync Migration', () {
|
group('SyncStreamService - Sync Migration', () {
|
||||||
test('ensure that <2.5.0 migrations run', () async {
|
test('ensure that <2.5.0 migrations run', () async {
|
||||||
await Store.put(StoreKey.syncMigrationStatus, "[]");
|
when(
|
||||||
|
() => mockAppMetadataRepository.get(AppMetadataKey.syncMigrationStatus),
|
||||||
|
).thenAnswer((_) async => const <String>[]);
|
||||||
when(
|
when(
|
||||||
() => mockServerApi.getServerVersion(),
|
() => mockServerApi.getServerVersion(),
|
||||||
).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1, prerelease: null));
|
).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1, prerelease: null));
|
||||||
@@ -580,7 +592,9 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('ensure that >=2.5.0 migrations run', () async {
|
test('ensure that >=2.5.0 migrations run', () async {
|
||||||
await Store.put(StoreKey.syncMigrationStatus, "[]");
|
when(
|
||||||
|
() => mockAppMetadataRepository.get(AppMetadataKey.syncMigrationStatus),
|
||||||
|
).thenAnswer((_) async => const <String>[]);
|
||||||
when(
|
when(
|
||||||
() => mockServerApi.getServerVersion(),
|
() => mockServerApi.getServerVersion(),
|
||||||
).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 5, patch_: 0, prerelease: null));
|
).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 5, patch_: 0, prerelease: null));
|
||||||
@@ -606,10 +620,9 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('ensure that migrations do not re-run', () async {
|
test('ensure that migrations do not re-run', () async {
|
||||||
await Store.put(
|
when(
|
||||||
StoreKey.syncMigrationStatus,
|
() => mockAppMetadataRepository.get(AppMetadataKey.syncMigrationStatus),
|
||||||
'["${SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name}"]',
|
).thenAnswer((_) async => [SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name]);
|
||||||
);
|
|
||||||
|
|
||||||
when(
|
when(
|
||||||
() => mockServerApi.getServerVersion(),
|
() => mockServerApi.getServerVersion(),
|
||||||
|
|||||||
@@ -1,78 +1,62 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
import '../../fixtures/user.stub.dart';
|
import '../../fixtures/user.stub.dart';
|
||||||
import '../../infrastructure/repository.mock.dart';
|
import '../../infrastructure/repository.mock.dart';
|
||||||
import '../service.mock.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late UserService sut;
|
late UserService sut;
|
||||||
late UserApiRepository mockUserApiRepo;
|
late UserApiRepository mockUserApiRepo;
|
||||||
late StoreService mockStoreService;
|
late DriftAuthUserRepository mockAuthUserRepo;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
mockUserApiRepo = MockUserApiRepository();
|
mockUserApiRepo = MockUserApiRepository();
|
||||||
mockStoreService = MockStoreService();
|
mockAuthUserRepo = MockDriftAuthUserRepository();
|
||||||
sut = UserService(userApiRepository: mockUserApiRepo, storeService: mockStoreService);
|
sut = UserService(userApiRepository: mockUserApiRepo, authUserRepository: mockAuthUserRepo);
|
||||||
|
|
||||||
registerFallbackValue(UserStub.admin);
|
registerFallbackValue(UserStub.admin);
|
||||||
when(() => mockStoreService.get(StoreKey.currentUser)).thenReturn(UserStub.admin);
|
when(() => mockAuthUserRepo.get()).thenAnswer((_) async => UserStub.admin);
|
||||||
when(() => mockStoreService.tryGet(StoreKey.currentUser)).thenReturn(UserStub.admin);
|
when(() => mockAuthUserRepo.upsert(any())).thenAnswer((_) async => UserStub.admin);
|
||||||
});
|
|
||||||
|
|
||||||
group('getMyUser', () {
|
|
||||||
test('should return user from store', () {
|
|
||||||
final result = sut.getMyUser();
|
|
||||||
expect(result, UserStub.admin);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle user not found scenario', () {
|
|
||||||
when(() => mockStoreService.get(StoreKey.currentUser)).thenThrow(Exception('User not found'));
|
|
||||||
|
|
||||||
expect(() => sut.getMyUser(), throwsA(isA<Exception>()));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('tryGetMyUser', () {
|
group('tryGetMyUser', () {
|
||||||
test('should return user from store', () {
|
test('should return the current user from the auth user repository', () async {
|
||||||
final result = sut.tryGetMyUser();
|
final result = await sut.tryGetMyUser();
|
||||||
expect(result, UserStub.admin);
|
expect(result, UserStub.admin);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return null if user not found', () {
|
test('should return null if no user is logged in', () async {
|
||||||
when(() => mockStoreService.tryGet(StoreKey.currentUser)).thenReturn(null);
|
when(() => mockAuthUserRepo.get()).thenAnswer((_) async => null);
|
||||||
final result = sut.tryGetMyUser();
|
final result = await sut.tryGetMyUser();
|
||||||
expect(result, isNull);
|
expect(result, isNull);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('watchMyUser', () {
|
group('watchMyUser', () {
|
||||||
test('should return user stream from store', () {
|
test('should return the current user stream from the auth user repository', () {
|
||||||
when(() => mockStoreService.watch(StoreKey.currentUser)).thenAnswer((_) => Stream.value(UserStub.admin));
|
when(() => mockAuthUserRepo.watch()).thenAnswer((_) => Stream.value(UserStub.admin));
|
||||||
final result = sut.watchMyUser();
|
final result = sut.watchMyUser();
|
||||||
expect(result, emits(UserStub.admin));
|
expect(result, emits(UserStub.admin));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return an empty stream if user not found', () {
|
test('should return an empty stream if no user is logged in', () {
|
||||||
when(() => mockStoreService.watch(StoreKey.currentUser)).thenAnswer((_) => const Stream.empty());
|
when(() => mockAuthUserRepo.watch()).thenAnswer((_) => const Stream.empty());
|
||||||
final result = sut.watchMyUser();
|
final result = sut.watchMyUser();
|
||||||
expect(result, emitsInOrder([]));
|
expect(result, emitsInOrder([]));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('refreshMyUser', () {
|
group('refreshMyUser', () {
|
||||||
test('should return user from api and store it', () async {
|
test('should return user from api and persist it', () async {
|
||||||
when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => UserStub.admin);
|
when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => UserStub.admin);
|
||||||
when(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)).thenAnswer((_) async => true);
|
|
||||||
|
|
||||||
final result = await sut.refreshMyUser();
|
final result = await sut.refreshMyUser();
|
||||||
verify(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)).called(1);
|
verify(() => mockAuthUserRepo.upsert(UserStub.admin)).called(1);
|
||||||
expect(result, UserStub.admin);
|
expect(result, UserStub.admin);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,7 +64,7 @@ void main() {
|
|||||||
when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => null);
|
when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => null);
|
||||||
|
|
||||||
final result = await sut.refreshMyUser();
|
final result = await sut.refreshMyUser();
|
||||||
verifyNever(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin));
|
verifyNever(() => mockAuthUserRepo.upsert(any()));
|
||||||
expect(result, isNull);
|
expect(result, isNull);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -88,29 +72,26 @@ void main() {
|
|||||||
group('createProfileImage', () {
|
group('createProfileImage', () {
|
||||||
test('should return profile image path', () async {
|
test('should return profile image path', () async {
|
||||||
const profileImagePath = 'profile.jpg';
|
const profileImagePath = 'profile.jpg';
|
||||||
final updatedUser = UserStub.admin;
|
|
||||||
|
|
||||||
when(
|
when(
|
||||||
() => mockUserApiRepo.createProfileImage(name: profileImagePath, data: Uint8List(0)),
|
() => mockUserApiRepo.createProfileImage(name: profileImagePath, data: Uint8List(0)),
|
||||||
).thenAnswer((_) async => profileImagePath);
|
).thenAnswer((_) async => profileImagePath);
|
||||||
when(() => mockStoreService.put(StoreKey.currentUser, updatedUser)).thenAnswer((_) async => true);
|
|
||||||
|
|
||||||
final result = await sut.createProfileImage(profileImagePath, Uint8List(0));
|
final result = await sut.createProfileImage(profileImagePath, Uint8List(0));
|
||||||
|
|
||||||
verify(() => mockStoreService.put(StoreKey.currentUser, updatedUser)).called(1);
|
verify(() => mockAuthUserRepo.upsert(UserStub.admin)).called(1);
|
||||||
expect(result, profileImagePath);
|
expect(result, profileImagePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return null if profile image creation fails', () async {
|
test('should return null if profile image creation fails', () async {
|
||||||
const profileImagePath = 'profile.jpg';
|
const profileImagePath = 'profile.jpg';
|
||||||
final updatedUser = UserStub.admin;
|
|
||||||
|
|
||||||
when(
|
when(
|
||||||
() => mockUserApiRepo.createProfileImage(name: profileImagePath, data: Uint8List(0)),
|
() => mockUserApiRepo.createProfileImage(name: profileImagePath, data: Uint8List(0)),
|
||||||
).thenThrow(Exception('Failed to create profile image'));
|
).thenThrow(Exception('Failed to create profile image'));
|
||||||
|
|
||||||
final result = await sut.createProfileImage(profileImagePath, Uint8List(0));
|
final result = await sut.createProfileImage(profileImagePath, Uint8List(0));
|
||||||
verifyNever(() => mockStoreService.put(StoreKey.currentUser, updatedUser));
|
verifyNever(() => mockAuthUserRepo.upsert(any()));
|
||||||
expect(result, isNull);
|
expect(result, isNull);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+12
@@ -33,6 +33,9 @@ import 'schema_v26.dart' as v26;
|
|||||||
import 'schema_v27.dart' as v27;
|
import 'schema_v27.dart' as v27;
|
||||||
import 'schema_v28.dart' as v28;
|
import 'schema_v28.dart' as v28;
|
||||||
import 'schema_v29.dart' as v29;
|
import 'schema_v29.dart' as v29;
|
||||||
|
import 'schema_v30.dart' as v30;
|
||||||
|
import 'schema_v31.dart' as v31;
|
||||||
|
import 'schema_v32.dart' as v32;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@@ -96,6 +99,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v28.DatabaseAtV28(db);
|
return v28.DatabaseAtV28(db);
|
||||||
case 29:
|
case 29:
|
||||||
return v29.DatabaseAtV29(db);
|
return v29.DatabaseAtV29(db);
|
||||||
|
case 30:
|
||||||
|
return v30.DatabaseAtV30(db);
|
||||||
|
case 31:
|
||||||
|
return v31.DatabaseAtV31(db);
|
||||||
|
case 32:
|
||||||
|
return v32.DatabaseAtV32(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
@@ -131,5 +140,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
27,
|
27,
|
||||||
28,
|
28,
|
||||||
29,
|
29,
|
||||||
|
30,
|
||||||
|
31,
|
||||||
|
32,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
+10027
File diff suppressed because it is too large
Load Diff
+10241
File diff suppressed because it is too large
Load Diff
+10459
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,11 @@
|
|||||||
// dart format width=80
|
|
||||||
// ignore_for_file: unused_local_variable, unused_import
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_dev/api/migrations_native.dart';
|
import 'package:drift_dev/api/migrations_native.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
|
||||||
import 'generated/schema.dart';
|
import 'generated/schema.dart';
|
||||||
import 'generated/schema_v1.dart' as v1;
|
import 'generated/schema_v31.dart' as v31;
|
||||||
import 'generated/schema_v2.dart' as v2;
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||||
@@ -35,4 +33,36 @@ void main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('data migrations', () {
|
||||||
|
test('v31->v32 backfills the migration', () async {
|
||||||
|
final schema = await verifier.schemaAt(31);
|
||||||
|
|
||||||
|
final oldDb = v31.DatabaseAtV31(schema.newConnection());
|
||||||
|
await oldDb.into(oldDb.storeEntity).insert(v31.StoreEntityCompanion.insert(id: 0, intValue: const Value(28)));
|
||||||
|
await oldDb.close();
|
||||||
|
|
||||||
|
final db = Drift(schema.newConnection());
|
||||||
|
await verifier.migrateAndValidate(db, 32);
|
||||||
|
|
||||||
|
final cursor = await (db.appMetadataEntity.select()..where((tbl) => tbl.key.equals(AppMetadataKey.version.name)))
|
||||||
|
.map((row) => row.value)
|
||||||
|
.getSingleOrNull();
|
||||||
|
expect(cursor, '28');
|
||||||
|
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('v31->v32 writes no row when the legacy store has none', () async {
|
||||||
|
final schema = await verifier.schemaAt(31);
|
||||||
|
|
||||||
|
final db = Drift(schema.newConnection());
|
||||||
|
await verifier.migrateAndValidate(db, 32);
|
||||||
|
|
||||||
|
final rows = await db.appMetadataEntity.select().get();
|
||||||
|
expect(rows, isEmpty);
|
||||||
|
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,20 @@ import 'package:drift/drift.dart' hide isNull;
|
|||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
|
|
||||||
import '../../fixtures/user.stub.dart';
|
|
||||||
|
|
||||||
const _kTestAccessToken = "#TestToken";
|
const _kTestAccessToken = "#TestToken";
|
||||||
const _kTestVersion = 10;
|
const _kTestVersion = 10;
|
||||||
const _kTestAdvancedTroubleshooting = false;
|
const _kTestAdvancedTroubleshooting = false;
|
||||||
final _kTestUser = UserStub.admin;
|
|
||||||
|
|
||||||
Future<void> _populateStore(Drift db) async {
|
Future<void> _populateStore(Drift db) async {
|
||||||
await db.batch((batch) async {
|
await db.batch((batch) async {
|
||||||
batch.insert(
|
batch.insert(
|
||||||
db.storeEntity,
|
db.storeEntity,
|
||||||
StoreEntityCompanion(
|
StoreEntityCompanion(
|
||||||
id: Value(StoreKey.advancedTroubleshooting.id),
|
id: Value(StoreKey.legacyAdvancedTroubleshooting.id),
|
||||||
intValue: const Value(_kTestAdvancedTroubleshooting ? 1 : 0),
|
intValue: const Value(_kTestAdvancedTroubleshooting ? 1 : 0),
|
||||||
stringValue: const Value(null),
|
stringValue: const Value(null),
|
||||||
),
|
),
|
||||||
@@ -29,7 +25,7 @@ Future<void> _populateStore(Drift db) async {
|
|||||||
batch.insert(
|
batch.insert(
|
||||||
db.storeEntity,
|
db.storeEntity,
|
||||||
StoreEntityCompanion(
|
StoreEntityCompanion(
|
||||||
id: Value(StoreKey.accessToken.id),
|
id: Value(StoreKey.legacyAccessToken.id),
|
||||||
intValue: const Value(null),
|
intValue: const Value(null),
|
||||||
stringValue: const Value(_kTestAccessToken),
|
stringValue: const Value(_kTestAccessToken),
|
||||||
),
|
),
|
||||||
@@ -37,7 +33,7 @@ Future<void> _populateStore(Drift db) async {
|
|||||||
batch.insert(
|
batch.insert(
|
||||||
db.storeEntity,
|
db.storeEntity,
|
||||||
StoreEntityCompanion(
|
StoreEntityCompanion(
|
||||||
id: Value(StoreKey.version.id),
|
id: Value(StoreKey.legacyVersion.id),
|
||||||
intValue: const Value(_kTestVersion),
|
intValue: const Value(_kTestVersion),
|
||||||
stringValue: const Value(null),
|
stringValue: const Value(null),
|
||||||
),
|
),
|
||||||
@@ -60,36 +56,28 @@ void main() {
|
|||||||
|
|
||||||
group('Store Repository converters:', () {
|
group('Store Repository converters:', () {
|
||||||
test('converts int', () async {
|
test('converts int', () async {
|
||||||
int? version = await sut.tryGet(StoreKey.version);
|
int? version = await sut.tryGet(StoreKey.legacyVersion);
|
||||||
expect(version, isNull);
|
expect(version, isNull);
|
||||||
await sut.upsert(StoreKey.version, _kTestVersion);
|
await sut.upsert(StoreKey.legacyVersion, _kTestVersion);
|
||||||
version = await sut.tryGet(StoreKey.version);
|
version = await sut.tryGet(StoreKey.legacyVersion);
|
||||||
expect(version, _kTestVersion);
|
expect(version, _kTestVersion);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('converts string', () async {
|
test('converts string', () async {
|
||||||
String? accessToken = await sut.tryGet(StoreKey.accessToken);
|
String? accessToken = await sut.tryGet(StoreKey.legacyAccessToken);
|
||||||
expect(accessToken, isNull);
|
expect(accessToken, isNull);
|
||||||
await sut.upsert(StoreKey.accessToken, _kTestAccessToken);
|
await sut.upsert(StoreKey.legacyAccessToken, _kTestAccessToken);
|
||||||
accessToken = await sut.tryGet(StoreKey.accessToken);
|
accessToken = await sut.tryGet(StoreKey.legacyAccessToken);
|
||||||
expect(accessToken, _kTestAccessToken);
|
expect(accessToken, _kTestAccessToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('converts bool', () async {
|
test('converts bool', () async {
|
||||||
bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
|
bool? advancedTroubleshooting = await sut.tryGet(StoreKey.legacyAdvancedTroubleshooting);
|
||||||
expect(advancedTroubleshooting, isNull);
|
expect(advancedTroubleshooting, isNull);
|
||||||
await sut.upsert(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting);
|
await sut.upsert(StoreKey.legacyAdvancedTroubleshooting, _kTestAdvancedTroubleshooting);
|
||||||
advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
|
advancedTroubleshooting = await sut.tryGet(StoreKey.legacyAdvancedTroubleshooting);
|
||||||
expect(advancedTroubleshooting, _kTestAdvancedTroubleshooting);
|
expect(advancedTroubleshooting, _kTestAdvancedTroubleshooting);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('converts user', () async {
|
|
||||||
UserDto? user = await sut.tryGet(StoreKey.currentUser);
|
|
||||||
expect(user, isNull);
|
|
||||||
await sut.upsert(StoreKey.currentUser, _kTestUser);
|
|
||||||
user = await sut.tryGet(StoreKey.currentUser);
|
|
||||||
expect(user, _kTestUser);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Store Repository Deletes:', () {
|
group('Store Repository Deletes:', () {
|
||||||
@@ -98,10 +86,10 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('delete()', () async {
|
test('delete()', () async {
|
||||||
bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
|
bool? advancedTroubleshooting = await sut.tryGet(StoreKey.legacyAdvancedTroubleshooting);
|
||||||
expect(advancedTroubleshooting, isFalse);
|
expect(advancedTroubleshooting, isFalse);
|
||||||
await sut.delete(StoreKey.advancedTroubleshooting);
|
await sut.delete(StoreKey.legacyAdvancedTroubleshooting);
|
||||||
advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
|
advancedTroubleshooting = await sut.tryGet(StoreKey.legacyAdvancedTroubleshooting);
|
||||||
expect(advancedTroubleshooting, isNull);
|
expect(advancedTroubleshooting, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,10 +107,10 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('upsert()', () async {
|
test('upsert()', () async {
|
||||||
int? version = await sut.tryGet(StoreKey.version);
|
int? version = await sut.tryGet(StoreKey.legacyVersion);
|
||||||
expect(version, _kTestVersion);
|
expect(version, _kTestVersion);
|
||||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
await sut.upsert(StoreKey.legacyVersion, _kTestVersion + 10);
|
||||||
version = await sut.tryGet(StoreKey.version);
|
version = await sut.tryGet(StoreKey.legacyVersion);
|
||||||
expect(version, _kTestVersion + 10);
|
expect(version, _kTestVersion + 10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -133,10 +121,10 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('watch()', () async {
|
test('watch()', () async {
|
||||||
final stream = sut.watch(StoreKey.version);
|
final stream = sut.watch(StoreKey.legacyVersion);
|
||||||
unawaited(expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10])));
|
unawaited(expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10])));
|
||||||
await pumpEventQueue();
|
await pumpEventQueue();
|
||||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
await sut.upsert(StoreKey.legacyVersion, _kTestVersion + 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('watchAll()', () async {
|
test('watchAll()', () async {
|
||||||
@@ -146,19 +134,19 @@ void main() {
|
|||||||
stream,
|
stream,
|
||||||
emitsInOrder([
|
emitsInOrder([
|
||||||
[
|
[
|
||||||
const StoreDto<Object>(StoreKey.version, _kTestVersion),
|
const StoreDto<Object>(StoreKey.legacyVersion, _kTestVersion),
|
||||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
const StoreDto<Object>(StoreKey.legacyAccessToken, _kTestAccessToken),
|
||||||
const StoreDto<Object>(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
const StoreDto<Object>(StoreKey.legacyAdvancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
|
const StoreDto<Object>(StoreKey.legacyVersion, _kTestVersion + 10),
|
||||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
const StoreDto<Object>(StoreKey.legacyAccessToken, _kTestAccessToken),
|
||||||
const StoreDto<Object>(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
const StoreDto<Object>(StoreKey.legacyAdvancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
await sut.upsert(StoreKey.legacyVersion, _kTestVersion + 10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
@@ -22,6 +23,8 @@ class MockDriftStoreRepository extends Mock implements DriftStoreRepository {}
|
|||||||
|
|
||||||
class MockSettingsRepository extends Mock implements SettingsRepository {}
|
class MockSettingsRepository extends Mock implements SettingsRepository {}
|
||||||
|
|
||||||
|
class MockAppMetadataRepository extends Mock implements AppMetadataRepository {}
|
||||||
|
|
||||||
class MockLogRepository extends Mock implements LogRepository {}
|
class MockLogRepository extends Mock implements LogRepository {}
|
||||||
|
|
||||||
class MockSyncStreamRepository extends Mock implements SyncStreamRepository {}
|
class MockSyncStreamRepository extends Mock implements SyncStreamRepository {}
|
||||||
@@ -48,6 +51,8 @@ class MockSyncMigrationRepository extends Mock implements SyncMigrationRepositor
|
|||||||
|
|
||||||
class MockUserRepository extends Mock implements UserRepository {}
|
class MockUserRepository extends Mock implements UserRepository {}
|
||||||
|
|
||||||
|
class MockDriftAuthUserRepository extends Mock implements DriftAuthUserRepository {}
|
||||||
|
|
||||||
class MockPartnerRepository extends Mock implements PartnerRepository {}
|
class MockPartnerRepository extends Mock implements PartnerRepository {}
|
||||||
|
|
||||||
// API Repos
|
// API Repos
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/app_metadata_key.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/app_metadata.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/app_metadata.repository.dart';
|
||||||
|
|
||||||
|
import '../repository_context.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late MediumRepositoryContext ctx;
|
||||||
|
late AppMetadataRepository sut;
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
ctx = MediumRepositoryContext();
|
||||||
|
sut = AppMetadataRepository(ctx.db);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await ctx.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
await ctx.db.delete(ctx.db.appMetadataEntity).go();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('get', () {
|
||||||
|
test('a stored NULL value column resolves to the key default', () async {
|
||||||
|
await ctx.db
|
||||||
|
.into(ctx.db.appMetadataEntity)
|
||||||
|
.insert(
|
||||||
|
AppMetadataEntityCompanion.insert(
|
||||||
|
key: AppMetadataKey.manageLocalMediaAndroid.name,
|
||||||
|
value: const .new(null),
|
||||||
|
updatedAt: .new(DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await sut.get(.manageLocalMediaAndroid), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('defaults', () {
|
||||||
|
test('falls back to the key default when the value is absent', () async {
|
||||||
|
expect(await sut.get(.version), kCurrentVersion);
|
||||||
|
expect(await sut.get(.syncMigrationStatus), const <String>[]);
|
||||||
|
expect(await sut.get(.manageLocalMediaAndroid), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a stored value takes precedence over the default', () async {
|
||||||
|
await sut.set(.version, 5);
|
||||||
|
await sut.set(.syncMigrationStatus, const ['task']);
|
||||||
|
await sut.set(.manageLocalMediaAndroid, true);
|
||||||
|
|
||||||
|
expect(await sut.get(.version), 5);
|
||||||
|
expect(await sut.get(.syncMigrationStatus), const ['task']);
|
||||||
|
expect(await sut.get(.manageLocalMediaAndroid), true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('set', () {
|
||||||
|
test('round-trips int, List and bool values to their typed form', () async {
|
||||||
|
await sut.set(.version, 42);
|
||||||
|
await sut.set(.syncMigrationStatus, const ['task']);
|
||||||
|
await sut.set(.manageLocalMediaAndroid, true);
|
||||||
|
|
||||||
|
expect(await sut.get(.version), 42);
|
||||||
|
expect(await sut.get(.syncMigrationStatus), const ['task']);
|
||||||
|
expect(await sut.get(.manageLocalMediaAndroid), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('overwrites the existing value and keeps a single row per key', () async {
|
||||||
|
await sut.set(.version, 1);
|
||||||
|
await sut.set(.version, 2);
|
||||||
|
|
||||||
|
expect(await sut.get(.version), 2);
|
||||||
|
expect(await ctx.db.select(ctx.db.appMetadataEntity).get(), hasLength(1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('cache-less reads', () {
|
||||||
|
test('observes a value mutated directly in the DB', () async {
|
||||||
|
await sut.set(.version, 10);
|
||||||
|
|
||||||
|
await (ctx.db.update(ctx.db.appMetadataEntity)..where((r) => r.key.equals(AppMetadataKey.version.name))).write(
|
||||||
|
AppMetadataEntityCompanion(value: .new(AppMetadataKey.version.encode(99))),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await sut.get(.version), 99);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -456,7 +456,7 @@ void main() {
|
|||||||
|
|
||||||
test('does not update when longitude does not match', () async {
|
test('does not update when longitude does not match', () async {
|
||||||
final remoteAsset = await ctx.newRemoteAsset(ownerId: userId);
|
final remoteAsset = await ctx.newRemoteAsset(ownerId: userId);
|
||||||
final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id, longitude: (-74.006).toOption());
|
final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id, longitude: .fromNullable((-74.006)));
|
||||||
final localAsset = await ctx.newLocalAsset(
|
final localAsset = await ctx.newLocalAsset(
|
||||||
checksumOption: const Option.none(),
|
checksumOption: const Option.none(),
|
||||||
iCloudId: cloudIdAsset.cloudId,
|
iCloudId: cloudIdAsset.cloudId,
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/session.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
|
|
||||||
|
import '../repository_context.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late MediumRepositoryContext ctx;
|
||||||
|
late SessionRepository sut;
|
||||||
|
|
||||||
|
setUpAll(() async {
|
||||||
|
ctx = MediumRepositoryContext();
|
||||||
|
sut = await SessionRepository.ensureInitialized(ctx.db);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await ctx.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
await ctx.db.delete(ctx.db.sessionEntity).go();
|
||||||
|
await SessionRepository.instance.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('defaults', () {
|
||||||
|
test('session returns null fields when DB is empty', () {
|
||||||
|
expect(sut.session.serverUrl, isNull);
|
||||||
|
expect(sut.session.accessToken, isNull);
|
||||||
|
expect(sut.session.serverEndpoint, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('write', () {
|
||||||
|
test('persists a value and reflects it in the composed view', () async {
|
||||||
|
await sut.write(.serverEndpoint, 'https://demo.immich.app/api');
|
||||||
|
expect(sut.session.serverEndpoint, 'https://demo.immich.app/api');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('persists across keys independently', () async {
|
||||||
|
await sut.write(.serverUrl, 'https://demo.immich.app');
|
||||||
|
await sut.write(.accessToken, 'token-123');
|
||||||
|
expect(sut.session.serverUrl, 'https://demo.immich.app');
|
||||||
|
expect(sut.session.accessToken, 'token-123');
|
||||||
|
expect(sut.session.serverEndpoint, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('null values', () {
|
||||||
|
test('a stored NULL value column decodes to null on refresh', () async {
|
||||||
|
await ctx.db
|
||||||
|
.into(ctx.db.sessionEntity)
|
||||||
|
.insert(
|
||||||
|
SessionEntityCompanion.insert(
|
||||||
|
key: SessionKey.accessToken.name,
|
||||||
|
value: const .new(null),
|
||||||
|
updatedAt: .new(DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await SessionRepository.instance.refresh();
|
||||||
|
expect(sut.session.accessToken, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('sync', () {
|
||||||
|
test('picks up rows that were inserted directly into the DB', () async {
|
||||||
|
await ctx.db
|
||||||
|
.into(ctx.db.sessionEntity)
|
||||||
|
.insert(
|
||||||
|
SessionEntityCompanion.insert(
|
||||||
|
key: SessionKey.serverEndpoint.name,
|
||||||
|
value: const .new('https://demo.immich.app/api'),
|
||||||
|
updatedAt: .new(DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(sut.session.serverEndpoint, isNull);
|
||||||
|
|
||||||
|
await SessionRepository.instance.refresh();
|
||||||
|
expect(sut.session.serverEndpoint, 'https://demo.immich.app/api');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('drops cached values for rows that were deleted out from under the repo', () async {
|
||||||
|
await sut.write(.serverEndpoint, 'https://demo.immich.app/api');
|
||||||
|
await ctx.db.delete(ctx.db.sessionEntity).go();
|
||||||
|
expect(sut.session.serverEndpoint, 'https://demo.immich.app/api');
|
||||||
|
|
||||||
|
await SessionRepository.instance.refresh();
|
||||||
|
expect(sut.session.serverEndpoint, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skips rows whose key is unknown to SessionKey', () async {
|
||||||
|
await ctx.db
|
||||||
|
.into(ctx.db.sessionEntity)
|
||||||
|
.insert(
|
||||||
|
SessionEntityCompanion.insert(
|
||||||
|
key: 'session.unknown.future-key',
|
||||||
|
value: const .new('unknown'),
|
||||||
|
updatedAt: .new(DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await SessionRepository.instance.refresh();
|
||||||
|
expect(sut.session.serverEndpoint, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('watch', () {
|
||||||
|
test('watchSession emits the new value after a write', () async {
|
||||||
|
final expectation = expectLater(
|
||||||
|
sut.watch().map((s) => s.serverEndpoint),
|
||||||
|
emitsThrough('https://demo.immich.app/api/watch'),
|
||||||
|
);
|
||||||
|
await sut.write(SessionKey.serverEndpoint, 'https://demo.immich.app/api/watch');
|
||||||
|
await expectation;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
@@ -61,6 +60,39 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('null values', () {
|
||||||
|
test('writing null to a nullable key clears the row and reverts the cache to null', () async {
|
||||||
|
await sut.write(SettingsKey.networkPreferredWifiName, 'home-wifi');
|
||||||
|
expect(sut.appConfig.network.preferredWifiName, 'home-wifi');
|
||||||
|
expect(await ctx.db.select(ctx.db.settingsEntity).get(), hasLength(1));
|
||||||
|
|
||||||
|
await sut.write(SettingsKey.networkPreferredWifiName, null);
|
||||||
|
|
||||||
|
expect(await ctx.db.select(ctx.db.settingsEntity).get(), isEmpty);
|
||||||
|
expect(sut.appConfig.network.preferredWifiName, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('writing null to an already-null key is a no-op', () async {
|
||||||
|
await sut.write(SettingsKey.networkPreferredWifiName, null);
|
||||||
|
expect(await ctx.db.select(ctx.db.settingsEntity).get(), isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a stored NULL value column decodes to null on refresh', () async {
|
||||||
|
await ctx.db
|
||||||
|
.into(ctx.db.settingsEntity)
|
||||||
|
.insert(
|
||||||
|
SettingsEntityCompanion.insert(
|
||||||
|
key: SettingsKey.networkPreferredWifiName.name,
|
||||||
|
value: const .new(null),
|
||||||
|
updatedAt: .new(DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await SettingsRepository.instance.refresh();
|
||||||
|
expect(sut.appConfig.network.preferredWifiName, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
group('delete', () {});
|
group('delete', () {});
|
||||||
|
|
||||||
group('sync', () {
|
group('sync', () {
|
||||||
@@ -70,8 +102,8 @@ void main() {
|
|||||||
.insert(
|
.insert(
|
||||||
SettingsEntityCompanion.insert(
|
SettingsEntityCompanion.insert(
|
||||||
key: SettingsKey.themeMode.name,
|
key: SettingsKey.themeMode.name,
|
||||||
value: ThemeMode.dark.name,
|
value: .new(ThemeMode.dark.name),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: .new(DateTime.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -98,8 +130,8 @@ void main() {
|
|||||||
.insert(
|
.insert(
|
||||||
SettingsEntityCompanion.insert(
|
SettingsEntityCompanion.insert(
|
||||||
key: 'app-config.unknown.future-key',
|
key: 'app-config.unknown.future-key',
|
||||||
value: 'whatever',
|
value: const .new('unknown'),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: .new(DateTime.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -110,13 +142,13 @@ void main() {
|
|||||||
|
|
||||||
group('watch', () {
|
group('watch', () {
|
||||||
test('watchAppConfig emits the new value after a write', () async {
|
test('watchAppConfig emits the new value after a write', () async {
|
||||||
final expectation = expectLater(sut.watchConfig().map((c) => c.theme.mode), emitsThrough(ThemeMode.dark));
|
final expectation = expectLater(sut.watch().map((c) => c.theme.mode), emitsThrough(ThemeMode.dark));
|
||||||
await sut.write(SettingsKey.themeMode, ThemeMode.dark);
|
await sut.write(SettingsKey.themeMode, ThemeMode.dark);
|
||||||
await expectation;
|
await expectation;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('watchConfig emits the new value after a write', () async {
|
test('watchConfig emits the new value after a write', () async {
|
||||||
final expectation = expectLater(sut.watchConfig().map((c) => c.logLevel), emitsThrough(LogLevel.warning));
|
final expectation = expectLater(sut.watch().map((c) => c.logLevel), emitsThrough(LogLevel.warning));
|
||||||
await sut.write(SettingsKey.logLevel, LogLevel.warning);
|
await sut.write(SettingsKey.logLevel, LogLevel.warning);
|
||||||
await expectation;
|
await expectation;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ void main() {
|
|||||||
when(() => actionService.editDateTime(any(), any())).thenAnswer((_) async => true);
|
when(() => actionService.editDateTime(any(), any())).thenAnswer((_) async => true);
|
||||||
when(() => assetService.watchAsset(any())).thenAnswer((_) => const Stream.empty());
|
when(() => assetService.watchAsset(any())).thenAnswer((_) => const Stream.empty());
|
||||||
when(() => assetService.getExif(any())).thenAnswer((_) async => null);
|
when(() => assetService.getExif(any())).thenAnswer((_) async => null);
|
||||||
when(() => userService.tryGetMyUser()).thenReturn(_user);
|
when(() => userService.tryGetMyUser()).thenAnswer((_) async => _user);
|
||||||
when(() => userService.watchMyUser()).thenAnswer((_) => const Stream.empty());
|
when(() => userService.watchMyUser()).thenAnswer((_) => const Stream.empty());
|
||||||
|
|
||||||
container = ProviderContainer(
|
container = ProviderContainer(
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/services/network.service.dart';
|
import 'package:immich_mobile/services/network.service.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class MockApiService extends Mock implements ApiService {}
|
class MockApiService extends Mock implements ApiService {}
|
||||||
|
|
||||||
class MockNetworkService extends Mock implements NetworkService {}
|
class MockNetworkService extends Mock implements NetworkService {}
|
||||||
|
|
||||||
class MockAppSettingService extends Mock implements AppSettingsService {}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:drift/drift.dart' as drift;
|
|||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
@@ -28,6 +27,7 @@ void main() {
|
|||||||
late MockAssetMediaRepository assetMediaRepository;
|
late MockAssetMediaRepository assetMediaRepository;
|
||||||
late MockDownloadRepository downloadRepository;
|
late MockDownloadRepository downloadRepository;
|
||||||
late MockTagService tagService;
|
late MockTagService tagService;
|
||||||
|
late MockAppMetadataRepository appMetadataRepository;
|
||||||
|
|
||||||
late Drift db;
|
late Drift db;
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ void main() {
|
|||||||
assetMediaRepository = MockAssetMediaRepository();
|
assetMediaRepository = MockAssetMediaRepository();
|
||||||
downloadRepository = MockDownloadRepository();
|
downloadRepository = MockDownloadRepository();
|
||||||
tagService = MockTagService();
|
tagService = MockTagService();
|
||||||
|
appMetadataRepository = MockAppMetadataRepository();
|
||||||
|
|
||||||
sut = ActionService(
|
sut = ActionService(
|
||||||
assetApiRepository,
|
assetApiRepository,
|
||||||
@@ -66,7 +67,10 @@ void main() {
|
|||||||
assetMediaRepository,
|
assetMediaRepository,
|
||||||
downloadRepository,
|
downloadRepository,
|
||||||
tagService,
|
tagService,
|
||||||
|
appMetadataRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
when(() => appMetadataRepository.get(.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
@@ -144,7 +148,7 @@ void main() {
|
|||||||
|
|
||||||
group('ActionService.deleteLocal', () {
|
group('ActionService.deleteLocal', () {
|
||||||
test('routes deleted ids to trashed repository when Android trash handling is enabled', () async {
|
test('routes deleted ids to trashed repository when Android trash handling is enabled', () async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
when(() => appMetadataRepository.get(.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||||
const ids = ['a', 'b'];
|
const ids = ['a', 'b'];
|
||||||
|
|
||||||
when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => ids);
|
when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => ids);
|
||||||
@@ -159,7 +163,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('deletes locally when Android trash handling is disabled', () async {
|
test('deletes locally when Android trash handling is disabled', () async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, false);
|
when(() => appMetadataRepository.get(.manageLocalMediaAndroid)).thenAnswer((_) async => false);
|
||||||
const ids = ['c'];
|
const ids = ['c'];
|
||||||
|
|
||||||
when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => ids);
|
when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => ids);
|
||||||
@@ -174,7 +178,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('short-circuits when nothing was deleted', () async {
|
test('short-circuits when nothing was deleted', () async {
|
||||||
await Store.put(StoreKey.manageLocalMediaAndroid, true);
|
when(() => appMetadataRepository.get(.manageLocalMediaAndroid)).thenAnswer((_) async => true);
|
||||||
const ids = ['x'];
|
const ids = ['x'];
|
||||||
|
|
||||||
when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => <String>[]);
|
when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => <String>[]);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/services/auth.service.dart';
|
import 'package:immich_mobile/services/auth.service.dart';
|
||||||
@@ -44,6 +45,7 @@ void main() {
|
|||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||||
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
||||||
|
await SessionRepository.ensureInitialized(db);
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDownAll(() async {
|
tearDownAll(() async {
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/services/background_upload.service.dart';
|
import 'package:immich_mobile/services/background_upload.service.dart';
|
||||||
@@ -39,8 +41,8 @@ void main() {
|
|||||||
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||||
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
await StoreService.init(storeRepository: DriftStoreRepository(db));
|
||||||
await SettingsRepository.ensureInitialized(db);
|
await SettingsRepository.ensureInitialized(db);
|
||||||
|
await SessionRepository.ensureInitialized(db);
|
||||||
await Store.put(StoreKey.serverEndpoint, 'http://test-server.com');
|
await SessionRepository.instance.write(SessionKey.serverEndpoint, 'https://demo.immich.app');
|
||||||
await Store.put(StoreKey.deviceId, 'test-device-id');
|
await Store.put(StoreKey.deviceId, 'test-device-id');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/locales.dart';
|
import 'package:immich_mobile/constants/locales.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/session.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/session.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
|
|
||||||
import '../test_utils.dart';
|
import '../test_utils.dart';
|
||||||
@@ -25,7 +26,8 @@ class PresentationContext {
|
|||||||
if (_db == null) {
|
if (_db == null) {
|
||||||
final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true));
|
||||||
await StoreService.init(storeRepository: DriftStoreRepository(db), listenUpdates: false);
|
await StoreService.init(storeRepository: DriftStoreRepository(db), listenUpdates: false);
|
||||||
await StoreService.I.put(StoreKey.serverEndpoint, serverEndpoint);
|
await SessionRepository.ensureInitialized(db);
|
||||||
|
await SessionRepository.instance.write(SessionKey.serverEndpoint, serverEndpoint);
|
||||||
_db = db;
|
_db = db;
|
||||||
}
|
}
|
||||||
return const PresentationContext._();
|
return const PresentationContext._();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user