Merge branch 'db_and_services' of github.com:ente-io/photos-app into db_and_services

This commit is contained in:
Neeraj Gupta 2023-01-03 20:25:12 +05:30
commit 628967e999
No known key found for this signature in database
GPG key ID: 3C5A1684DC1729E1
5 changed files with 171 additions and 22 deletions

View file

@ -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);

View file

@ -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)

View file

@ -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,

View 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");
}
}
}

View file

@ -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();
}
}