mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
feat(mobile): preserve mobile album info on upload (#11965)
* curating assets with albums to upload * sorting for background backup * background upload works * transform fields string array to javascript array * send json array * generate sql * refactor upload callback * remove albums info from upload payload * mechanism to create album on album selection * album creation * Sync to upload album * Remove unused service * unify name changes * Add mechanism to sync uploaded assets to albums * Put add to album operation after updating the UI state * clean up * background album sync * add to album in background context * remove add to album in callback * refactor * refactor * refactor * fix: make sure all selected albums are selected for building upload candidate * clean up * add manual sync button * lint * revert server changes * pr feedback * revert time filtering * const * sync album on manual upload * linting * pr feedback and proper time filtering * wording
This commit is contained in:
parent
f4371578f5
commit
6b6d2a6621
19 changed files with 657 additions and 233 deletions
|
|
@ -5,15 +5,21 @@ import 'package:fluttertoast/fluttertoast.dart';
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/backup/available_album.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class AlbumInfoCard extends HookConsumerWidget {
|
||||
final AvailableAlbum album;
|
||||
|
||||
const AlbumInfoCard({super.key, required this.album});
|
||||
const AlbumInfoCard({
|
||||
super.key,
|
||||
required this.album,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
|
|
@ -21,6 +27,9 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||
ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||
final bool isExcluded =
|
||||
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||
final syncAlbum = ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.syncAlbums);
|
||||
|
||||
final isDarkTheme = context.isDarkTheme;
|
||||
|
||||
|
|
@ -85,6 +94,9 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||
ref.read(backupProvider.notifier).removeAlbumForBackup(album);
|
||||
} else {
|
||||
ref.read(backupProvider.notifier).addAlbumForBackup(album);
|
||||
if (syncAlbum) {
|
||||
ref.read(albumProvider.notifier).createSyncAlbum(album.name);
|
||||
}
|
||||
}
|
||||
},
|
||||
onDoubleTap: () {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@ import 'package:fluttertoast/fluttertoast.dart';
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/backup/available_album.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class AlbumInfoListTile extends HookConsumerWidget {
|
||||
|
|
@ -21,7 +24,10 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||
ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||
final bool isExcluded =
|
||||
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||
var assetCount = useState(0);
|
||||
final assetCount = useState(0);
|
||||
final syncAlbum = ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.syncAlbums);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
|
|
@ -98,6 +104,9 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||
ref.read(backupProvider.notifier).removeAlbumForBackup(album);
|
||||
} else {
|
||||
ref.read(backupProvider.notifier).addAlbumForBackup(album);
|
||||
if (syncAlbum) {
|
||||
ref.read(albumProvider.notifier).createSyncAlbum(album.name);
|
||||
}
|
||||
}
|
||||
},
|
||||
leading: buildIcon(),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/providers/backup/backup_verification.provider.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/services/asset.service.dart';
|
||||
import 'package:immich_mobile/widgets/settings/backup_settings/background_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/backup_settings/foreground_settings.dart';
|
||||
import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart';
|
||||
|
|
@ -23,7 +26,21 @@ class BackupSettings extends HookConsumerWidget {
|
|||
useAppSettingsState(AppSettingsEnum.ignoreIcloudAssets);
|
||||
final isAdvancedTroubleshooting =
|
||||
useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
|
||||
final albumSync = useAppSettingsState(AppSettingsEnum.syncAlbums);
|
||||
final isCorruptCheckInProgress = ref.watch(backupVerificationProvider);
|
||||
final isAlbumSyncInProgress = useState(false);
|
||||
|
||||
syncAlbums() async {
|
||||
isAlbumSyncInProgress.value = true;
|
||||
try {
|
||||
await ref.read(assetServiceProvider).syncUploadedAssetToAlbums();
|
||||
} catch (_) {
|
||||
} finally {
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
isAlbumSyncInProgress.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final backupSettings = [
|
||||
const ForegroundBackupSettings(),
|
||||
|
|
@ -58,6 +75,23 @@ class BackupSettings extends HookConsumerWidget {
|
|||
.performBackupCheck(context)
|
||||
: null,
|
||||
),
|
||||
if (albumSync.value)
|
||||
SettingsButtonListTile(
|
||||
icon: Icons.photo_album_outlined,
|
||||
title: 'sync_albums'.tr(),
|
||||
subtitle: Text(
|
||||
"sync_albums_manual_subtitle".tr(),
|
||||
),
|
||||
buttonText: 'sync_albums'.tr(),
|
||||
child: isAlbumSyncInProgress.value
|
||||
? const CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: syncAlbums,
|
||||
child: Text('sync'.tr()),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
return SettingsSubPageScaffold(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class SettingsButtonListTile extends StatelessWidget {
|
|||
final Widget? subtitle;
|
||||
final String? subtileText;
|
||||
final String buttonText;
|
||||
final Widget? child;
|
||||
final void Function()? onButtonTap;
|
||||
|
||||
const SettingsButtonListTile({
|
||||
|
|
@ -18,6 +19,7 @@ class SettingsButtonListTile extends StatelessWidget {
|
|||
this.subtileText,
|
||||
this.subtitle,
|
||||
required this.buttonText,
|
||||
this.child,
|
||||
this.onButtonTap,
|
||||
super.key,
|
||||
});
|
||||
|
|
@ -48,7 +50,8 @@ class SettingsButtonListTile extends StatelessWidget {
|
|||
),
|
||||
if (subtitle != null) subtitle!,
|
||||
const SizedBox(height: 6),
|
||||
ElevatedButton(onPressed: onButtonTap, child: Text(buttonText)),
|
||||
child ??
|
||||
ElevatedButton(onPressed: onButtonTap, child: Text(buttonText)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ class SettingsSwitchListTile extends StatelessWidget {
|
|||
final String? subtitle;
|
||||
final IconData? icon;
|
||||
final Function(bool)? onChanged;
|
||||
final EdgeInsets? contentPadding;
|
||||
final TextStyle? titleStyle;
|
||||
final TextStyle? subtitleStyle;
|
||||
|
||||
const SettingsSwitchListTile({
|
||||
required this.valueNotifier,
|
||||
|
|
@ -17,6 +20,9 @@ class SettingsSwitchListTile extends StatelessWidget {
|
|||
this.icon,
|
||||
this.enabled = true,
|
||||
this.onChanged,
|
||||
this.contentPadding = const EdgeInsets.symmetric(horizontal: 20),
|
||||
this.titleStyle,
|
||||
this.subtitleStyle,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -30,7 +36,7 @@ class SettingsSwitchListTile extends StatelessWidget {
|
|||
}
|
||||
|
||||
return SwitchListTile.adaptive(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
contentPadding: contentPadding,
|
||||
selectedTileColor: enabled ? null : context.themeData.disabledColor,
|
||||
value: valueNotifier.value,
|
||||
onChanged: onSwitchChanged,
|
||||
|
|
@ -45,20 +51,22 @@ class SettingsSwitchListTile extends StatelessWidget {
|
|||
: null,
|
||||
title: Text(
|
||||
title,
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: enabled ? null : context.themeData.disabledColor,
|
||||
height: 1.5,
|
||||
),
|
||||
style: titleStyle ??
|
||||
context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: enabled ? null : context.themeData.disabledColor,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
subtitle: subtitle != null
|
||||
? Text(
|
||||
subtitle!,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: enabled
|
||||
? context.colorScheme.onSurfaceSecondary
|
||||
: context.themeData.disabledColor,
|
||||
),
|
||||
style: subtitleStyle ??
|
||||
context.textTheme.bodyMedium?.copyWith(
|
||||
color: enabled
|
||||
? context.colorScheme.onSurfaceSecondary
|
||||
: context.themeData.disabledColor,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue