1
0
mirror of https://github.com/immich-app/immich.git synced 2025-04-23 13:09:00 +02:00

chore(mobile): remove exclude album mechanism for backup (#10552)

* chore(mobile): remove exclude album selection mechanism

* code generator

* code generator
This commit is contained in:
Alex 2024-06-22 15:31:27 -07:00 committed by GitHub
parent 8e2f6f1f41
commit 5f47cf604a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 32 additions and 309 deletions

View File

@ -18,5 +18,4 @@ class BackupAlbum {
enum BackupSelection { enum BackupSelection {
none, none,
select, select,
exclude;
} }

View File

@ -107,12 +107,10 @@ P _backupAlbumDeserializeProp<P>(
const _BackupAlbumselectionEnumValueMap = { const _BackupAlbumselectionEnumValueMap = {
'none': 0, 'none': 0,
'select': 1, 'select': 1,
'exclude': 2,
}; };
const _BackupAlbumselectionValueEnumMap = { const _BackupAlbumselectionValueEnumMap = {
0: BackupSelection.none, 0: BackupSelection.none,
1: BackupSelection.select, 1: BackupSelection.select,
2: BackupSelection.exclude,
}; };
Id _backupAlbumGetId(BackupAlbum object) { Id _backupAlbumGetId(BackupAlbum object) {

View File

@ -38,10 +38,9 @@ class BackUpState {
/// All available albums on the device /// All available albums on the device
final List<AvailableAlbum> availableAlbums; final List<AvailableAlbum> availableAlbums;
final Set<AvailableAlbum> selectedBackupAlbums; final Set<AvailableAlbum> selectedBackupAlbums;
final Set<AvailableAlbum> excludedBackupAlbums;
/// Assets that are not overlapping in selected backup albums and excluded backup albums /// Assets that are not overlapping in selected backup albums and excluded backup albums
final Set<AssetEntity> allUniqueAssets; final Set<AssetEntity> backupCandidates;
/// All assets from the selected albums that have been backup /// All assets from the selected albums that have been backup
final Set<String> selectedAlbumsBackupAssetsIds; final Set<String> selectedAlbumsBackupAssetsIds;
@ -68,8 +67,7 @@ class BackUpState {
required this.backupTriggerDelay, required this.backupTriggerDelay,
required this.availableAlbums, required this.availableAlbums,
required this.selectedBackupAlbums, required this.selectedBackupAlbums,
required this.excludedBackupAlbums, required this.backupCandidates,
required this.allUniqueAssets,
required this.selectedAlbumsBackupAssetsIds, required this.selectedAlbumsBackupAssetsIds,
required this.currentUploadAsset, required this.currentUploadAsset,
}); });
@ -93,8 +91,7 @@ class BackUpState {
int? backupTriggerDelay, int? backupTriggerDelay,
List<AvailableAlbum>? availableAlbums, List<AvailableAlbum>? availableAlbums,
Set<AvailableAlbum>? selectedBackupAlbums, Set<AvailableAlbum>? selectedBackupAlbums,
Set<AvailableAlbum>? excludedBackupAlbums, Set<AssetEntity>? backupCandidates,
Set<AssetEntity>? allUniqueAssets,
Set<String>? selectedAlbumsBackupAssetsIds, Set<String>? selectedAlbumsBackupAssetsIds,
CurrentUploadAsset? currentUploadAsset, CurrentUploadAsset? currentUploadAsset,
}) { }) {
@ -121,8 +118,7 @@ class BackUpState {
backupTriggerDelay: backupTriggerDelay ?? this.backupTriggerDelay, backupTriggerDelay: backupTriggerDelay ?? this.backupTriggerDelay,
availableAlbums: availableAlbums ?? this.availableAlbums, availableAlbums: availableAlbums ?? this.availableAlbums,
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums, selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums, backupCandidates: backupCandidates ?? this.backupCandidates,
allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets,
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssetsIds:
selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds, selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds,
currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset, currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset,
@ -131,7 +127,7 @@ class BackUpState {
@override @override
String toString() { String toString() {
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, progressInFileSize: $progressInFileSize, progressInFileSpeed: $progressInFileSpeed, progressInFileSpeeds: $progressInFileSpeeds, progressInFileSpeedUpdateTime: $progressInFileSpeedUpdateTime, progressInFileSpeedUpdateSentBytes: $progressInFileSpeedUpdateSentBytes, iCloudDownloadProgress: $iCloudDownloadProgress, cancelToken: $cancelToken, serverInfo: $serverInfo, autoBackup: $autoBackup, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, backupTriggerDelay: $backupTriggerDelay, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)'; return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, progressInFileSize: $progressInFileSize, progressInFileSpeed: $progressInFileSpeed, progressInFileSpeeds: $progressInFileSpeeds, progressInFileSpeedUpdateTime: $progressInFileSpeedUpdateTime, progressInFileSpeedUpdateSentBytes: $progressInFileSpeedUpdateSentBytes, iCloudDownloadProgress: $iCloudDownloadProgress, cancelToken: $cancelToken, serverInfo: $serverInfo, autoBackup: $autoBackup, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, backupTriggerDelay: $backupTriggerDelay, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, backupCandidates: $backupCandidates, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)';
} }
@override @override
@ -158,8 +154,7 @@ class BackUpState {
other.backupTriggerDelay == backupTriggerDelay && other.backupTriggerDelay == backupTriggerDelay &&
collectionEquals(other.availableAlbums, availableAlbums) && collectionEquals(other.availableAlbums, availableAlbums) &&
collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) && collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) &&
collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) && collectionEquals(other.backupCandidates, backupCandidates) &&
collectionEquals(other.allUniqueAssets, allUniqueAssets) &&
collectionEquals( collectionEquals(
other.selectedAlbumsBackupAssetsIds, other.selectedAlbumsBackupAssetsIds,
selectedAlbumsBackupAssetsIds, selectedAlbumsBackupAssetsIds,
@ -187,8 +182,7 @@ class BackUpState {
backupTriggerDelay.hashCode ^ backupTriggerDelay.hashCode ^
availableAlbums.hashCode ^ availableAlbums.hashCode ^
selectedBackupAlbums.hashCode ^ selectedBackupAlbums.hashCode ^
excludedBackupAlbums.hashCode ^ backupCandidates.hashCode ^
allUniqueAssets.hashCode ^
selectedAlbumsBackupAssetsIds.hashCode ^ selectedAlbumsBackupAssetsIds.hashCode ^
currentUploadAsset.hashCode; currentUploadAsset.hashCode;
} }

View File

@ -3,7 +3,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/constants/immich_colors.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/widgets/backup/album_info_card.dart'; import 'package:immich_mobile/widgets/backup/album_info_card.dart';
@ -17,7 +16,6 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
// final availableAlbums = ref.watch(backupProvider).availableAlbums; // final availableAlbums = ref.watch(backupProvider).availableAlbums;
final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums; final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums;
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
final isDarkTheme = context.isDarkTheme; final isDarkTheme = context.isDarkTheme;
final albums = ref.watch(backupProvider).availableAlbums; final albums = ref.watch(backupProvider).availableAlbums;
@ -111,83 +109,6 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
}).toSet(); }).toSet();
} }
buildExcludedAlbumNameChip() {
return excludedBackupAlbums.map((album) {
void removeSelection() {
ref
.watch(backupProvider.notifier)
.removeExcludedAlbumForBackup(album);
}
return GestureDetector(
onTap: removeSelection,
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Chip(
label: Text(
album.name,
style: TextStyle(
fontSize: 12,
color: isDarkTheme ? Colors.black : immichBackgroundColor,
fontWeight: FontWeight.bold,
),
),
backgroundColor: Colors.red[300],
deleteIconColor:
isDarkTheme ? Colors.black : immichBackgroundColor,
deleteIcon: const Icon(
Icons.cancel_rounded,
size: 15,
),
onDeleted: removeSelection,
),
),
);
}).toSet();
}
// buildSearchBar() {
// return Padding(
// padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 8.0),
// child: TextFormField(
// onChanged: (searchValue) {
// // if (searchValue.isEmpty) {
// // albums = availableAlbums;
// // } else {
// // albums.value = availableAlbums
// // .where(
// // (album) => album.name
// // .toLowerCase()
// // .contains(searchValue.toLowerCase()),
// // )
// // .toList();
// // }
// },
// decoration: InputDecoration(
// contentPadding: const EdgeInsets.symmetric(
// horizontal: 8.0,
// vertical: 8.0,
// ),
// hintText: "Search",
// hintStyle: TextStyle(
// color: isDarkTheme ? Colors.white : Colors.grey,
// fontSize: 14.0,
// ),
// prefixIcon: const Icon(
// Icons.search,
// color: Colors.grey,
// ),
// border: OutlineInputBorder(
// borderRadius: BorderRadius.circular(10),
// borderSide: BorderSide.none,
// ),
// filled: true,
// fillColor: isDarkTheme ? Colors.white30 : Colors.grey[200],
// ),
// ),
// );
// }
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
@ -223,7 +144,6 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
child: Wrap( child: Wrap(
children: [ children: [
...buildSelectedAlbumNameChip(), ...buildSelectedAlbumNameChip(),
...buildExcludedAlbumNameChip(),
], ],
), ),
), ),

