Merge branch 'db_and_services' of github.com:ente-io/photos-app into db_and_services
This commit is contained in:
commit
628967e999
5 changed files with 171 additions and 22 deletions
|
@ -395,6 +395,29 @@ class CollectionsService {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> trashEmptyCollection(Collection collection) async {
|
||||
try {
|
||||
// While trashing empty albums, we must pass keepFiles flag as True.
|
||||
// The server will verify that the collection is actually empty before
|
||||
// deleting the files. If keepFiles is set as False and the collection
|
||||
// is not empty, then the files in the collections will be moved to trash.
|
||||
await _enteDio.delete(
|
||||
"/collections/v3/${collection.id}?keepFiles=True&collectionID=${collection.id}",
|
||||
);
|
||||
final deletedCollection = collection.copyWith(isDeleted: true);
|
||||
_collectionIDToCollections[collection.id] = deletedCollection;
|
||||
unawaited(_db.insert([deletedCollection]));
|
||||
} on DioError catch (e) {
|
||||
if (e.response != null) {
|
||||
debugPrint("Error " + e.response!.toString());
|
||||
}
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
_logger.severe('failed to trash empty collection', e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleCollectionDeletion(Collection collection) async {
|
||||
await _filesDB.deleteCollection(collection.id);
|
||||
final deletedCollection = collection.copyWith(isDeleted: true);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
@ -22,6 +20,7 @@ import 'package:photos/ui/collections/remote_collections_grid_view_widget.dart';
|
|||
import 'package:photos/ui/collections/section_title.dart';
|
||||
import 'package:photos/ui/collections/trash_button_widget.dart';
|
||||
import 'package:photos/ui/common/loading_widget.dart';
|
||||
import 'package:photos/ui/viewer/actions/delete_empty_albums.dart';
|
||||
import 'package:photos/ui/viewer/gallery/empty_state.dart';
|
||||
import 'package:photos/utils/local_settings.dart';
|
||||
|
||||
|
@ -37,7 +36,8 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
|
|||
with AutomaticKeepAliveClientMixin {
|
||||
final _logger = Logger((_CollectionsGalleryWidgetState).toString());
|
||||
late StreamSubscription<LocalPhotosUpdatedEvent> _localFilesSubscription;
|
||||
late StreamSubscription<CollectionUpdatedEvent> _collectionUpdatesSubscription;
|
||||
late StreamSubscription<CollectionUpdatedEvent>
|
||||
_collectionUpdatesSubscription;
|
||||
late StreamSubscription<UserLoggedOutEvent> _loggedOutEvent;
|
||||
AlbumSortKey? sortKey;
|
||||
String _loadReason = "init";
|
||||
|
@ -123,6 +123,8 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
|
|||
Widget _getCollectionsGalleryWidget(
|
||||
List<CollectionWithThumbnail>? collections,
|
||||
) {
|
||||
final bool showDeleteAlbumsButton =
|
||||
collections!.where((c) => c.thumbnail == null).length >= 3;
|
||||
final TextStyle trashAndHiddenTextStyle = Theme.of(context)
|
||||
.textTheme
|
||||
.subtitle1!
|
||||
|
@ -149,6 +151,12 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
|
|||
_sortMenu(),
|
||||
],
|
||||
),
|
||||
showDeleteAlbumsButton
|
||||
? const Padding(
|
||||
padding: EdgeInsets.only(top: 2, left: 8.5, right: 48),
|
||||
child: DeleteEmptyAlbums(),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
const SizedBox(height: 12),
|
||||
Configuration.instance.hasConfiguredAccount()
|
||||
? RemoteCollectionsGridViewWidget(collections)
|
||||
|
|
|
@ -60,6 +60,11 @@ class ButtonWidget extends StatelessWidget {
|
|||
///This should be set to true if the alert which uses this button needs to
|
||||
///return the Button's action.
|
||||
final bool isInAlert;
|
||||
|
||||
/// progressStatus can be used to display information about the action
|
||||
/// progress when ExecutionState is in Progress.
|
||||
final ValueNotifier<String>? progressStatus;
|
||||
|
||||
const ButtonWidget({
|
||||
required this.buttonType,
|
||||
this.buttonSize = ButtonSize.large,
|
||||
|
@ -72,6 +77,7 @@ class ButtonWidget extends StatelessWidget {
|
|||
this.isInAlert = false,
|
||||
this.iconColor,
|
||||
this.shouldSurfaceExecutionStates = true,
|
||||
this.progressStatus,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -137,6 +143,7 @@ class ButtonWidget extends StatelessWidget {
|
|||
icon: icon,
|
||||
buttonAction: buttonAction,
|
||||
shouldSurfaceExecutionStates: shouldSurfaceExecutionStates,
|
||||
progressStatus: progressStatus,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -152,6 +159,8 @@ class ButtonChildWidget extends StatefulWidget {
|
|||
final ButtonAction? buttonAction;
|
||||
final bool isInAlert;
|
||||
final bool shouldSurfaceExecutionStates;
|
||||
final ValueNotifier<String>? progressStatus;
|
||||
|
||||
const ButtonChildWidget({
|
||||
required this.buttonStyle,
|
||||
required this.buttonType,
|
||||
|
@ -159,6 +168,7 @@ class ButtonChildWidget extends StatefulWidget {
|
|||
required this.buttonSize,
|
||||
required this.isInAlert,
|
||||
required this.shouldSurfaceExecutionStates,
|
||||
this.progressStatus,
|
||||
this.onTap,
|
||||
this.labelText,
|
||||
this.icon,
|
||||
|
@ -177,14 +187,17 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
|
|||
late TextStyle labelStyle;
|
||||
late Color checkIconColor;
|
||||
late Color loadingIconColor;
|
||||
ValueNotifier<String>? progressStatus;
|
||||
|
||||
///This is used to store the width of the button in idle state (small button)
|
||||
///to be used as width for the button when the loading/succes states comes.
|
||||
double? widthOfButton;
|
||||
final _debouncer = Debouncer(const Duration(milliseconds: 300));
|
||||
ExecutionState executionState = ExecutionState.idle;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
progressStatus = widget.progressStatus;
|
||||
checkIconColor = widget.buttonStyle.checkIconColor ??
|
||||
widget.buttonStyle.defaultIconColor;
|
||||
loadingIconColor = widget.buttonStyle.defaultIconColor;
|
||||
|
@ -203,6 +216,7 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
|
|||
iconColor = widget.buttonStyle.defaultIconColor;
|
||||
labelStyle = widget.buttonStyle.defaultLabelStyle;
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -315,6 +329,21 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
progressStatus == null
|
||||
? const SizedBox.shrink()
|
||||
: ValueListenableBuilder<String>(
|
||||
valueListenable: progressStatus!,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
String value,
|
||||
Widget? child,
|
||||
) {
|
||||
return Text(
|
||||
value,
|
||||
style: lightTextTheme.smallBold,
|
||||
);
|
||||
},
|
||||
),
|
||||
EnteLoadingWidget(
|
||||
is20pts: true,
|
||||
color: loadingIconColor,
|
||||
|
|
108
lib/ui/viewer/actions/delete_empty_albums.dart
Normal file
108
lib/ui/viewer/actions/delete_empty_albums.dart
Normal file
|
@ -0,0 +1,108 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/events/collection_updated_event.dart';
|
||||
import 'package:photos/models/file.dart';
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import 'package:photos/ui/components/action_sheet_widget.dart';
|
||||
import 'package:photos/ui/components/button_widget.dart';
|
||||
import 'package:photos/ui/components/models/button_type.dart';
|
||||
|
||||
class DeleteEmptyAlbums extends StatefulWidget {
|
||||
const DeleteEmptyAlbums({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DeleteEmptyAlbums> createState() => _DeleteEmptyAlbumsState();
|
||||
}
|
||||
|
||||
class _DeleteEmptyAlbumsState extends State<DeleteEmptyAlbums> {
|
||||
final ValueNotifier<String> _deleteProgress = ValueNotifier("");
|
||||
bool _isCancelled = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_deleteProgress.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: ButtonWidget(
|
||||
buttonSize: ButtonSize.small,
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: "Delete empty albums",
|
||||
icon: Icons.delete_sweep_outlined,
|
||||
shouldSurfaceExecutionStates: false,
|
||||
onTap: () async {
|
||||
await showActionSheet(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
labelText: "Yes",
|
||||
buttonType: ButtonType.neutral,
|
||||
buttonSize: ButtonSize.large,
|
||||
shouldStickToDarkTheme: true,
|
||||
shouldSurfaceExecutionStates: true,
|
||||
progressStatus: _deleteProgress,
|
||||
onTap: () async {
|
||||
await _deleteEmptyAlbums();
|
||||
if (!_isCancelled) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
}
|
||||
Bus.instance.fire(
|
||||
CollectionUpdatedEvent(
|
||||
0,
|
||||
<File>[],
|
||||
"empty_albums_deleted",
|
||||
),
|
||||
);
|
||||
CollectionsService.instance.sync().ignore();
|
||||
_isCancelled = false;
|
||||
},
|
||||
),
|
||||
ButtonWidget(
|
||||
labelText: "Cancel",
|
||||
buttonType: ButtonType.secondary,
|
||||
buttonSize: ButtonSize.large,
|
||||
shouldStickToDarkTheme: true,
|
||||
onTap: () async {
|
||||
_isCancelled = true;
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
title: "Delete empty albums",
|
||||
body:
|
||||
"This will delete all empty albums. This is useful when you want to reduce the clutter in your album list.",
|
||||
actionSheetType: ActionSheetType.defaultActionSheet,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _deleteEmptyAlbums() async {
|
||||
final collections =
|
||||
await CollectionsService.instance.getCollectionsWithThumbnails();
|
||||
collections.removeWhere((element) => element.thumbnail != null);
|
||||
int failedCount = 0;
|
||||
for (int i = 0; i < collections.length; i++) {
|
||||
if (mounted && !_isCancelled) {
|
||||
_deleteProgress.value =
|
||||
"Deleting ${(i + 1).toString().padLeft(collections.length.toString().length, '0')}/ "
|
||||
"${collections.length} ";
|
||||
try {
|
||||
await CollectionsService.instance
|
||||
.trashEmptyCollection(collections[i].collection);
|
||||
} catch (_) {
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failedCount > 0) {
|
||||
debugPrint("Delete ops failed for $failedCount collections");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:photos/models/gallery_type.dart';
|
||||
import 'package:photos/models/selected_files.dart';
|
||||
|
||||
class FileSelectionCommonActionWidget extends StatelessWidget {
|
||||
final GalleryType type;
|
||||
final SelectedFiles selectedFiles;
|
||||
|
||||
const FileSelectionCommonActionWidget({
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.selectedFiles,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO: implement build
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue