mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 21:42:18 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b07be3c00 |
+1
-1
@@ -32,7 +32,7 @@
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@socket.io/component-emitter": "^3.1.2",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^24.12.4",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/pg": "^8.15.1",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/supertest": "^7.0.0",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
ARG DEVICE=cpu
|
||||
|
||||
FROM python:3.11-bookworm@sha256:970c99f886b839fc8829289040c1845dadaf2cae46b37acc7710333158ec29b4 AS builder-cpu
|
||||
FROM python:3.11-bookworm@sha256:99f4240b31e5b9cf1e792420390b0d531022af3922b3d15681e7b818ca63fe37 AS builder-cpu
|
||||
|
||||
FROM python:3.13-slim-trixie@sha256:d168b8d9eb761f4d3fe305ebd04aeb7e7f2de0297cec5fb2f8f6403244621664 AS builder-openvino
|
||||
FROM python:3.13-slim-trixie@sha256:dc1546eefcbe8caaa1f004f16ab76b204b5e1dbd58ff81b899f21cd40541232f AS builder-openvino
|
||||
|
||||
FROM builder-cpu AS builder-cuda
|
||||
|
||||
@@ -39,12 +39,12 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||
uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy
|
||||
|
||||
FROM python:3.11-slim-bookworm@sha256:9c6f90801e6b68e772b7c0ca74260cbf7af9f320acec894e26fccdaccfbe3b47 AS prod-cpu
|
||||
FROM python:3.11-slim-bookworm@sha256:cd67330292a51e2963156f74ff340455d66b2172e9190e99f40dff9357471177 AS prod-cpu
|
||||
|
||||
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
||||
MACHINE_LEARNING_MODEL_ARENA=false
|
||||
|
||||
FROM python:3.13-slim-trixie@sha256:d168b8d9eb761f4d3fe305ebd04aeb7e7f2de0297cec5fb2f8f6403244621664 AS prod-openvino
|
||||
FROM python:3.13-slim-trixie@sha256:dc1546eefcbe8caaa1f004f16ab76b204b5e1dbd58ff81b899f21cd40541232f AS prod-openvino
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
||||
|
||||
Generated
+418
-371
File diff suppressed because it is too large
Load Diff
@@ -1,26 +0,0 @@
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
|
||||
class AlbumConfig {
|
||||
final AlbumSortMode sortMode;
|
||||
final bool isReverse;
|
||||
final bool isGrid;
|
||||
|
||||
const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false});
|
||||
|
||||
AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) => AlbumConfig(
|
||||
sortMode: sortMode ?? this.sortMode,
|
||||
isReverse: isReverse ?? this.isReverse,
|
||||
isGrid: isGrid ?? this.isGrid,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is AlbumConfig && other.sortMode == sortMode && other.isReverse == isReverse && other.isGrid == isGrid);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(sortMode, isReverse, isGrid);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumConfig(sortMode: $sortMode, isReverse: $isReverse, isGrid: $isGrid)';
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
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/cleanup_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/image_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/map_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/slideshow_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/timeline_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/viewer_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/slideshow_config.dart';
|
||||
|
||||
class AppConfig {
|
||||
final ThemeConfig theme;
|
||||
@@ -16,8 +14,6 @@ class AppConfig {
|
||||
final ImageConfig image;
|
||||
final ViewerConfig viewer;
|
||||
final SlideshowConfig slideshow;
|
||||
final AlbumConfig album;
|
||||
final BackupConfig backup;
|
||||
|
||||
const AppConfig({
|
||||
this.theme = const .new(),
|
||||
@@ -27,8 +23,6 @@ class AppConfig {
|
||||
this.image = const .new(),
|
||||
this.viewer = const .new(),
|
||||
this.slideshow = const .new(),
|
||||
this.album = const .new(),
|
||||
this.backup = const .new(),
|
||||
});
|
||||
|
||||
AppConfig copyWith({
|
||||
@@ -39,8 +33,6 @@ class AppConfig {
|
||||
ImageConfig? image,
|
||||
ViewerConfig? viewer,
|
||||
SlideshowConfig? slideshow,
|
||||
AlbumConfig? album,
|
||||
BackupConfig? backup,
|
||||
}) => .new(
|
||||
theme: theme ?? this.theme,
|
||||
cleanup: cleanup ?? this.cleanup,
|
||||
@@ -49,8 +41,6 @@ class AppConfig {
|
||||
image: image ?? this.image,
|
||||
viewer: viewer ?? this.viewer,
|
||||
slideshow: slideshow ?? this.slideshow,
|
||||
album: album ?? this.album,
|
||||
backup: backup ?? this.backup,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -63,14 +53,12 @@ class AppConfig {
|
||||
other.timeline == timeline &&
|
||||
other.image == image &&
|
||||
other.viewer == viewer &&
|
||||
other.slideshow == slideshow &&
|
||||
other.album == album &&
|
||||
other.backup == backup);
|
||||
other.slideshow == slideshow);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(theme, cleanup, map, timeline, image, viewer, slideshow, album, backup);
|
||||
int get hashCode => Object.hash(theme, cleanup, map, timeline, image, viewer, slideshow);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'AppConfig(theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow, album: $album, backup: $backup)';
|
||||
'AppConfig(theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, slideshow: $slideshow)';
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
class BackupConfig {
|
||||
final bool enabled;
|
||||
final bool useCellularForVideos;
|
||||
final bool useCellularForPhotos;
|
||||
final bool requireCharging;
|
||||
final int triggerDelay;
|
||||
final bool syncAlbums;
|
||||
|
||||
const BackupConfig({
|
||||
this.enabled = false,
|
||||
this.useCellularForVideos = false,
|
||||
this.useCellularForPhotos = false,
|
||||
this.requireCharging = false,
|
||||
this.triggerDelay = 30,
|
||||
this.syncAlbums = false,
|
||||
});
|
||||
|
||||
BackupConfig copyWith({
|
||||
bool? enabled,
|
||||
bool? useCellularForVideos,
|
||||
bool? useCellularForPhotos,
|
||||
bool? requireCharging,
|
||||
int? triggerDelay,
|
||||
bool? syncAlbums,
|
||||
}) => BackupConfig(
|
||||
enabled: enabled ?? this.enabled,
|
||||
useCellularForVideos: useCellularForVideos ?? this.useCellularForVideos,
|
||||
useCellularForPhotos: useCellularForPhotos ?? this.useCellularForPhotos,
|
||||
requireCharging: requireCharging ?? this.requireCharging,
|
||||
triggerDelay: triggerDelay ?? this.triggerDelay,
|
||||
syncAlbums: syncAlbums ?? this.syncAlbums,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is BackupConfig &&
|
||||
other.enabled == enabled &&
|
||||
other.useCellularForVideos == useCellularForVideos &&
|
||||
other.useCellularForPhotos == useCellularForPhotos &&
|
||||
other.requireCharging == requireCharging &&
|
||||
other.triggerDelay == triggerDelay &&
|
||||
other.syncAlbums == syncAlbums);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(enabled, useCellularForVideos, useCellularForPhotos, requireCharging, triggerDelay, syncAlbums);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'BackupConfig(enabled: $enabled, useCellularForVideos: $useCellularForVideos, useCellularForPhotos: $useCellularForPhotos, requireCharging: $requireCharging, triggerDelay: $triggerDelay, syncAlbums: $syncAlbums)';
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import 'package:immich_mobile/domain/models/config/app_config.dart';
|
||||
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
|
||||
enum MetadataDomain<T extends Object> {
|
||||
appConfig<AppConfig>('config.app'),
|
||||
@@ -52,24 +51,6 @@ enum MetadataKey<T extends Object> {
|
||||
_MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string),
|
||||
),
|
||||
|
||||
// Album
|
||||
albumSortMode<AlbumSortMode>(
|
||||
.appConfig,
|
||||
'album.sortMode',
|
||||
AlbumSortMode.mostRecent,
|
||||
_EnumCodec(AlbumSortMode.values),
|
||||
),
|
||||
albumIsReverse<bool>(.appConfig, 'album.isReverse', true),
|
||||
albumIsGrid<bool>(.appConfig, 'album.isGrid', false),
|
||||
|
||||
// Backup
|
||||
backupEnabled<bool>(.appConfig, 'backup.enabled', false),
|
||||
backupUseCellularForVideos<bool>(.appConfig, 'backup.useCellularForVideos', false),
|
||||
backupUseCellularForPhotos<bool>(.appConfig, 'backup.useCellularForPhotos', false),
|
||||
backupRequireCharging<bool>(.appConfig, 'backup.requireCharging', false),
|
||||
backupTriggerDelay<int>(.appConfig, 'backup.triggerDelay', 30),
|
||||
backupSyncAlbums<bool>(.appConfig, 'backup.syncAlbums', false),
|
||||
|
||||
// Timeline
|
||||
timelineTilesPerRow<int>(.appConfig, 'timeline.tilesPerRow', 4),
|
||||
timelineGroupAssetsBy<GroupAssetsBy>(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
|
||||
enum Setting<T> {
|
||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false);
|
||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false),
|
||||
enableBackup<bool>(StoreKey.enableBackup, false);
|
||||
|
||||
const Setting(this.storeKey, this.defaultValue);
|
||||
|
||||
|
||||
@@ -6,28 +6,29 @@ enum StoreKey<T> {
|
||||
version<int>._(0),
|
||||
currentUser<UserDto>._(2),
|
||||
deviceId<String>._(4),
|
||||
backupRequireCharging<bool>._(7),
|
||||
backupTriggerDelay<int>._(8),
|
||||
serverUrl<String>._(10),
|
||||
accessToken<String>._(11),
|
||||
serverEndpoint<String>._(12),
|
||||
selectedAlbumSortOrder<int>._(113),
|
||||
advancedTroubleshooting<bool>._(114),
|
||||
selectedAlbumSortReverse<bool>._(123),
|
||||
enableHapticFeedback<bool>._(126),
|
||||
syncAlbums<bool>._(131),
|
||||
|
||||
manageLocalMediaAndroid<bool>._(137),
|
||||
// Read-only Mode settings
|
||||
readonlyModeEnabled<bool>._(138),
|
||||
albumGridView<bool>._(140),
|
||||
|
||||
// Experimental stuff
|
||||
enableBackup<bool>._(1003),
|
||||
useWifiForUploadVideos<bool>._(1004),
|
||||
useWifiForUploadPhotos<bool>._(1005),
|
||||
syncMigrationStatus<String>._(1013),
|
||||
|
||||
// Legacy keys that have been migrated to the new metadata store
|
||||
legacyBackupRequireCharging<bool>._(7),
|
||||
legacyBackupTriggerDelay<int>._(8),
|
||||
legacySyncAlbums<bool>._(131),
|
||||
legacyEnableBackup<bool>._(1003),
|
||||
legacyUseWifiForUploadVideos<bool>._(1004),
|
||||
legacyUseWifiForUploadPhotos<bool>._(1005),
|
||||
legacySelectedAlbumSortOrder<int>._(113),
|
||||
legacySelectedAlbumSortReverse<bool>._(123),
|
||||
legacyAlbumGridView<bool>._(140),
|
||||
legacyAutoEndpointSwitching<bool>._(132),
|
||||
legacyPreferredWifiName<String>._(133),
|
||||
legacyLocalEndpoint<String>._(134),
|
||||
|
||||
@@ -11,14 +11,15 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||
import 'package:immich_mobile/platform/background_worker_lock_api.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/backup/drift_backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart' show nativeSyncApiProvider;
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/services/auth.service.dart';
|
||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||
import 'package:immich_mobile/services/localization.service.dart';
|
||||
@@ -38,15 +39,16 @@ class BackgroundWorkerFgService {
|
||||
Future<void> saveNotificationMessage(String title, String body) =>
|
||||
_foregroundHostApi.saveNotificationMessage(title, body);
|
||||
|
||||
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) {
|
||||
final backup = MetadataRepository.instance.appConfig.backup;
|
||||
return _foregroundHostApi.configure(
|
||||
BackgroundWorkerSettings(
|
||||
minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay,
|
||||
requiresCharging: requireCharging ?? backup.requireCharging,
|
||||
),
|
||||
);
|
||||
}
|
||||
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure(
|
||||
BackgroundWorkerSettings(
|
||||
minimumDelaySeconds:
|
||||
minimumDelaySeconds ??
|
||||
Store.get(AppSettingsEnum.backupTriggerDelay.storeKey, AppSettingsEnum.backupTriggerDelay.defaultValue),
|
||||
requiresCharging:
|
||||
requireCharging ??
|
||||
Store.get(AppSettingsEnum.backupRequireCharging.storeKey, AppSettingsEnum.backupRequireCharging.defaultValue),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> disable() => _foregroundHostApi.disable();
|
||||
}
|
||||
@@ -69,7 +71,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||
BackgroundWorkerFlutterApi.setUp(this);
|
||||
}
|
||||
|
||||
bool get _isBackupEnabled => MetadataRepository.instance.appConfig.backup.enabled;
|
||||
bool get _isBackupEnabled => _ref?.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup) ?? false;
|
||||
|
||||
Future<void> init() async {
|
||||
try {
|
||||
|
||||
@@ -147,19 +147,6 @@ extension<T extends Object> on MetadataDomain<T> {
|
||||
look: repo._read(.slideshowLook),
|
||||
direction: repo._read(.slideshowDirection),
|
||||
),
|
||||
album: .new(
|
||||
sortMode: repo._read(.albumSortMode),
|
||||
isReverse: repo._read(.albumIsReverse),
|
||||
isGrid: repo._read(.albumIsGrid),
|
||||
),
|
||||
backup: .new(
|
||||
enabled: repo._read(.backupEnabled),
|
||||
useCellularForVideos: repo._read(.backupUseCellularForVideos),
|
||||
useCellularForPhotos: repo._read(.backupUseCellularForPhotos),
|
||||
requireCharging: repo._read(.backupRequireCharging),
|
||||
triggerDelay: repo._read(.backupTriggerDelay),
|
||||
syncAlbums: repo._read(.backupSyncAlbums),
|
||||
),
|
||||
);
|
||||
case .systemConfig:
|
||||
repo._systemConfig = .new(
|
||||
|
||||
@@ -8,13 +8,13 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
|
||||
import 'package:immich_mobile/widgets/common/search_field.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -43,7 +43,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
_searchController = TextEditingController();
|
||||
_searchFocusNode = FocusNode();
|
||||
|
||||
_enableSyncUploadAlbum.value = ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
||||
_enableSyncUploadAlbum.value = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||
ref.read(backupAlbumProvider.notifier).getAll();
|
||||
|
||||
_initialTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
||||
@@ -55,7 +55,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
return;
|
||||
}
|
||||
|
||||
final enableSyncUploadAlbum = ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
||||
final enableSyncUploadAlbum = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||
final selectedAlbums = ref
|
||||
.read(backupAlbumProvider)
|
||||
.where((a) => a.backupSelection == BackupSelection.selected)
|
||||
@@ -103,7 +103,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
return;
|
||||
}
|
||||
|
||||
final isBackupEnabled = MetadataRepository.instance.appConfig.backup.enabled;
|
||||
final isBackupEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(user.id);
|
||||
final currentTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
||||
final totalChanged = currentTotalAssetCount != _initialTotalAssetCount;
|
||||
|
||||
@@ -3,12 +3,14 @@ import 'dart:async';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.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/infrastructure/metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
@@ -19,20 +21,18 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
bool hasPopped = false;
|
||||
final previousBackup = ref.read(metadataProvider).appConfig.backup;
|
||||
final previousCellularForVideos = previousBackup.useCellularForVideos;
|
||||
final previousCellularForPhotos = previousBackup.useCellularForPhotos;
|
||||
final previousWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
||||
final previousWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
// There is an issue with Flutter where the pop event
|
||||
// can be triggered multiple times, so we guard it with _hasPopped
|
||||
|
||||
final currentBackup = ref.read(metadataProvider).appConfig.backup;
|
||||
final currentCellularForVideos = currentBackup.useCellularForVideos;
|
||||
final currentCellularForPhotos = currentBackup.useCellularForPhotos;
|
||||
final currentWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
||||
final currentWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
||||
|
||||
if (currentCellularForVideos == previousCellularForVideos &&
|
||||
currentCellularForPhotos == previousCellularForPhotos) {
|
||||
if (currentWifiReqForVideos == previousWifiReqForVideos &&
|
||||
currentWifiReqForPhotos == previousWifiReqForPhotos) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||
final isBackupEnabled = MetadataRepository.instance.appConfig.backup.enabled;
|
||||
final isBackupEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||
if (!isBackupEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ 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/translations.g.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||
@@ -341,7 +340,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
await backgroundManager.hashAssets();
|
||||
}
|
||||
|
||||
if (MetadataRepository.instance.appConfig.backup.syncAlbums) {
|
||||
if (Store.get(StoreKey.syncAlbums, false)) {
|
||||
await backgroundManager.syncLinkedAlbum();
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -370,7 +369,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
}
|
||||
|
||||
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
|
||||
final isEnableBackup = MetadataRepository.instance.appConfig.backup.enabled;
|
||||
final isEnableBackup = Store.get(StoreKey.enableBackup, false);
|
||||
|
||||
if (isEnableBackup) {
|
||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||
|
||||
@@ -15,15 +15,15 @@ import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/utils/album_filter.utils.dart';
|
||||
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
@@ -58,11 +58,19 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final albumConfig = ref.read(metadataProvider).appConfig.album;
|
||||
final appSettings = ref.read(appSettingsServiceProvider);
|
||||
final savedSortMode = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortOrder);
|
||||
final savedIsReverse = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortReverse);
|
||||
final savedIsGrid = appSettings.getSetting(AppSettingsEnum.albumGridView);
|
||||
|
||||
final albumSortMode = AlbumSortMode.values.firstWhere(
|
||||
(e) => e.storeIndex == savedSortMode,
|
||||
orElse: () => AlbumSortMode.lastModified,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
sort = AlbumSort(mode: albumConfig.sortMode, isReverse: albumConfig.isReverse);
|
||||
isGrid = albumConfig.isGrid;
|
||||
sort = AlbumSort(mode: albumSortMode, isReverse: savedIsReverse);
|
||||
isGrid = savedIsGrid;
|
||||
});
|
||||
|
||||
ref.read(remoteAlbumProvider.notifier).refresh();
|
||||
@@ -94,7 +102,7 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
||||
setState(() {
|
||||
isGrid = !isGrid;
|
||||
});
|
||||
ref.read(metadataProvider).write(MetadataKey.albumIsGrid, isGrid);
|
||||
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.albumGridView, isGrid);
|
||||
}
|
||||
|
||||
void changeFilter(QuickFilterMode mode) {
|
||||
@@ -110,9 +118,9 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
||||
this.sort = sort;
|
||||
});
|
||||
|
||||
final metadata = ref.read(metadataProvider);
|
||||
await metadata.write(MetadataKey.albumSortMode, sort.mode);
|
||||
await metadata.write(MetadataKey.albumIsReverse, sort.isReverse);
|
||||
final appSettings = ref.read(appSettingsServiceProvider);
|
||||
await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortOrder, sort.mode.storeIndex);
|
||||
await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortReverse, sort.isReverse);
|
||||
|
||||
await sortAlbums();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
|
||||
class BackupToggleButton extends ConsumerStatefulWidget {
|
||||
final VoidCallback onStart;
|
||||
@@ -31,7 +31,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
||||
end: 1,
|
||||
).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
|
||||
|
||||
_isEnabled = ref.read(metadataProvider).appConfig.backup.enabled;
|
||||
_isEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -41,7 +41,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
||||
}
|
||||
|
||||
Future<void> _onToggle(bool value) async {
|
||||
await ref.read(metadataProvider).write(MetadataKey.backupEnabled, value);
|
||||
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.enableBackup, value);
|
||||
|
||||
setState(() {
|
||||
_isEnabled = value;
|
||||
|
||||
@@ -5,15 +5,16 @@ import 'package:immich_mobile/domain/models/store.model.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/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/auth.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/gallery_permission.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
enum AppLifeCycleEnum { active, inactive, paused, resumed, detached, hidden }
|
||||
@@ -107,7 +108,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
final backgroundManager = _ref.read(backgroundSyncProvider);
|
||||
final isAlbumLinkedSyncEnable = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
||||
final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||
|
||||
try {
|
||||
bool syncSuccess = false;
|
||||
@@ -137,7 +138,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||
}
|
||||
|
||||
Future<void> _resumeBackup() async {
|
||||
final isEnableBackup = _ref.read(metadataProvider).appConfig.backup.enabled;
|
||||
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||
|
||||
if (isEnableBackup) {
|
||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:immich_mobile/infrastructure/repositories/network.repository.dar
|
||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/utils/debounce.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
@@ -193,7 +192,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
||||
return;
|
||||
}
|
||||
|
||||
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
||||
final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false);
|
||||
try {
|
||||
unawaited(
|
||||
_ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) {
|
||||
@@ -214,7 +213,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
||||
return;
|
||||
}
|
||||
|
||||
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
||||
final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false);
|
||||
try {
|
||||
unawaited(
|
||||
_ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) {
|
||||
|
||||
@@ -2,10 +2,19 @@ import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
|
||||
enum AppSettingsEnum<T> {
|
||||
selectedAlbumSortOrder<int>(StoreKey.selectedAlbumSortOrder, "selectedAlbumSortOrder", 2),
|
||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
|
||||
manageLocalMediaAndroid<bool>(StoreKey.manageLocalMediaAndroid, null, false),
|
||||
selectedAlbumSortReverse<bool>(StoreKey.selectedAlbumSortReverse, null, true),
|
||||
enableHapticFeedback<bool>(StoreKey.enableHapticFeedback, null, true),
|
||||
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false);
|
||||
syncAlbums<bool>(StoreKey.syncAlbums, null, false),
|
||||
enableBackup<bool>(StoreKey.enableBackup, null, false),
|
||||
useCellularForUploadVideos<bool>(StoreKey.useWifiForUploadVideos, null, false),
|
||||
useCellularForUploadPhotos<bool>(StoreKey.useWifiForUploadPhotos, null, false),
|
||||
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false),
|
||||
albumGridView<bool>(StoreKey.albumGridView, "albumGridView", false),
|
||||
backupRequireCharging<bool>(StoreKey.backupRequireCharging, null, false),
|
||||
backupTriggerDelay<int>(StoreKey.backupTriggerDelay, null, 30);
|
||||
|
||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.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/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/repositories/auth.repository.dart';
|
||||
import 'package:immich_mobile/repositories/auth_api.repository.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:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
@@ -25,6 +25,7 @@ final authServiceProvider = Provider(
|
||||
ref.watch(apiServiceProvider),
|
||||
ref.watch(networkServiceProvider),
|
||||
ref.watch(backgroundSyncProvider),
|
||||
ref.watch(appSettingsServiceProvider),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -34,6 +35,7 @@ class AuthService {
|
||||
final ApiService _apiService;
|
||||
final NetworkService _networkService;
|
||||
final BackgroundSyncManager _backgroundSyncManager;
|
||||
final AppSettingsService _appSettingsService;
|
||||
final _log = Logger("AuthService");
|
||||
|
||||
AuthService(
|
||||
@@ -42,6 +44,7 @@ class AuthService {
|
||||
this._apiService,
|
||||
this._networkService,
|
||||
this._backgroundSyncManager,
|
||||
this._appSettingsService,
|
||||
);
|
||||
|
||||
/// Validates the provided server URL by resolving and setting the endpoint.
|
||||
@@ -100,7 +103,7 @@ class AuthService {
|
||||
_log.severe("Error clearing local data", error, stackTrace);
|
||||
});
|
||||
|
||||
await MetadataRepository.instance.write(MetadataKey.backupEnabled, false);
|
||||
await _appSettingsService.setSetting(AppSettingsEnum.enableBackup, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,14 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.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/metadata.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/utils/debug_print.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
@@ -30,6 +31,7 @@ final backgroundUploadServiceProvider = Provider((ref) {
|
||||
ref.watch(storageRepositoryProvider),
|
||||
ref.watch(localAssetRepository),
|
||||
ref.watch(backupRepositoryProvider),
|
||||
ref.watch(appSettingsServiceProvider),
|
||||
ref.watch(assetMediaRepositoryProvider),
|
||||
);
|
||||
|
||||
@@ -103,6 +105,7 @@ class BackgroundUploadService {
|
||||
this._storageRepository,
|
||||
this._localAssetRepository,
|
||||
this._backupRepository,
|
||||
this._appSettingsService,
|
||||
this._assetMediaRepository,
|
||||
) {
|
||||
_uploadRepository.onUploadStatus = _onUploadCallback;
|
||||
@@ -113,6 +116,7 @@ class BackgroundUploadService {
|
||||
final StorageRepository _storageRepository;
|
||||
final DriftLocalAssetRepository _localAssetRepository;
|
||||
final DriftBackupRepository _backupRepository;
|
||||
final AppSettingsService _appSettingsService;
|
||||
final AssetMediaRepository _assetMediaRepository;
|
||||
final Logger _logger = Logger('BackgroundUploadService');
|
||||
|
||||
@@ -359,14 +363,15 @@ class BackgroundUploadService {
|
||||
}
|
||||
|
||||
bool _shouldRequireWiFi(LocalAsset asset) {
|
||||
final backup = MetadataRepository.instance.appConfig.backup;
|
||||
if (asset.isVideo && backup.useCellularForVideos) {
|
||||
return false;
|
||||
bool requiresWiFi = true;
|
||||
|
||||
if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) {
|
||||
requiresWiFi = false;
|
||||
} else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) {
|
||||
requiresWiFi = false;
|
||||
}
|
||||
if (!asset.isVideo && backup.useCellularForPhotos) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
return requiresWiFi;
|
||||
}
|
||||
|
||||
Future<UploadTask> buildUploadTask(
|
||||
|
||||
@@ -7,17 +7,18 @@ import 'package:immich_mobile/domain/models/asset/asset_metadata.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/network_capability_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/network_capability_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
|
||||
@@ -38,6 +39,7 @@ final foregroundUploadServiceProvider = Provider((ref) {
|
||||
ref.watch(storageRepositoryProvider),
|
||||
ref.watch(backupRepositoryProvider),
|
||||
ref.watch(connectivityApiProvider),
|
||||
ref.watch(appSettingsServiceProvider),
|
||||
ref.watch(assetMediaRepositoryProvider),
|
||||
);
|
||||
});
|
||||
@@ -53,6 +55,7 @@ class ForegroundUploadService {
|
||||
this._storageRepository,
|
||||
this._backupRepository,
|
||||
this._connectivityApi,
|
||||
this._appSettingsService,
|
||||
this._assetMediaRepository,
|
||||
);
|
||||
|
||||
@@ -60,6 +63,7 @@ class ForegroundUploadService {
|
||||
final StorageRepository _storageRepository;
|
||||
final DriftBackupRepository _backupRepository;
|
||||
final ConnectivityApi _connectivityApi;
|
||||
final AppSettingsService _appSettingsService;
|
||||
final AssetMediaRepository _assetMediaRepository;
|
||||
final Logger _logger = Logger('ForegroundUploadService');
|
||||
|
||||
@@ -451,13 +455,14 @@ class ForegroundUploadService {
|
||||
}
|
||||
|
||||
bool _shouldRequireWiFi(LocalAsset asset) {
|
||||
final backup = MetadataRepository.instance.appConfig.backup;
|
||||
if (asset.isVideo && backup.useCellularForVideos) {
|
||||
return false;
|
||||
bool requiresWiFi = true;
|
||||
|
||||
if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) {
|
||||
requiresWiFi = false;
|
||||
} else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) {
|
||||
requiresWiFi = false;
|
||||
}
|
||||
if (!asset.isVideo && backup.useCellularForPhotos) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
return requiresWiFi;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
|
||||
const int targetVersion = 26;
|
||||
|
||||
@@ -82,7 +81,14 @@ Future<void> _migrateTo26(Drift drift) async {
|
||||
final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(StoreKey.legacyCleanupKeepAlbumIds.id);
|
||||
if (cleanupKeepAlbumIds != null) {
|
||||
final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList();
|
||||
migrator.stage(StoreKey.legacyCleanupKeepAlbumIds, MetadataKey.cleanupKeepAlbumIds, ids);
|
||||
await drift.metadataEntity.insertOnConflictUpdate(
|
||||
MetadataEntityCompanion.insert(
|
||||
key: MetadataKey.cleanupKeepAlbumIds.key,
|
||||
value: MetadataKey.cleanupKeepAlbumIds.encode(ids),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
await migrator.deleteLegacyStoreRows([StoreKey.legacyCleanupKeepAlbumIds.id]);
|
||||
}
|
||||
await migrator.migrateBool(StoreKey.legacyCleanupKeepFavorites, MetadataKey.cleanupKeepFavorites);
|
||||
await migrator.migrateEnumIndex(
|
||||
@@ -118,37 +124,12 @@ Future<void> _migrateTo26(Drift drift) async {
|
||||
await migrator.migrateBool(StoreKey.legacyAutoEndpointSwitching, MetadataKey.networkAutoEndpointSwitching);
|
||||
await migrator.migrateString(StoreKey.legacyPreferredWifiName, MetadataKey.networkPreferredWifiName);
|
||||
await migrator.migrateString(StoreKey.legacyLocalEndpoint, MetadataKey.networkLocalEndpoint);
|
||||
await _migrateExternalEndpointList(migrator);
|
||||
await _migrateCustomHeaders(migrator);
|
||||
// Album
|
||||
await _migrateAlbumSortMode(migrator);
|
||||
await migrator.migrateBool(StoreKey.legacySelectedAlbumSortReverse, MetadataKey.albumIsReverse);
|
||||
await migrator.migrateBool(StoreKey.legacyAlbumGridView, MetadataKey.albumIsGrid);
|
||||
// Backup
|
||||
await migrator.migrateBool(StoreKey.legacyEnableBackup, MetadataKey.backupEnabled);
|
||||
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadVideos, MetadataKey.backupUseCellularForVideos);
|
||||
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadPhotos, MetadataKey.backupUseCellularForPhotos);
|
||||
await migrator.migrateBool(StoreKey.legacyBackupRequireCharging, MetadataKey.backupRequireCharging);
|
||||
await migrator.migrateInt(StoreKey.legacyBackupTriggerDelay, MetadataKey.backupTriggerDelay);
|
||||
await migrator.migrateBool(StoreKey.legacySyncAlbums, MetadataKey.backupSyncAlbums);
|
||||
await _migrateExternalEndpointList(drift, migrator);
|
||||
await _migrateCustomHeaders(drift, migrator);
|
||||
await migrator.complete();
|
||||
}
|
||||
|
||||
Future<void> _migrateAlbumSortMode(_StoreMigrator migrator) async {
|
||||
final raw = await migrator.readLegacyStoreInt(StoreKey.legacySelectedAlbumSortOrder.id);
|
||||
if (raw == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final mode = AlbumSortMode.values.firstWhere(
|
||||
(e) => e.storeIndex == raw,
|
||||
orElse: () => MetadataKey.albumSortMode.defaultValue,
|
||||
);
|
||||
|
||||
migrator.stage(StoreKey.legacySelectedAlbumSortOrder, MetadataKey.albumSortMode, mode);
|
||||
}
|
||||
|
||||
Future<void> _migrateExternalEndpointList(_StoreMigrator migrator) async {
|
||||
Future<void> _migrateExternalEndpointList(Drift drift, _StoreMigrator migrator) async {
|
||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyExternalEndpointList.id);
|
||||
if (raw == null) {
|
||||
return;
|
||||
@@ -169,10 +150,17 @@ Future<void> _migrateExternalEndpointList(_StoreMigrator migrator) async {
|
||||
// ignore invalid entries
|
||||
}
|
||||
|
||||
migrator.stage(StoreKey.legacyExternalEndpointList, MetadataKey.networkExternalEndpointList, urls);
|
||||
await drift.metadataEntity.insertOnConflictUpdate(
|
||||
MetadataEntityCompanion.insert(
|
||||
key: MetadataKey.networkExternalEndpointList.key,
|
||||
value: MetadataKey.networkExternalEndpointList.encode(urls),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
await migrator.deleteLegacyStoreRows([StoreKey.legacyExternalEndpointList.id]);
|
||||
}
|
||||
|
||||
Future<void> _migrateCustomHeaders(_StoreMigrator migrator) async {
|
||||
Future<void> _migrateCustomHeaders(Drift drift, _StoreMigrator migrator) async {
|
||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyCustomHeaders.id);
|
||||
if (raw == null) {
|
||||
return;
|
||||
@@ -192,7 +180,14 @@ Future<void> _migrateCustomHeaders(_StoreMigrator migrator) async {
|
||||
// ignore invalid entries
|
||||
}
|
||||
|
||||
migrator.stage(StoreKey.legacyCustomHeaders, MetadataKey.networkCustomHeaders, headers);
|
||||
await drift.metadataEntity.insertOnConflictUpdate(
|
||||
MetadataEntityCompanion.insert(
|
||||
key: MetadataKey.networkCustomHeaders.key,
|
||||
value: MetadataKey.networkCustomHeaders.encode(headers),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
await migrator.deleteLegacyStoreRows([StoreKey.legacyCustomHeaders.id]);
|
||||
}
|
||||
|
||||
class _StoreMigrator {
|
||||
@@ -259,11 +254,6 @@ class _StoreMigrator {
|
||||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
||||
void stage<T extends Object>(StoreKey legacyKey, MetadataKey<T> newKey, T value) {
|
||||
_cache[newKey] = value;
|
||||
_migratedStoreIds.add(legacyKey.id);
|
||||
}
|
||||
|
||||
Future<void> complete() async {
|
||||
await _db.batch((batch) {
|
||||
for (final entry in _cache.entries) {
|
||||
|
||||
@@ -6,12 +6,13 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/setting.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/timeline/multiselect.provider.dart';
|
||||
@@ -192,51 +193,64 @@ class _BackupIndicator extends ConsumerWidget {
|
||||
}
|
||||
|
||||
Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) {
|
||||
final backupEnabled = ref.watch(appConfigProvider.select((c) => c.backup.enabled));
|
||||
final backupStateStream = ref.watch(settingsProvider).watch(Setting.enableBackup);
|
||||
final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none));
|
||||
final isDarkTheme = context.isDarkTheme;
|
||||
final iconColor = isDarkTheme ? Colors.white : Colors.black;
|
||||
final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty));
|
||||
|
||||
if (!backupEnabled) {
|
||||
return _BadgeLabel(
|
||||
Icon(Icons.cloud_off_rounded, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()),
|
||||
);
|
||||
}
|
||||
return StreamBuilder(
|
||||
stream: backupStateStream,
|
||||
initialData: false,
|
||||
builder: (ctx, snapshot) {
|
||||
final backupEnabled = snapshot.data ?? false;
|
||||
|
||||
if (hasError) {
|
||||
return _BadgeLabel(
|
||||
Icon(
|
||||
Icons.warning_rounded,
|
||||
size: 12,
|
||||
color: context.colorScheme.error,
|
||||
semanticLabel: 'backup_controller_page_backup'.tr(),
|
||||
),
|
||||
backgroundColor: context.colorScheme.errorContainer,
|
||||
);
|
||||
}
|
||||
|
||||
if (isUploading) {
|
||||
return _BadgeLabel(
|
||||
Container(
|
||||
padding: const EdgeInsets.all(3.5),
|
||||
child: Theme(
|
||||
data: context.themeData.copyWith(
|
||||
progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true),
|
||||
if (!backupEnabled) {
|
||||
return _BadgeLabel(
|
||||
Icon(
|
||||
Icons.cloud_off_rounded,
|
||||
size: 9,
|
||||
color: iconColor,
|
||||
semanticLabel: 'backup_controller_page_backup'.tr(),
|
||||
),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
strokeCap: StrokeCap.round,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(iconColor),
|
||||
semanticsLabel: 'backup_controller_page_backup'.tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return _BadgeLabel(
|
||||
Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()),
|
||||
if (hasError) {
|
||||
return _BadgeLabel(
|
||||
Icon(
|
||||
Icons.warning_rounded,
|
||||
size: 12,
|
||||
color: context.colorScheme.error,
|
||||
semanticLabel: 'backup_controller_page_backup'.tr(),
|
||||
),
|
||||
backgroundColor: context.colorScheme.errorContainer,
|
||||
);
|
||||
}
|
||||
|
||||
if (isUploading) {
|
||||
return _BadgeLabel(
|
||||
Container(
|
||||
padding: const EdgeInsets.all(3.5),
|
||||
child: Theme(
|
||||
data: context.themeData.copyWith(
|
||||
progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true),
|
||||
),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
strokeCap: StrokeCap.round,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(iconColor),
|
||||
semanticsLabel: 'backup_controller_page_backup'.tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return _BadgeLabel(
|
||||
Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ 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/translate_extensions.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||
@@ -187,7 +186,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
await backgroundManager.syncRemote();
|
||||
await backgroundManager.hashAssets();
|
||||
|
||||
if (MetadataRepository.instance.appConfig.backup.syncAlbums) {
|
||||
if (Store.get(StoreKey.syncAlbums, false)) {
|
||||
await backgroundManager.syncLinkedAlbum();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,18 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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/config/app_config.dart';
|
||||
import 'package:immich_mobile/domain/models/metadata_key.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/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.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/settings_sub_page_scaffold.dart';
|
||||
@@ -30,8 +31,8 @@ class DriftBackupSettings extends ConsumerWidget {
|
||||
title: "network_requirements".t(context: context),
|
||||
icon: Icons.cell_tower,
|
||||
),
|
||||
const _UseCellularForVideosButton(),
|
||||
const _UseCellularForPhotosButton(),
|
||||
const _UseWifiForUploadVideosButton(),
|
||||
const _UseWifiForUploadPhotosButton(),
|
||||
if (CurrentPlatform.isAndroid) ...[
|
||||
const Divider(),
|
||||
SettingGroupTitle(
|
||||
@@ -98,58 +99,64 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final albumSyncEnable = ref.watch(appConfigProvider.select((c) => c.backup.syncAlbums));
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SettingListTile(
|
||||
title: "sync_albums".t(context: context),
|
||||
subtitle: "sync_upload_album_setting_subtitle".t(context: context),
|
||||
trailing: Switch(
|
||||
value: albumSyncEnable,
|
||||
onChanged: (bool newValue) async {
|
||||
await ref.read(metadataProvider).write(MetadataKey.backupSyncAlbums, newValue);
|
||||
StreamBuilder(
|
||||
stream: Store.watch(StoreKey.syncAlbums),
|
||||
initialData: Store.tryGet(StoreKey.syncAlbums) ?? false,
|
||||
builder: (context, snapshot) {
|
||||
final albumSyncEnable = snapshot.data ?? false;
|
||||
return Column(
|
||||
children: [
|
||||
SettingListTile(
|
||||
title: "sync_albums".t(context: context),
|
||||
subtitle: "sync_upload_album_setting_subtitle".t(context: context),
|
||||
trailing: Switch(
|
||||
value: albumSyncEnable,
|
||||
onChanged: (bool newValue) async {
|
||||
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.syncAlbums, newValue);
|
||||
|
||||
if (newValue == true) {
|
||||
await _manageLinkedAlbums();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
opacity: albumSyncEnable ? 1.0 : 0.0,
|
||||
child: albumSyncEnable
|
||||
? SettingListTile(
|
||||
onTap: _manualSyncAlbums,
|
||||
contentPadding: const EdgeInsets.only(left: 32, right: 16),
|
||||
title: "organize_into_albums".t(context: context),
|
||||
subtitle: "organize_into_albums_description".t(context: context),
|
||||
trailing: isAlbumSyncInProgress
|
||||
? const SizedBox(
|
||||
width: 32,
|
||||
height: 32,
|
||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: _manualSyncAlbums,
|
||||
icon: const Icon(Icons.sync_rounded),
|
||||
color: context.colorScheme.onSurface.withValues(alpha: 0.7),
|
||||
iconSize: 20,
|
||||
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (newValue == true) {
|
||||
await _manageLinkedAlbums();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
opacity: albumSyncEnable ? 1.0 : 0.0,
|
||||
child: albumSyncEnable
|
||||
? SettingListTile(
|
||||
onTap: _manualSyncAlbums,
|
||||
contentPadding: const EdgeInsets.only(left: 32, right: 16),
|
||||
title: "organize_into_albums".t(context: context),
|
||||
subtitle: "organize_into_albums_description".t(context: context),
|
||||
trailing: isAlbumSyncInProgress
|
||||
? const SizedBox(
|
||||
width: 32,
|
||||
height: 32,
|
||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: _manualSyncAlbums,
|
||||
icon: const Icon(Icons.sync_rounded),
|
||||
color: context.colorScheme.onSurface.withValues(alpha: 0.7),
|
||||
iconSize: 20,
|
||||
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -157,34 +164,60 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
|
||||
}
|
||||
}
|
||||
|
||||
class _BackupSwitchTile extends ConsumerWidget {
|
||||
final MetadataKey<bool> metadataKey;
|
||||
final bool Function(AppConfig) selector;
|
||||
class _SettingsSwitchTile extends ConsumerStatefulWidget {
|
||||
final AppSettingsEnum<bool> appSettingsEnum;
|
||||
final String titleKey;
|
||||
final String subtitleKey;
|
||||
final void Function(bool)? onChanged;
|
||||
final void Function(bool?)? onChanged;
|
||||
|
||||
const _BackupSwitchTile({
|
||||
required this.metadataKey,
|
||||
required this.selector,
|
||||
const _SettingsSwitchTile({
|
||||
required this.appSettingsEnum,
|
||||
required this.titleKey,
|
||||
required this.subtitleKey,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final value = ref.watch(appConfigProvider.select(selector));
|
||||
ConsumerState createState() => _SettingsSwitchTileState();
|
||||
}
|
||||
|
||||
class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> {
|
||||
late final Stream<bool?> valueStream;
|
||||
late final StreamSubscription<bool?> subscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
valueStream = Store.watch(widget.appSettingsEnum.storeKey).asBroadcastStream();
|
||||
subscription = valueStream.listen((value) {
|
||||
widget.onChanged?.call(value);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
subscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: SettingListTile(
|
||||
title: titleKey.t(context: context),
|
||||
subtitle: subtitleKey.t(context: context),
|
||||
trailing: Switch(
|
||||
value: value,
|
||||
onChanged: (bool newValue) async {
|
||||
await ref.read(metadataProvider).write(metadataKey, newValue);
|
||||
onChanged?.call(newValue);
|
||||
title: widget.titleKey.t(context: context),
|
||||
subtitle: widget.subtitleKey.t(context: context),
|
||||
trailing: StreamBuilder(
|
||||
stream: valueStream,
|
||||
initialData: Store.tryGet(widget.appSettingsEnum.storeKey) ?? widget.appSettingsEnum.defaultValue,
|
||||
builder: (context, snapshot) {
|
||||
final value = snapshot.data ?? false;
|
||||
return Switch(
|
||||
value: value,
|
||||
onChanged: (bool newValue) async {
|
||||
await ref.read(appSettingsServiceProvider).setSetting(widget.appSettingsEnum, newValue);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -192,28 +225,26 @@ class _BackupSwitchTile extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _UseCellularForVideosButton extends StatelessWidget {
|
||||
const _UseCellularForVideosButton();
|
||||
class _UseWifiForUploadVideosButton extends ConsumerWidget {
|
||||
const _UseWifiForUploadVideosButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BackupSwitchTile(
|
||||
metadataKey: MetadataKey.backupUseCellularForVideos,
|
||||
selector: (c) => c.backup.useCellularForVideos,
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return const _SettingsSwitchTile(
|
||||
appSettingsEnum: AppSettingsEnum.useCellularForUploadVideos,
|
||||
titleKey: "videos",
|
||||
subtitleKey: "network_requirement_videos_upload",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UseCellularForPhotosButton extends StatelessWidget {
|
||||
const _UseCellularForPhotosButton();
|
||||
class _UseWifiForUploadPhotosButton extends ConsumerWidget {
|
||||
const _UseWifiForUploadPhotosButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BackupSwitchTile(
|
||||
metadataKey: MetadataKey.backupUseCellularForPhotos,
|
||||
selector: (c) => c.backup.useCellularForPhotos,
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return const _SettingsSwitchTile(
|
||||
appSettingsEnum: AppSettingsEnum.useCellularForUploadPhotos,
|
||||
titleKey: "photos",
|
||||
subtitleKey: "network_requirement_photos_upload",
|
||||
);
|
||||
@@ -225,22 +256,29 @@ class _BackupOnlyWhenChargingButton extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final fgService = ref.read(backgroundWorkerFgServiceProvider);
|
||||
return _BackupSwitchTile(
|
||||
metadataKey: MetadataKey.backupRequireCharging,
|
||||
selector: (c) => c.backup.requireCharging,
|
||||
return _SettingsSwitchTile(
|
||||
appSettingsEnum: AppSettingsEnum.backupRequireCharging,
|
||||
titleKey: "charging",
|
||||
subtitleKey: "charging_requirement_mobile_backup",
|
||||
onChanged: (value) {
|
||||
fgService.configure(requireCharging: value);
|
||||
ref.read(backgroundWorkerFgServiceProvider).configure(requireCharging: value ?? false);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BackupDelaySlider extends ConsumerWidget {
|
||||
class _BackupDelaySlider extends ConsumerStatefulWidget {
|
||||
const _BackupDelaySlider();
|
||||
|
||||
@override
|
||||
ConsumerState<_BackupDelaySlider> createState() => _BackupDelaySliderState();
|
||||
}
|
||||
|
||||
class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> {
|
||||
late final Stream<int?> valueStream;
|
||||
late final StreamSubscription<int?> subscription;
|
||||
late int currentValue;
|
||||
|
||||
static int backupDelayToSliderValue(int ms) => switch (ms) {
|
||||
5 => 0,
|
||||
30 => 1,
|
||||
@@ -263,9 +301,30 @@ class _BackupDelaySlider extends ConsumerWidget {
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final triggerDelay = ref.watch(appConfigProvider.select((c) => c.backup.triggerDelay));
|
||||
final currentValue = backupDelayToSliderValue(triggerDelay);
|
||||
void initState() {
|
||||
super.initState();
|
||||
final initialValue =
|
||||
Store.tryGet(AppSettingsEnum.backupTriggerDelay.storeKey) ?? AppSettingsEnum.backupTriggerDelay.defaultValue;
|
||||
currentValue = backupDelayToSliderValue(initialValue);
|
||||
|
||||
valueStream = Store.watch(AppSettingsEnum.backupTriggerDelay.storeKey).asBroadcastStream();
|
||||
subscription = valueStream.listen((value) {
|
||||
if (mounted && value != null) {
|
||||
setState(() {
|
||||
currentValue = backupDelayToSliderValue(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
subscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -280,13 +339,14 @@ class _BackupDelaySlider extends ConsumerWidget {
|
||||
),
|
||||
Slider(
|
||||
value: currentValue.toDouble(),
|
||||
onChanged: (double v) async {
|
||||
final seconds = backupDelayToSeconds(v.toInt());
|
||||
await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds);
|
||||
onChanged: (double v) {
|
||||
setState(() {
|
||||
currentValue = v.toInt();
|
||||
});
|
||||
},
|
||||
onChangeEnd: (double v) async {
|
||||
final seconds = backupDelayToSeconds(v.toInt());
|
||||
await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds);
|
||||
final milliseconds = backupDelayToSeconds(v.toInt());
|
||||
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.backupTriggerDelay, milliseconds);
|
||||
},
|
||||
max: 3.0,
|
||||
min: 0.0,
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:mocktail/mocktail.dart';
|
||||
import '../../infrastructure/repository.mock.dart';
|
||||
|
||||
const _kAccessToken = '#ThisIsAToken';
|
||||
const _kAdvancedTroubleshooting = false;
|
||||
const _kEnableBackup = false;
|
||||
const _kVersion = 2;
|
||||
|
||||
void main() {
|
||||
@@ -22,13 +22,13 @@ void main() {
|
||||
mockDriftStoreRepo = MockDriftStoreRepository();
|
||||
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
|
||||
registerFallbackValue(StoreKey.accessToken);
|
||||
registerFallbackValue(StoreKey.version);
|
||||
registerFallbackValue(StoreKey.advancedTroubleshooting);
|
||||
registerFallbackValue(StoreKey.backupTriggerDelay);
|
||||
registerFallbackValue(StoreKey.enableBackup);
|
||||
|
||||
when(() => mockDriftStoreRepo.getAll()).thenAnswer(
|
||||
(_) async => [
|
||||
const StoreDto(StoreKey.accessToken, _kAccessToken),
|
||||
const StoreDto(StoreKey.advancedTroubleshooting, _kAdvancedTroubleshooting),
|
||||
const StoreDto(StoreKey.enableBackup, _kEnableBackup),
|
||||
const StoreDto(StoreKey.version, _kVersion),
|
||||
],
|
||||
);
|
||||
@@ -46,7 +46,7 @@ void main() {
|
||||
test('Populates the internal cache on init', () {
|
||||
verify(() => mockDriftStoreRepo.getAll()).called(1);
|
||||
expect(sut.tryGet(StoreKey.accessToken), _kAccessToken);
|
||||
expect(sut.tryGet(StoreKey.advancedTroubleshooting), _kAdvancedTroubleshooting);
|
||||
expect(sut.tryGet(StoreKey.enableBackup), _kEnableBackup);
|
||||
expect(sut.tryGet(StoreKey.version), _kVersion);
|
||||
// Other keys should be null
|
||||
expect(sut.tryGet(StoreKey.currentUser), isNull);
|
||||
@@ -147,7 +147,7 @@ void main() {
|
||||
await sut.clear();
|
||||
verify(() => mockDriftStoreRepo.deleteAll()).called(1);
|
||||
expect(sut.tryGet(StoreKey.accessToken), isNull);
|
||||
expect(sut.tryGet(StoreKey.advancedTroubleshooting), isNull);
|
||||
expect(sut.tryGet(StoreKey.enableBackup), isNull);
|
||||
expect(sut.tryGet(StoreKey.version), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import '../../fixtures/user.stub.dart';
|
||||
|
||||
const _kTestAccessToken = "#TestToken";
|
||||
const _kTestVersion = 10;
|
||||
const _kTestAdvancedTroubleshooting = false;
|
||||
const _kTestBackupRequireCharging = false;
|
||||
final _kTestUser = UserStub.admin;
|
||||
|
||||
Future<void> _populateStore(Drift db) async {
|
||||
@@ -21,8 +21,8 @@ Future<void> _populateStore(Drift db) async {
|
||||
batch.insert(
|
||||
db.storeEntity,
|
||||
StoreEntityCompanion(
|
||||
id: Value(StoreKey.advancedTroubleshooting.id),
|
||||
intValue: const Value(_kTestAdvancedTroubleshooting ? 1 : 0),
|
||||
id: Value(StoreKey.backupRequireCharging.id),
|
||||
intValue: const Value(_kTestBackupRequireCharging ? 1 : 0),
|
||||
stringValue: const Value(null),
|
||||
),
|
||||
);
|
||||
@@ -76,11 +76,11 @@ void main() {
|
||||
});
|
||||
|
||||
test('converts bool', () async {
|
||||
bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
|
||||
expect(advancedTroubleshooting, isNull);
|
||||
await sut.upsert(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting);
|
||||
advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
|
||||
expect(advancedTroubleshooting, _kTestAdvancedTroubleshooting);
|
||||
bool? backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging);
|
||||
expect(backupRequireCharging, isNull);
|
||||
await sut.upsert(StoreKey.backupRequireCharging, _kTestBackupRequireCharging);
|
||||
backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging);
|
||||
expect(backupRequireCharging, _kTestBackupRequireCharging);
|
||||
});
|
||||
|
||||
test('converts user', () async {
|
||||
@@ -98,11 +98,11 @@ void main() {
|
||||
});
|
||||
|
||||
test('delete()', () async {
|
||||
bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
|
||||
expect(advancedTroubleshooting, isFalse);
|
||||
await sut.delete(StoreKey.advancedTroubleshooting);
|
||||
advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting);
|
||||
expect(advancedTroubleshooting, isNull);
|
||||
bool? backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging);
|
||||
expect(backupRequireCharging, isFalse);
|
||||
await sut.delete(StoreKey.backupRequireCharging);
|
||||
backupRequireCharging = await sut.tryGet(StoreKey.backupRequireCharging);
|
||||
expect(backupRequireCharging, isNull);
|
||||
});
|
||||
|
||||
test('deleteAll()', () async {
|
||||
@@ -147,13 +147,13 @@ void main() {
|
||||
emitsInOrder([
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion),
|
||||
const StoreDto<Object>(StoreKey.backupRequireCharging, _kTestBackupRequireCharging),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
||||
],
|
||||
[
|
||||
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
|
||||
const StoreDto<Object>(StoreKey.backupRequireCharging, _kTestBackupRequireCharging),
|
||||
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||
const StoreDto<Object>(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting),
|
||||
],
|
||||
]),
|
||||
),
|
||||
|
||||
@@ -21,6 +21,7 @@ void main() {
|
||||
late MockApiService apiService;
|
||||
late MockNetworkService networkService;
|
||||
late MockBackgroundSyncManager backgroundSyncManager;
|
||||
late MockAppSettingService appSettingsService;
|
||||
late Drift db;
|
||||
|
||||
setUp(() async {
|
||||
@@ -29,12 +30,15 @@ void main() {
|
||||
apiService = MockApiService();
|
||||
networkService = MockNetworkService();
|
||||
backgroundSyncManager = MockBackgroundSyncManager();
|
||||
appSettingsService = MockAppSettingService();
|
||||
|
||||
sut = AuthService(
|
||||
authApiRepository,
|
||||
authRepository,
|
||||
apiService,
|
||||
networkService,
|
||||
backgroundSyncManager,
|
||||
appSettingsService,
|
||||
);
|
||||
|
||||
registerFallbackValue(Uri());
|
||||
|
||||
@@ -13,9 +13,11 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/services/background_upload.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../domain/service.mock.dart';
|
||||
import '../fixtures/asset.stub.dart';
|
||||
import '../infrastructure/repository.mock.dart';
|
||||
import '../mocks/asset_entity.mock.dart';
|
||||
@@ -27,10 +29,13 @@ void main() {
|
||||
late MockStorageRepository mockStorageRepository;
|
||||
late MockDriftLocalAssetRepository mockLocalAssetRepository;
|
||||
late MockDriftBackupRepository mockBackupRepository;
|
||||
late MockAppSettingsService mockAppSettingsService;
|
||||
late MockAssetMediaRepository mockAssetMediaRepository;
|
||||
late Drift db;
|
||||
|
||||
setUpAll(() async {
|
||||
registerFallbackValue(AppSettingsEnum.useCellularForUploadPhotos);
|
||||
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
const MethodChannel('plugins.flutter.io/path_provider'),
|
||||
@@ -49,13 +54,18 @@ void main() {
|
||||
mockStorageRepository = MockStorageRepository();
|
||||
mockLocalAssetRepository = MockDriftLocalAssetRepository();
|
||||
mockBackupRepository = MockDriftBackupRepository();
|
||||
mockAppSettingsService = MockAppSettingsService();
|
||||
mockAssetMediaRepository = MockAssetMediaRepository();
|
||||
|
||||
when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)).thenReturn(false);
|
||||
when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)).thenReturn(false);
|
||||
|
||||
sut = BackgroundUploadService(
|
||||
mockUploadRepository,
|
||||
mockStorageRepository,
|
||||
mockLocalAssetRepository,
|
||||
mockBackupRepository,
|
||||
mockAppSettingsService,
|
||||
mockAssetMediaRepository,
|
||||
);
|
||||
|
||||
@@ -171,6 +181,7 @@ void main() {
|
||||
mockStorageRepository,
|
||||
mockLocalAssetRepository,
|
||||
mockBackupRepository,
|
||||
mockAppSettingsService,
|
||||
mockAssetMediaRepository,
|
||||
);
|
||||
addTearDown(() => sutWithV24.dispose());
|
||||
@@ -221,6 +232,7 @@ void main() {
|
||||
mockStorageRepository,
|
||||
mockLocalAssetRepository,
|
||||
mockBackupRepository,
|
||||
mockAppSettingsService,
|
||||
mockAssetMediaRepository,
|
||||
);
|
||||
addTearDown(() => sutAndroid.dispose());
|
||||
@@ -261,6 +273,7 @@ void main() {
|
||||
mockStorageRepository,
|
||||
mockLocalAssetRepository,
|
||||
mockBackupRepository,
|
||||
mockAppSettingsService,
|
||||
mockAssetMediaRepository,
|
||||
);
|
||||
addTearDown(() => sutWithV24.dispose());
|
||||
@@ -301,6 +314,7 @@ void main() {
|
||||
mockStorageRepository,
|
||||
mockLocalAssetRepository,
|
||||
mockBackupRepository,
|
||||
mockAppSettingsService,
|
||||
mockAssetMediaRepository,
|
||||
);
|
||||
addTearDown(() => sutWithV24.dispose());
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"description": "Album ID",
|
||||
"x-nestjs_zod-parent-metadata": {
|
||||
"description": "Activity search"
|
||||
},
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
@@ -22,6 +25,9 @@
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Asset ID (if activity is for an asset)",
|
||||
"x-nestjs_zod-parent-metadata": {
|
||||
"description": "Activity search"
|
||||
},
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
@@ -32,6 +38,9 @@
|
||||
"name": "level",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"x-nestjs_zod-parent-metadata": {
|
||||
"description": "Activity search"
|
||||
},
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ReactionLevel"
|
||||
}
|
||||
@@ -40,6 +49,9 @@
|
||||
"name": "type",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"x-nestjs_zod-parent-metadata": {
|
||||
"description": "Activity search"
|
||||
},
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ReactionType"
|
||||
}
|
||||
@@ -49,6 +61,9 @@
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by user ID",
|
||||
"x-nestjs_zod-parent-metadata": {
|
||||
"description": "Activity search"
|
||||
},
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
@@ -172,6 +187,9 @@
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"description": "Album ID",
|
||||
"x-nestjs_zod-parent-metadata": {
|
||||
"description": "Activity"
|
||||
},
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
@@ -183,6 +201,9 @@
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Asset ID (if activity is for an asset)",
|
||||
"x-nestjs_zod-parent-metadata": {
|
||||
"description": "Activity"
|
||||
},
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/micromatch": "^4.0.9",
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/node": "^24.12.4",
|
||||
"@types/node": "^24.12.2",
|
||||
"@vitest/coverage-v8": "^4.0.0",
|
||||
"byte-size": "^9.0.0",
|
||||
"cli-progress": "^3.12.0",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"packageManager": "pnpm@10.30.3",
|
||||
"devDependencies": {
|
||||
"@extism/js-pdk": "^1.1.1",
|
||||
"@types/node": "^24.12.4",
|
||||
"@types/node": "^24.11.0",
|
||||
"esbuild": "^0.27.3",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"typescript": "^5.9.3"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.12.4",
|
||||
"@types/node": "^24.12.2",
|
||||
"typescript": "^6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+258
-258
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -138,7 +138,7 @@
|
||||
"@types/luxon": "^3.6.2",
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node": "^24.12.4",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/nodemailer": "^8.0.0",
|
||||
"@types/picomatch": "^4.0.0",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
|
||||
@@ -36,16 +36,18 @@ const ActivityStatisticsResponseSchema = z
|
||||
})
|
||||
.meta({ id: 'ActivityStatisticsResponseDto' });
|
||||
|
||||
const ActivitySchema = z.object({
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
assetId: z.uuidv4().optional().describe('Asset ID (if activity is for an asset)'),
|
||||
});
|
||||
const ActivitySchema = z
|
||||
.object({
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
assetId: z.uuidv4().optional().describe('Asset ID (if activity is for an asset)'),
|
||||
})
|
||||
.describe('Activity');
|
||||
|
||||
const ActivitySearchSchema = ActivitySchema.extend({
|
||||
type: ReactionTypeSchema.optional(),
|
||||
level: ReactionLevelSchema.optional(),
|
||||
userId: z.uuidv4().optional().describe('Filter by user ID'),
|
||||
});
|
||||
}).describe('Activity search');
|
||||
|
||||
const ActivityCreateSchema = ActivitySchema.extend({
|
||||
type: ReactionTypeSchema,
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^7.0.0",
|
||||
"svelte": "5.55.8",
|
||||
"svelte": "5.55.2",
|
||||
"svelte-check": "^4.4.6",
|
||||
"svelte-eslint-parser": "^1.3.3",
|
||||
"tailwindcss": "^4.2.4",
|
||||
|
||||
Reference in New Issue
Block a user