View File

@ -29,7 +29,7 @@ class BackupControllerPage extends HookConsumerWidget {
final didGetBackupInfo = useState(false); final didGetBackupInfo = useState(false);
bool hasExclusiveAccess = bool hasExclusiveAccess =
backupState.backupProgress != BackUpProgressEnum.inBackground; backupState.backupProgress != BackUpProgressEnum.inBackground;
bool shouldBackup = backupState.allUniqueAssets.length - bool shouldBackup = backupState.backupCandidates.length -
backupState.selectedAlbumsBackupAssetsIds.length == backupState.selectedAlbumsBackupAssetsIds.length ==
0 || 0 ||
!hasExclusiveAccess !hasExclusiveAccess
@ -100,29 +100,6 @@ class BackupControllerPage extends HookConsumerWidget {
} }
} }
Widget buildExcludedAlbumName() {
var text = "backup_controller_page_excluded".tr();
var albums = ref.watch(backupProvider).excludedBackupAlbums;
if (albums.isNotEmpty) {
for (var album in albums) {
text += "${album.name}, ";
}
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
text.trim().substring(0, text.length - 2),
style: context.textTheme.labelLarge?.copyWith(
color: Colors.red[300],
),
),
);
} else {
return const SizedBox();
}
}
buildFolderSelectionTile() { buildFolderSelectionTile() {
return Padding( return Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
@ -154,7 +131,6 @@ class BackupControllerPage extends HookConsumerWidget {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
).tr(), ).tr(),
buildSelectedAlbumName(), buildSelectedAlbumName(),
buildExcludedAlbumName(),
], ],
), ),
), ),
@ -292,7 +268,7 @@ class BackupControllerPage extends HookConsumerWidget {
subtitle: "backup_controller_page_total_sub".tr(), subtitle: "backup_controller_page_total_sub".tr(),
info: ref.watch(backupProvider).availableAlbums.isEmpty info: ref.watch(backupProvider).availableAlbums.isEmpty
? "..." ? "..."
: "${backupState.allUniqueAssets.length}", : "${backupState.backupCandidates.length}",
), ),
BackupInfoCard( BackupInfoCard(
title: "backup_controller_page_backup".tr(), title: "backup_controller_page_backup".tr(),
@ -306,7 +282,7 @@ class BackupControllerPage extends HookConsumerWidget {
subtitle: "backup_controller_page_remainder_sub".tr(), subtitle: "backup_controller_page_remainder_sub".tr(),
info: ref.watch(backupProvider).availableAlbums.isEmpty info: ref.watch(backupProvider).availableAlbums.isEmpty
? "..." ? "..."
: "${max(0, backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length)}", : "${max(0, backupState.backupCandidates.length - backupState.selectedAlbumsBackupAssetsIds.length)}",
), ),
const Divider(), const Divider(),
const CurrentUploadingAssetInfoBox(), const CurrentUploadingAssetInfoBox(),

View File

@ -61,8 +61,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
), ),
availableAlbums: const [], availableAlbums: const [],
selectedBackupAlbums: const {}, selectedBackupAlbums: const {},
excludedBackupAlbums: const {}, backupCandidates: const {},
allUniqueAssets: const {},
selectedAlbumsBackupAssetsIds: const {}, selectedAlbumsBackupAssetsIds: const {},
currentUploadAsset: CurrentUploadAsset( currentUploadAsset: CurrentUploadAsset(
id: '...', id: '...',
@ -94,22 +93,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
/// The total unique assets will be used for backing mechanism /// The total unique assets will be used for backing mechanism
/// ///
void addAlbumForBackup(AvailableAlbum album) { void addAlbumForBackup(AvailableAlbum album) {
if (state.excludedBackupAlbums.contains(album)) {
removeExcludedAlbumForBackup(album);
}
state = state state = state
.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album}); .copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album});
} }
void addExcludedAlbumForBackup(AvailableAlbum album) {
if (state.selectedBackupAlbums.contains(album)) {
removeAlbumForBackup(album);
}
state = state
.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album});
}
void removeAlbumForBackup(AvailableAlbum album) { void removeAlbumForBackup(AvailableAlbum album) {
Set<AvailableAlbum> currentSelectedAlbums = state.selectedBackupAlbums; Set<AvailableAlbum> currentSelectedAlbums = state.selectedBackupAlbums;
@ -118,14 +105,6 @@ class BackupNotifier extends StateNotifier<BackUpState> {
state = state.copyWith(selectedBackupAlbums: currentSelectedAlbums); state = state.copyWith(selectedBackupAlbums: currentSelectedAlbums);
} }
void removeExcludedAlbumForBackup(AvailableAlbum album) {
Set<AvailableAlbum> currentExcludedAlbums = state.excludedBackupAlbums;
currentExcludedAlbums.removeWhere((a) => a == album);
state = state.copyWith(excludedBackupAlbums: currentExcludedAlbums);
}
Future<void> backupAlbumSelectionDone() { Future<void> backupAlbumSelectionDone() {
if (state.selectedBackupAlbums.isEmpty) { if (state.selectedBackupAlbums.isEmpty) {
// disable any backup // disable any backup
@ -240,8 +219,6 @@ class BackupNotifier extends StateNotifier<BackUpState> {
} }
state = state.copyWith(availableAlbums: availableAlbums); state = state.copyWith(availableAlbums: availableAlbums);
final List<BackupAlbum> excludedBackupAlbums =
await _backupService.excludedAlbumsQuery().findAll();
final List<BackupAlbum> selectedBackupAlbums = final List<BackupAlbum> selectedBackupAlbums =
await _backupService.selectedAlbumsQuery().findAll(); await _backupService.selectedAlbumsQuery().findAll();
@ -259,22 +236,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
} }
} }
final Set<AvailableAlbum> excludedAlbums = {};
for (final BackupAlbum ba in excludedBackupAlbums) {
final albumAsset = albumMap[ba.id];
if (albumAsset != null) {
excludedAlbums.add(
AvailableAlbum(albumEntity: albumAsset, lastBackup: ba.lastBackup),
);
} else {
log.severe('Excluded album not found');
}
}
state = state.copyWith( state = state.copyWith(
selectedBackupAlbums: selectedAlbums, selectedBackupAlbums: selectedAlbums,
excludedBackupAlbums: excludedAlbums,
); );
log.info( log.info(
@ -290,8 +253,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
/// ///
Future<void> _updateBackupAssetCount() async { Future<void> _updateBackupAssetCount() async {
final duplicatedAssetIds = await _backupService.getDuplicatedAssetIds(); final duplicatedAssetIds = await _backupService.getDuplicatedAssetIds();
final Set<AssetEntity> assetsFromSelectedAlbums = {}; final Set<AssetEntity> backupCandidates = {};
final Set<AssetEntity> assetsFromExcludedAlbums = {};
for (final album in state.selectedBackupAlbums) { for (final album in state.selectedBackupAlbums) {
final assetCount = await album.albumEntity.assetCountAsync; final assetCount = await album.albumEntity.assetCountAsync;
@ -304,25 +266,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
start: 0, start: 0,
end: assetCount, end: assetCount,
); );
assetsFromSelectedAlbums.addAll(assets); backupCandidates.addAll(assets);
} }
for (final album in state.excludedBackupAlbums) {
final assetCount = await album.albumEntity.assetCountAsync;
if (assetCount == 0) {
continue;
}
final assets = await album.albumEntity.getAssetListRange(
start: 0,
end: assetCount,
);
assetsFromExcludedAlbums.addAll(assets);
}
final Set<AssetEntity> allUniqueAssets =
assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums);
final allAssetsInDatabase = await _backupService.getDeviceBackupAsset(); final allAssetsInDatabase = await _backupService.getDeviceBackupAsset();
if (allAssetsInDatabase == null) { if (allAssetsInDatabase == null) {
@ -331,28 +277,28 @@ class BackupNotifier extends StateNotifier<BackUpState> {
// Find asset that were backup from selected albums // Find asset that were backup from selected albums
final Set<String> selectedAlbumsBackupAssets = final Set<String> selectedAlbumsBackupAssets =
Set.from(allUniqueAssets.map((e) => e.id)); Set.from(backupCandidates.map((e) => e.id));
selectedAlbumsBackupAssets selectedAlbumsBackupAssets
.removeWhere((assetId) => !allAssetsInDatabase.contains(assetId)); .removeWhere((assetId) => !allAssetsInDatabase.contains(assetId));
// Remove duplicated asset from all unique assets // Remove duplicated asset from all unique assets
allUniqueAssets.removeWhere( backupCandidates.removeWhere(
(asset) => duplicatedAssetIds.contains(asset.id), (asset) => duplicatedAssetIds.contains(asset.id),
); );
if (allUniqueAssets.isEmpty) { if (backupCandidates.isEmpty) {
log.info("No assets are selected for back up"); log.info("No assets are selected for back up");
state = state.copyWith( state = state.copyWith(
backupProgress: BackUpProgressEnum.idle, backupProgress: BackUpProgressEnum.idle,
allAssetsInDatabase: allAssetsInDatabase, allAssetsInDatabase: allAssetsInDatabase,
allUniqueAssets: {}, backupCandidates: {},
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets, selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
); );
} else { } else {
state = state.copyWith( state = state.copyWith(
allAssetsInDatabase: allAssetsInDatabase, allAssetsInDatabase: allAssetsInDatabase,
allUniqueAssets: allUniqueAssets, backupCandidates: backupCandidates,
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets, selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
); );
} }
@ -387,10 +333,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
final selected = state.selectedBackupAlbums.map( final selected = state.selectedBackupAlbums.map(
(e) => BackupAlbum(e.id, e.lastBackup ?? epoch, BackupSelection.select), (e) => BackupAlbum(e.id, e.lastBackup ?? epoch, BackupSelection.select),
); );
final excluded = state.excludedBackupAlbums.map(
(e) => BackupAlbum(e.id, e.lastBackup ?? epoch, BackupSelection.exclude), final backupAlbums = selected.toList();
);
final backupAlbums = selected.followedBy(excluded).toList();
backupAlbums.sortBy((e) => e.id); backupAlbums.sortBy((e) => e.id);
return _db.writeTxn(() async { return _db.writeTxn(() async {
final dbAlbums = await _db.backupAlbums.where().sortById().findAll(); final dbAlbums = await _db.backupAlbums.where().sortById().findAll();
@ -427,13 +371,13 @@ class BackupNotifier extends StateNotifier<BackUpState> {
if (hasPermission) { if (hasPermission) {
await PhotoManager.clearFileCache(); await PhotoManager.clearFileCache();
if (state.allUniqueAssets.isEmpty) { if (state.backupCandidates.isEmpty) {
log.info("No Asset On Device - Abort Backup Process"); log.info("No Asset On Device - Abort Backup Process");
state = state.copyWith(backupProgress: BackUpProgressEnum.idle); state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
return; return;
} }
Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets); Set<AssetEntity> assetsWillBeBackup = Set.from(state.backupCandidates);
// Remove item that has already been backed up // Remove item that has already been backed up
for (final assetId in state.allAssetsInDatabase) { for (final assetId in state.allAssetsInDatabase) {
assetsWillBeBackup.removeWhere((e) => e.id == assetId); assetsWillBeBackup.removeWhere((e) => e.id == assetId);
@ -504,7 +448,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
) { ) {
if (isDuplicated) { if (isDuplicated) {
state = state.copyWith( state = state.copyWith(
allUniqueAssets: state.allUniqueAssets backupCandidates: state.backupCandidates
.where((asset) => asset.id != deviceAssetId) .where((asset) => asset.id != deviceAssetId)
.toSet(), .toSet(),
); );
@ -518,20 +462,17 @@ class BackupNotifier extends StateNotifier<BackUpState> {
); );
} }
if (state.allUniqueAssets.length - if (state.backupCandidates.length -
state.selectedAlbumsBackupAssetsIds.length == state.selectedAlbumsBackupAssetsIds.length ==
0) { 0) {
final latestAssetBackup = final latestAssetBackup =
state.allUniqueAssets.map((e) => e.modifiedDateTime).reduce( state.backupCandidates.map((e) => e.modifiedDateTime).reduce(
(v, e) => e.isAfter(v) ? e : v, (v, e) => e.isAfter(v) ? e : v,
); );
state = state.copyWith( state = state.copyWith(
selectedBackupAlbums: state.selectedBackupAlbums selectedBackupAlbums: state.selectedBackupAlbums
.map((e) => e.copyWith(lastBackup: latestAssetBackup)) .map((e) => e.copyWith(lastBackup: latestAssetBackup))
.toSet(), .toSet(),
excludedBackupAlbums: state.excludedBackupAlbums
.map((e) => e.copyWith(lastBackup: latestAssetBackup))
.toSet(),
backupProgress: BackUpProgressEnum.done, backupProgress: BackUpProgressEnum.done,
progressInPercentage: 0.0, progressInPercentage: 0.0,
progressInFileSize: "0 B / 0 B", progressInFileSize: "0 B / 0 B",
@ -630,12 +571,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
.filter() .filter()
.selectionEqualTo(BackupSelection.select) .selectionEqualTo(BackupSelection.select)
.findAll(); .findAll();
final List<BackupAlbum> excludedBackupAlbums = await _db.backupAlbums
.filter()
.selectionEqualTo(BackupSelection.exclude)
.findAll();
Set<AvailableAlbum> selectedAlbums = state.selectedBackupAlbums; Set<AvailableAlbum> selectedAlbums = state.selectedBackupAlbums;
Set<AvailableAlbum> excludedAlbums = state.excludedBackupAlbums;
if (selectedAlbums.isNotEmpty) { if (selectedAlbums.isNotEmpty) {
selectedAlbums = _updateAlbumsBackupTime( selectedAlbums = _updateAlbumsBackupTime(
selectedAlbums, selectedAlbums,
@ -643,17 +580,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
); );
} }
if (excludedAlbums.isNotEmpty) {
excludedAlbums = _updateAlbumsBackupTime(
excludedAlbums,
excludedBackupAlbums,
);
}
final BackUpProgressEnum previous = state.backupProgress; final BackUpProgressEnum previous = state.backupProgress;
state = state.copyWith( state = state.copyWith(
backupProgress: BackUpProgressEnum.inBackground, backupProgress: BackUpProgressEnum.inBackground,
selectedBackupAlbums: selectedAlbums, selectedBackupAlbums: selectedAlbums,
excludedBackupAlbums: excludedAlbums,
); );
// assumes the background service is currently running // assumes the background service is currently running
// if true, waits until it has stopped to start the backup // if true, waits until it has stopped to start the backup

View File

@ -23,7 +23,7 @@ class BackupVerification extends _$BackupVerification {
state = true; state = true;
final backupState = ref.read(backupProvider); final backupState = ref.read(backupProvider);
if (backupState.allUniqueAssets.length > if (backupState.backupCandidates.length >
backupState.selectedAlbumsBackupAssetsIds.length) { backupState.selectedAlbumsBackupAssetsIds.length) {
if (context.mounted) { if (context.mounted) {
ImmichToast.show( ImmichToast.show(

View File

@ -7,7 +7,7 @@ part of 'backup_verification.provider.dart';
// ************************************************************************** // **************************************************************************
String _$backupVerificationHash() => String _$backupVerificationHash() =>
r'b691e0cc27856eef189258d3c102cc73ce4812a4'; r'4f64459d68d20de4a61160ec8e9be347ec945fb6';
/// See also [BackupVerification]. /// See also [BackupVerification].
@ProviderFor(BackupVerification) @ProviderFor(BackupVerification)

View File

@ -349,7 +349,6 @@ class BackgroundService {
AppSettingsService settingsService = AppSettingsService(); AppSettingsService settingsService = AppSettingsService();
final selectedAlbums = backupService.selectedAlbumsQuery().findAllSync(); final selectedAlbums = backupService.selectedAlbumsQuery().findAllSync();
final excludedAlbums = backupService.excludedAlbumsQuery().findAllSync();
if (selectedAlbums.isEmpty) { if (selectedAlbums.isEmpty) {
return true; return true;
} }
@ -361,11 +360,10 @@ class BackgroundService {
backupService, backupService,
settingsService, settingsService,
selectedAlbums, selectedAlbums,
excludedAlbums,
); );
if (backupOk) { if (backupOk) {
await Store.delete(StoreKey.backupFailedSince); await Store.delete(StoreKey.backupFailedSince);
final backupAlbums = [...selectedAlbums, ...excludedAlbums]; final backupAlbums = [...selectedAlbums];
backupAlbums.sortBy((e) => e.id); backupAlbums.sortBy((e) => e.id);
db.writeTxnSync(() { db.writeTxnSync(() {
final dbAlbums = db.backupAlbums.where().sortById().findAllSync(); final dbAlbums = db.backupAlbums.where().sortById().findAllSync();
@ -404,7 +402,6 @@ class BackgroundService {
BackupService backupService, BackupService backupService,
AppSettingsService settingsService, AppSettingsService settingsService,
List<BackupAlbum> selectedAlbums, List<BackupAlbum> selectedAlbums,
List<BackupAlbum> excludedAlbums,
) async { ) async {
_errorGracePeriodExceeded = _isErrorGracePeriodExceeded(settingsService); _errorGracePeriodExceeded = _isErrorGracePeriodExceeded(settingsService);
final bool notifyTotalProgress = settingsService final bool notifyTotalProgress = settingsService
@ -418,7 +415,6 @@ class BackgroundService {
List<AssetEntity> toUpload = await backupService.buildUploadCandidates( List<AssetEntity> toUpload = await backupService.buildUploadCandidates(
selectedAlbums, selectedAlbums,
excludedAlbums,
); );
try { try {

View File

@ -65,14 +65,10 @@ class BackupService {
QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition>
selectedAlbumsQuery() => selectedAlbumsQuery() =>
_db.backupAlbums.filter().selectionEqualTo(BackupSelection.select); _db.backupAlbums.filter().selectionEqualTo(BackupSelection.select);
QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition>
excludedAlbumsQuery() =>
_db.backupAlbums.filter().selectionEqualTo(BackupSelection.exclude);
/// Returns all assets newer than the last successful backup per album /// Returns all assets newer than the last successful backup per album
Future<List<AssetEntity>> buildUploadCandidates( Future<List<AssetEntity>> buildUploadCandidates(
List<BackupAlbum> selectedBackupAlbums, List<BackupAlbum> selectedBackupAlbums,
List<BackupAlbum> excludedBackupAlbums,
) async { ) async {
final filter = FilterOptionGroup( final filter = FilterOptionGroup(
containsPathModified: true, containsPathModified: true,
@ -89,19 +85,13 @@ class BackupService {
} }
final int allIdx = selectedAlbums.indexWhere((e) => e != null && e.isAll); final int allIdx = selectedAlbums.indexWhere((e) => e != null && e.isAll);
if (allIdx != -1) { if (allIdx != -1) {
final List<AssetPathEntity?> excludedAlbums =
await _loadAlbumsWithTimeFilter(excludedBackupAlbums, filter, now);
final List<AssetEntity> toAdd = await _fetchAssetsAndUpdateLastBackup( final List<AssetEntity> toAdd = await _fetchAssetsAndUpdateLastBackup(
selectedAlbums.slice(allIdx, allIdx + 1), selectedAlbums.slice(allIdx, allIdx + 1),
selectedBackupAlbums.slice(allIdx, allIdx + 1), selectedBackupAlbums.slice(allIdx, allIdx + 1),
now, now,
); );
final List<AssetEntity> toRemove = await _fetchAssetsAndUpdateLastBackup(
excludedAlbums, return toAdd.toSet().toList();
excludedBackupAlbums,
now,
);
return toAdd.toSet().difference(toRemove.toSet()).toList();
} else { } else {
return await _fetchAssetsAndUpdateLastBackup( return await _fetchAssetsAndUpdateLastBackup(
selectedAlbums, selectedAlbums,

View File

@ -1,14 +1,12 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/backup/available_album.model.dart'; import 'package:immich_mobile/models/backup/available_album.model.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class AlbumInfoCard extends HookConsumerWidget { class AlbumInfoCard extends HookConsumerWidget {
final AvailableAlbum album; final AvailableAlbum album;
@ -19,8 +17,6 @@ class AlbumInfoCard extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final bool isSelected = final bool isSelected =
ref.watch(backupProvider).selectedBackupAlbums.contains(album); ref.watch(backupProvider).selectedBackupAlbums.contains(album);
final bool isExcluded =
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
final isDarkTheme = context.isDarkTheme; final isDarkTheme = context.isDarkTheme;
@ -28,8 +24,7 @@ class AlbumInfoCard extends HookConsumerWidget {
context.primaryColor.withAlpha(100), context.primaryColor.withAlpha(100),
BlendMode.darken, BlendMode.darken,
); );
ColorFilter excludedFilter =
ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken);
ColorFilter unselectedFilter = ColorFilter unselectedFilter =
const ColorFilter.mode(Colors.black, BlendMode.color); const ColorFilter.mode(Colors.black, BlendMode.color);
@ -48,20 +43,6 @@ class AlbumInfoCard extends HookConsumerWidget {
).tr(), ).tr(),
backgroundColor: context.primaryColor, backgroundColor: context.primaryColor,
); );
} else if (isExcluded) {
return Chip(
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
label: Text(
"album_info_card_backup_album_excluded",
style: TextStyle(
fontSize: 10,
color: isDarkTheme ? Colors.black : Colors.white,
fontWeight: FontWeight.bold,
),
).tr(),
backgroundColor: Colors.red[300],
);
} }
return const SizedBox(); return const SizedBox();
@ -70,8 +51,6 @@ class AlbumInfoCard extends HookConsumerWidget {
buildImageFilter() { buildImageFilter() {
if (isSelected) { if (isSelected) {
return selectedFilter; return selectedFilter;
} else if (isExcluded) {
return excludedFilter;
} else { } else {
return unselectedFilter; return unselectedFilter;
} }
@ -87,28 +66,6 @@ class AlbumInfoCard extends HookConsumerWidget {
ref.read(backupProvider.notifier).addAlbumForBackup(album); ref.read(backupProvider.notifier).addAlbumForBackup(album);
} }
}, },
onDoubleTap: () {
ref.read(hapticFeedbackProvider.notifier).selectionClick();
if (isExcluded) {
// Remove from exclude album list
ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album);
} else {
// Add to exclude album list
if (album.id == 'isAll' || album.name == 'Recents') {
ImmichToast.show(
context: context,
msg: 'Cannot exclude album contains all assets',
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
return;
}
ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album);
}
},
child: Card( child: Card(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
margin: const EdgeInsets.all(1), margin: const EdgeInsets.all(1),

View File

@ -1,14 +1,12 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/backup/available_album.model.dart'; import 'package:immich_mobile/models/backup/available_album.model.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class AlbumInfoListTile extends HookConsumerWidget { class AlbumInfoListTile extends HookConsumerWidget {
final AvailableAlbum album; final AvailableAlbum album;
@ -19,8 +17,6 @@ class AlbumInfoListTile extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final bool isSelected = final bool isSelected =
ref.watch(backupProvider).selectedBackupAlbums.contains(album); ref.watch(backupProvider).selectedBackupAlbums.contains(album);
final bool isExcluded =
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
var assetCount = useState(0); var assetCount = useState(0);
useEffect( useEffect(
@ -36,10 +32,6 @@ class AlbumInfoListTile extends HookConsumerWidget {
return context.isDarkTheme return context.isDarkTheme
? context.primaryColor.withAlpha(100) ? context.primaryColor.withAlpha(100)
: context.primaryColor.withAlpha(25); : context.primaryColor.withAlpha(25);
} else if (isExcluded) {
return context.isDarkTheme
? Colors.red[300]?.withAlpha(150)
: Colors.red[100]?.withAlpha(150);
} else { } else {
return Colors.transparent; return Colors.transparent;
} }
@ -53,13 +45,6 @@ class AlbumInfoListTile extends HookConsumerWidget {
); );
} }
if (isExcluded) {
return const Icon(
Icons.remove_circle_rounded,
color: Colors.red,
);
}
return Icon( return Icon(
Icons.circle, Icons.circle,
color: context.isDarkTheme ? Colors.grey[400] : Colors.black45, color: context.isDarkTheme ? Colors.grey[400] : Colors.black45,
@ -67,28 +52,6 @@ class AlbumInfoListTile extends HookConsumerWidget {
} }
return GestureDetector( return GestureDetector(
onDoubleTap: () {
ref.watch(hapticFeedbackProvider.notifier).selectionClick();
if (isExcluded) {
// Remove from exclude album list
ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album);
} else {
// Add to exclude album list
if (album.id == 'isAll' || album.name == 'Recents') {
ImmichToast.show(
context: context,
msg: 'Cannot exclude album contains all assets',
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
return;
}
ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album);
}
},
child: ListTile( child: ListTile(
tileColor: buildTileColor(), tileColor: buildTileColor(),
contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),