chore: refactor

This commit is contained in:
Alex
2026-01-23 10:36:30 -06:00
parent 327b784c86
commit 00374f2d6f
7 changed files with 59 additions and 82 deletions

View File

@@ -87,7 +87,7 @@ enum StoreKey<T> {
// Free up space
cleanupKeepFavorites<bool>._(1008),
cleanupKeepMediaType<int>._(1009),
cleanupExcludedAlbumIds<String>._(1010),
cleanupKeepAlbumIds<String>._(1010),
cleanupCutoffDaysAgo<int>._(1011);
const StoreKey._(this.id);

View File

@@ -135,7 +135,7 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
DateTime cutoffDate, {
AssetKeepType keepMediaType = AssetKeepType.none,
bool keepFavorites = true,
Set<String> excludedAlbumIds = const {},
Set<String> keepAlbumIds = const {},
}) async {
final iosSharedAlbumAssets = _db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId])
@@ -160,14 +160,13 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
// Exclude assets that are in iOS shared albums
whereClause = whereClause & _db.localAssetEntity.id.isNotInQuery(iosSharedAlbumAssets);
if (excludedAlbumIds.isNotEmpty) {
final excludedAlbumAssets = _db.localAlbumAssetEntity.selectOnly()
if (keepAlbumIds.isNotEmpty) {
final keepAlbumAssets = _db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId])
..where(_db.localAlbumAssetEntity.albumId.isIn(excludedAlbumIds));
whereClause = whereClause & _db.localAssetEntity.id.isNotInQuery(excludedAlbumAssets);
..where(_db.localAlbumAssetEntity.albumId.isIn(keepAlbumIds));
whereClause = whereClause & _db.localAssetEntity.id.isNotInQuery(keepAlbumAssets);
}
// keepMediaType specifies what to KEEP, so we filter to DELETE the opposite
if (keepMediaType == AssetKeepType.photosOnly) {
// Keep photos = delete only videos
whereClause = whereClause & _db.localAssetEntity.type.equalsValue(AssetType.video);

View File

@@ -13,7 +13,7 @@ class CleanupState {
final bool isDeleting;
final AssetKeepType keepMediaType;
final bool keepFavorites;
final Set<String> excludedAlbumIds;
final Set<String> keepAlbumIds;
const CleanupState({
this.selectedDate,
@@ -22,7 +22,7 @@ class CleanupState {
this.isDeleting = false,
this.keepMediaType = AssetKeepType.none,
this.keepFavorites = true,
this.excludedAlbumIds = const {},
this.keepAlbumIds = const {},
});
CleanupState copyWith({
@@ -32,7 +32,7 @@ class CleanupState {
bool? isDeleting,
AssetKeepType? keepMediaType,
bool? keepFavorites,
Set<String>? excludedAlbumIds,
Set<String>? keepAlbumIds,
}) {
return CleanupState(
selectedDate: selectedDate ?? this.selectedDate,
@@ -41,7 +41,7 @@ class CleanupState {
isDeleting: isDeleting ?? this.isDeleting,
keepMediaType: keepMediaType ?? this.keepMediaType,
keepFavorites: keepFavorites ?? this.keepFavorites,
excludedAlbumIds: excludedAlbumIds ?? this.excludedAlbumIds,
keepAlbumIds: keepAlbumIds ?? this.keepAlbumIds,
);
}
}
@@ -66,17 +66,17 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
void _loadPersistedSettings() {
final keepFavorites = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepFavorites);
final keepMediaTypeIndex = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepMediaType);
final excludedAlbumIdsString = _appSettingsService.getSetting(AppSettingsEnum.cleanupExcludedAlbumIds);
final keepAlbumIdsString = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepAlbumIds);
final cutoffDaysAgo = _appSettingsService.getSetting(AppSettingsEnum.cleanupCutoffDaysAgo);
final keepMediaType = AssetKeepType.values[keepMediaTypeIndex.clamp(0, AssetKeepType.values.length - 1)];
final excludedAlbumIds = excludedAlbumIdsString.isEmpty ? <String>{} : excludedAlbumIdsString.split(',').toSet();
final keepAlbumIds = keepAlbumIdsString.isEmpty ? <String>{} : keepAlbumIdsString.split(',').toSet();
final selectedDate = cutoffDaysAgo > 0 ? DateTime.now().subtract(Duration(days: cutoffDaysAgo)) : null;
state = state.copyWith(
keepFavorites: keepFavorites,
keepMediaType: keepMediaType,
excludedAlbumIds: excludedAlbumIds,
keepAlbumIds: keepAlbumIds,
selectedDate: selectedDate,
);
}
@@ -99,24 +99,24 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
_appSettingsService.setSetting(AppSettingsEnum.cleanupKeepFavorites, keepFavorites);
}
void toggleExcludedAlbum(String albumId) {
final newExcludedAlbumIds = Set<String>.from(state.excludedAlbumIds);
if (newExcludedAlbumIds.contains(albumId)) {
newExcludedAlbumIds.remove(albumId);
void toggleKeepAlbum(String albumId) {
final newKeepAlbumIds = Set<String>.from(state.keepAlbumIds);
if (newKeepAlbumIds.contains(albumId)) {
newKeepAlbumIds.remove(albumId);
} else {
newExcludedAlbumIds.add(albumId);
newKeepAlbumIds.add(albumId);
}
state = state.copyWith(excludedAlbumIds: newExcludedAlbumIds, assetsToDelete: []);
_persistExcludedAlbumIds(newExcludedAlbumIds);
state = state.copyWith(keepAlbumIds: newKeepAlbumIds, assetsToDelete: []);
_persistExcludedAlbumIds(newKeepAlbumIds);
}
void setExcludedAlbumIds(Set<String> albumIds) {
state = state.copyWith(excludedAlbumIds: albumIds, assetsToDelete: []);
state = state.copyWith(keepAlbumIds: albumIds, assetsToDelete: []);
_persistExcludedAlbumIds(albumIds);
}
void _persistExcludedAlbumIds(Set<String> albumIds) {
_appSettingsService.setSetting(AppSettingsEnum.cleanupExcludedAlbumIds, albumIds.join(','));
_appSettingsService.setSetting(AppSettingsEnum.cleanupKeepAlbumIds, albumIds.join(','));
}
Future<void> scanAssets() async {
@@ -131,7 +131,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
state.selectedDate!,
keepMediaType: state.keepMediaType,
keepFavorites: state.keepFavorites,
excludedAlbumIds: state.excludedAlbumIds,
keepAlbumIds: state.keepAlbumIds,
);
state = state.copyWith(assetsToDelete: assets, isScanning: false);
} catch (e) {

View File

@@ -57,7 +57,7 @@ enum AppSettingsEnum<T> {
backupTriggerDelay<int>(StoreKey.backupTriggerDelay, null, 30),
cleanupKeepFavorites<bool>(StoreKey.cleanupKeepFavorites, null, true),
cleanupKeepMediaType<int>(StoreKey.cleanupKeepMediaType, null, 0),
cleanupExcludedAlbumIds<String>(StoreKey.cleanupExcludedAlbumIds, null, ""),
cleanupKeepAlbumIds<String>(StoreKey.cleanupKeepAlbumIds, null, ""),
cleanupCutoffDaysAgo<int>(StoreKey.cleanupCutoffDaysAgo, null, 60);
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);

View File

@@ -20,14 +20,14 @@ class CleanupService {
DateTime cutoffDate, {
AssetKeepType keepMediaType = AssetKeepType.none,
bool keepFavorites = true,
Set<String> excludedAlbumIds = const {},
Set<String> keepAlbumIds = const {},
}) {
return _localAssetRepository.getRemovalCandidates(
userId,
cutoffDate,
keepMediaType: keepMediaType,
keepFavorites: keepFavorites,
excludedAlbumIds: excludedAlbumIds,
keepAlbumIds: keepAlbumIds,
);
}

View File

@@ -150,6 +150,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
final subtitleStyle = context.textTheme.bodyMedium!.copyWith(
color: context.textTheme.bodyMedium!.color!.withAlpha(215),
);
StepStyle styleForState(StepState stepState, {bool isDestructive = false}) {
switch (stepState) {
case StepState.complete:
@@ -187,7 +188,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
final step3State = hasAssets ? StepState.indexed : StepState.disabled;
final hasKeepSettings =
state.keepFavorites || state.excludedAlbumIds.isNotEmpty || state.keepMediaType != AssetKeepType.none;
state.keepFavorites || state.keepAlbumIds.isNotEmpty || state.keepMediaType != AssetKeepType.none;
String getKeepSettingsSummary() {
final parts = <String>[];
@@ -202,10 +203,8 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
parts.add('favorites'.t(context: context).toLowerCase());
}
if (state.excludedAlbumIds.isNotEmpty) {
parts.add(
'excluded_albums_count'.t(context: context, args: {'count': state.excludedAlbumIds.length.toString()}),
);
if (state.keepAlbumIds.isNotEmpty) {
parts.add('keep_albums_count'.t(context: context, args: {'count': state.keepAlbumIds.length.toString()}));
}
if (parts.isEmpty) {
@@ -286,20 +285,15 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('cleanup_filter_description'.t(context: context), style: subtitleStyle),
const SizedBox(height: 16),
Text('keep_description'.t(context: context), style: subtitleStyle),
const SizedBox(height: 4),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(
'keep_favorites'.t(context: context),
style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5),
),
subtitle: Text(
'keep_favorites_description'.t(context: context),
style: context.textTheme.bodyMedium!.copyWith(
color: context.textTheme.bodyMedium!.color!.withAlpha(215),
),
),
value: state.keepFavorites,
onChanged: (value) {
ref.read(cleanupProvider.notifier).setKeepFavorites(value);
@@ -307,16 +301,16 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
},
),
const SizedBox(height: 8),
_ExcludedAlbumsSection(
excludedAlbumIds: state.excludedAlbumIds,
_KeepAlbumsSection(
albumIds: state.keepAlbumIds,
onAlbumToggled: (albumId) {
ref.read(cleanupProvider.notifier).toggleExcludedAlbum(albumId);
ref.read(cleanupProvider.notifier).toggleKeepAlbum(albumId);
_onKeepSettingsChanged();
},
),
const SizedBox(height: 16),
Text(
'cleanup_keep_all_media_type'.t(context: context),
'always_keep'.t(context: context),
style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5),
),
const SizedBox(height: 4),
@@ -748,11 +742,11 @@ class _DatePresetCard extends StatelessWidget {
}
}
class _ExcludedAlbumsSection extends ConsumerWidget {
final Set<String> excludedAlbumIds;
class _KeepAlbumsSection extends ConsumerWidget {
final Set<String> albumIds;
final ValueChanged<String> onAlbumToggled;
const _ExcludedAlbumsSection({required this.excludedAlbumIds, required this.onAlbumToggled});
const _KeepAlbumsSection({required this.albumIds, required this.onAlbumToggled});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -762,15 +756,11 @@ class _ExcludedAlbumsSection extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'exclude_albums'.t(context: context),
'keep_albums'.t(context: context),
style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5),
),
const SizedBox(height: 4),
Text(
'exclude_albums_description'.t(context: context),
style: context.textTheme.bodyMedium!.copyWith(color: context.textTheme.bodyMedium!.color!.withAlpha(215)),
),
const SizedBox(height: 12),
const SizedBox(height: 8),
albumsAsync.when(
loading: () => const Center(
child: Padding(padding: EdgeInsets.all(16.0), child: CircularProgressIndicator(strokeWidth: 2)),
@@ -801,22 +791,18 @@ class _ExcludedAlbumsSection extends ConsumerWidget {
itemCount: albums.length,
itemBuilder: (context, index) {
final album = albums[index];
final isExcluded = excludedAlbumIds.contains(album.id);
return _AlbumExclusionTile(
album: album,
isExcluded: isExcluded,
onToggle: () => onAlbumToggled(album.id),
);
final isSelected = albumIds.contains(album.id);
return _AlbumTile(album: album, isSelected: isSelected, onToggle: () => onAlbumToggled(album.id));
},
),
),
);
},
),
if (excludedAlbumIds.isNotEmpty) ...[
if (albumIds.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
'excluded_albums_count'.t(context: context, args: {'count': excludedAlbumIds.length.toString()}),
'keep_albums_count'.t(context: context, args: {'count': albumIds.length.toString()}),
style: context.textTheme.bodySmall?.copyWith(
color: context.colorScheme.primary,
fontWeight: FontWeight.w500,
@@ -828,12 +814,12 @@ class _ExcludedAlbumsSection extends ConsumerWidget {
}
}
class _AlbumExclusionTile extends StatelessWidget {
class _AlbumTile extends StatelessWidget {
final LocalAlbum album;
final bool isExcluded;
final bool isSelected;
final VoidCallback onToggle;
const _AlbumExclusionTile({required this.album, required this.isExcluded, required this.onToggle});
const _AlbumTile({required this.album, required this.isSelected, required this.onToggle});
@override
Widget build(BuildContext context) {
@@ -841,13 +827,13 @@ class _AlbumExclusionTile extends StatelessWidget {
dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
leading: Icon(
isExcluded ? Icons.check_circle : Icons.circle_outlined,
color: isExcluded ? context.colorScheme.primary : context.colorScheme.onSurfaceVariant,
isSelected ? Icons.check_circle : Icons.circle_outlined,
color: isSelected ? context.colorScheme.primary : context.colorScheme.onSurfaceVariant,
size: 20,
),
title: Text(
album.name,
style: context.textTheme.bodyMedium?.copyWith(color: isExcluded ? context.colorScheme.primary : null),
style: context.textTheme.bodyMedium?.copyWith(color: isSelected ? context.colorScheme.primary : null),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),

View File

@@ -462,7 +462,7 @@ void main() {
await insertRemoteAsset(id: 'remote-excluded', checksum: 'checksum-excluded', ownerId: userId);
await insertLocalAlbumAsset(albumId: 'album-exclude', assetId: 'local-in-excluded');
final candidates = await repository.getRemovalCandidates(userId, cutoffDate, excludedAlbumIds: {'album-exclude'});
final candidates = await repository.getRemovalCandidates(userId, cutoffDate, keepAlbumIds: {'album-exclude'});
expect(candidates.length, 1);
expect(candidates[0].id, 'local-in-included');
@@ -510,7 +510,7 @@ void main() {
final candidates = await repository.getRemovalCandidates(
userId,
cutoffDate,
excludedAlbumIds: {'album-1', 'album-2'},
keepAlbumIds: {'album-1', 'album-2'},
);
expect(candidates.length, 1);
@@ -533,11 +533,7 @@ void main() {
await insertLocalAlbumAsset(albumId: 'album-included', assetId: 'local-both');
await insertLocalAlbumAsset(albumId: 'album-excluded', assetId: 'local-both');
final candidates = await repository.getRemovalCandidates(
userId,
cutoffDate,
excludedAlbumIds: {'album-excluded'},
);
final candidates = await repository.getRemovalCandidates(userId, cutoffDate, keepAlbumIds: {'album-excluded'});
expect(candidates, isEmpty);
});
@@ -565,7 +561,7 @@ void main() {
await insertRemoteAsset(id: 'remote-2', checksum: 'checksum-2', ownerId: userId);
// Empty excludedAlbumIds should include all eligible assets
final candidates = await repository.getRemovalCandidates(userId, cutoffDate, excludedAlbumIds: {});
final candidates = await repository.getRemovalCandidates(userId, cutoffDate, keepAlbumIds: {});
expect(candidates.length, 2);
});
@@ -594,11 +590,7 @@ void main() {
await insertRemoteAsset(id: 'remote-in-excluded', checksum: 'checksum-in-excluded', ownerId: userId);
await insertLocalAlbumAsset(albumId: 'album-excluded', assetId: 'local-in-excluded');
final candidates = await repository.getRemovalCandidates(
userId,
cutoffDate,
excludedAlbumIds: {'album-excluded'},
);
final candidates = await repository.getRemovalCandidates(userId, cutoffDate, keepAlbumIds: {'album-excluded'});
expect(candidates.length, 1);
expect(candidates[0].id, 'local-no-album');
@@ -645,7 +637,7 @@ void main() {
userId,
cutoffDate,
keepMediaType: AssetKeepType.photosOnly,
excludedAlbumIds: {'album-excluded'},
keepAlbumIds: {'album-excluded'},
);
expect(candidates.length, 1);