ente/lib/ui/collections/album/vertical_list.dart

446 lines
13 KiB
Dart
Raw Normal View History

import "dart:async";
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import "package:photos/core/event_bus.dart";
import "package:photos/events/tab_changed_event.dart";
2023-04-07 04:27:53 +00:00
import "package:photos/generated/l10n.dart";
2023-08-25 04:39:30 +00:00
import 'package:photos/models/collection/collection.dart';
import 'package:photos/models/collection/collection_items.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/services/collections_service.dart';
import "package:photos/services/hidden_service.dart";
import 'package:photos/services/remote_sync_service.dart';
2023-08-26 10:25:52 +00:00
import "package:photos/ui/actions/collection/collection_file_actions.dart";
import "package:photos/ui/actions/collection/collection_sharing_actions.dart";
2023-06-06 16:43:32 +00:00
import "package:photos/ui/collections/album/column_item.dart";
import "package:photos/ui/collections/album/new_list_item.dart";
2023-06-06 09:57:17 +00:00
import 'package:photos/ui/collections/collection_action_sheet.dart';
import "package:photos/ui/sharing/share_collection_page.dart";
import 'package:photos/ui/viewer/gallery/collection_page.dart';
import "package:photos/ui/viewer/gallery/empty_state.dart";
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/navigation_util.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
2023-06-06 16:43:32 +00:00
class AlbumVerticalListWidget extends StatelessWidget {
final List<Collection> collections;
final CollectionActionType actionType;
final SelectedFiles? selectedFiles;
final List<SharedMediaFile>? sharedFiles;
final String searchQuery;
final bool shouldShowCreateAlbum;
2023-06-06 16:43:32 +00:00
AlbumVerticalListWidget(
this.collections,
this.actionType,
this.selectedFiles,
this.sharedFiles,
this.searchQuery,
this.shouldShowCreateAlbum, {
Key? key,
}) : super(key: key);
2023-03-07 14:10:54 +00:00
final _logger = Logger("CollectionsListWidgetState");
2023-08-28 15:58:34 +00:00
final CollectionActions _collectionActions =
CollectionActions(CollectionsService.instance);
@override
Widget build(BuildContext context) {
2023-03-08 02:16:12 +00:00
final filesCount = sharedFiles != null
? sharedFiles!.length
: selectedFiles?.files.length ?? 0;
if (collections.isEmpty) {
if (shouldShowCreateAlbum) {
return _getNewAlbumWidget(context, filesCount);
}
return const EmptyState();
}
return ListView.separated(
itemBuilder: (context, index) {
2023-03-08 02:16:12 +00:00
if (index == 0 && shouldShowCreateAlbum) {
return _getNewAlbumWidget(context, filesCount);
}
final item = collections[index - (shouldShowCreateAlbum ? 1 : 0)];
return GestureDetector(
behavior: HitTestBehavior.opaque,
2023-03-08 02:16:12 +00:00
onTap: () => _albumListItemOnTap(context, item),
2023-06-06 16:43:32 +00:00
child: AlbumColumnItemWidget(
item,
),
);
},
separatorBuilder: (context, index) => const SizedBox(
height: 8,
),
itemCount: collections.length + (shouldShowCreateAlbum ? 1 : 0),
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
);
}
GestureDetector _getNewAlbumWidget(BuildContext context, int filesCount) {
return GestureDetector(
onTap: () async {
await _createNewAlbumOnTap(context, filesCount);
},
behavior: HitTestBehavior.opaque,
child: const NewAlbumListItemWidget(),
);
}
2023-03-08 02:16:12 +00:00
Future<void> _createNewAlbumOnTap(
BuildContext context,
int filesCount,
) async {
if (filesCount > 0) {
final result = await showTextInputDialog(
context,
2023-04-07 04:27:53 +00:00
title: S.of(context).albumTitle,
submitButtonLabel: S.of(context).ok,
hintText: S.of(context).enterAlbumName,
2023-03-08 02:16:12 +00:00
onSubmit: (name) {
return _nameAlbum(context, name);
},
showOnlyLoadingState: true,
textCapitalization: TextCapitalization.words,
);
if (result is Exception) {
showGenericErrorDialog(
context: context,
);
_logger.severe(
"Failed to name album",
result,
);
}
} else {
Navigator.pop(context);
await showToast(
context,
2023-04-07 04:27:53 +00:00
S.of(context).createAlbumActionHint,
);
Bus.instance.fire(
TabChangedEvent(
0,
TabChangedEventSource.collectionsPage,
),
);
}
}
2023-03-08 02:16:12 +00:00
Future<void> _nameAlbum(BuildContext context, String albumName) async {
if (albumName.isNotEmpty) {
bool hasVerifiedLock = false;
late final Collection? collection;
if (actionType == CollectionActionType.moveToHiddenCollection) {
collection =
await CollectionsService.instance.createHiddenAlbum(albumName);
hasVerifiedLock = true;
} else {
collection = await _createAlbum(albumName);
}
if (collection != null) {
if (await _runCollectionAction(
2023-03-08 02:16:12 +00:00
context,
collection,
showProgressDialog: false,
)) {
2023-03-08 02:16:12 +00:00
if (actionType == CollectionActionType.restoreFiles) {
showShortToast(
context,
'Restored files to album ' + albumName,
);
} else {
showShortToast(
context,
"Album '" + albumName + "' created.",
);
}
_navigateToCollection(
context,
collection,
hasVerifiedLock: hasVerifiedLock,
);
}
}
}
}
Future<Collection?> _createAlbum(String albumName) async {
Collection? collection;
try {
collection = await CollectionsService.instance.createAlbum(albumName);
} catch (e, s) {
_logger.severe("Failed to create album", e, s);
rethrow;
}
return collection;
}
2023-03-08 02:16:12 +00:00
Future<void> _albumListItemOnTap(
BuildContext context,
Collection item,
2023-03-08 02:16:12 +00:00
) async {
if (await _runCollectionAction(context, item)) {
late final String toastMessage;
bool shouldNavigateToCollection = false;
bool hasVerifiedLock = false;
2023-03-08 02:16:12 +00:00
if (actionType == CollectionActionType.addFiles) {
toastMessage = S.of(context).addedSuccessfullyTo(item.displayName);
shouldNavigateToCollection = true;
2023-03-08 02:16:12 +00:00
} else if (actionType == CollectionActionType.moveFiles ||
actionType == CollectionActionType.restoreFiles ||
actionType == CollectionActionType.unHide) {
toastMessage = S.of(context).movedSuccessfullyTo(item.displayName);
shouldNavigateToCollection = true;
} else if (actionType == CollectionActionType.moveToHiddenCollection) {
toastMessage = S.of(context).movedSuccessfullyTo(item.displayName);
shouldNavigateToCollection = true;
hasVerifiedLock = true;
2023-08-28 11:52:54 +00:00
} else if (actionType == CollectionActionType.addToHiddenAlbum) {
toastMessage = S.of(context).addedSuccessfullyTo(item.displayName);
shouldNavigateToCollection = true;
hasVerifiedLock = true;
} else {
toastMessage = "";
}
if (toastMessage.isNotEmpty) {
showShortToast(
context,
toastMessage,
);
}
if (shouldNavigateToCollection) {
_navigateToCollection(
2023-03-08 02:16:12 +00:00
context,
item,
hasVerifiedLock: hasVerifiedLock,
);
}
}
}
2023-03-08 02:16:12 +00:00
Future<bool> _runCollectionAction(
BuildContext context,
Collection collection, {
bool showProgressDialog = true,
}) async {
2023-03-08 02:16:12 +00:00
switch (actionType) {
case CollectionActionType.addFiles:
return _addToCollection(
2023-03-08 02:16:12 +00:00
context,
collection.id,
showProgressDialog,
);
case CollectionActionType.moveFiles:
2023-03-08 02:16:12 +00:00
return _moveFilesToCollection(context, collection.id);
case CollectionActionType.unHide:
2023-03-08 02:16:12 +00:00
return _moveFilesToCollection(context, collection.id);
case CollectionActionType.restoreFiles:
2023-03-08 02:16:12 +00:00
return _restoreFilesToCollection(context, collection.id);
case CollectionActionType.shareCollection:
2023-03-08 02:16:12 +00:00
return _showShareCollectionPage(context, collection);
case CollectionActionType.collectPhotos:
2023-03-08 02:16:12 +00:00
return _createCollaborativeLink(context, collection);
case CollectionActionType.moveToHiddenCollection:
return _moveFilesToCollection(context, collection.id);
2023-08-28 11:52:54 +00:00
case CollectionActionType.addToHiddenAlbum:
return _addToCollection(context, collection.id, showProgressDialog);
}
}
void _navigateToCollection(
BuildContext context,
Collection collection, {
bool hasVerifiedLock = false,
}) {
Navigator.pop(context);
routeToPage(
context,
CollectionPage(
CollectionWithThumbnail(collection, null),
hasVerifiedLock: hasVerifiedLock,
),
);
}
2023-03-08 02:16:12 +00:00
Future<bool> _createCollaborativeLink(
BuildContext context,
Collection collection,
) async {
final CollectionActions collectionActions =
CollectionActions(CollectionsService.instance);
if (collection.hasLink) {
if (collection.publicURLs!.first!.enableCollect) {
if (Configuration.instance.getUserID() == collection.owner!.id) {
unawaited(
routeToPage(
context,
ShareCollectionPage(collection),
),
);
}
2023-04-07 04:28:54 +00:00
showShortToast(
2023-04-18 09:05:21 +00:00
context,
S.of(context).thisAlbumAlreadyHDACollaborativeLink,
);
return Future.value(false);
} else {
try {
unawaited(
routeToPage(
context,
ShareCollectionPage(collection),
),
);
CollectionsService.instance
.updateShareUrl(collection, {'enableCollect': true}).then(
2023-04-07 04:28:54 +00:00
(value) => showShortToast(
context,
S.of(context).collaborativeLinkCreatedFor(collection.displayName),
),
);
return true;
} catch (e) {
showGenericErrorDialog(context: context);
return false;
}
}
}
final bool result = await collectionActions.enableUrl(
context,
collection,
enableCollect: true,
);
if (result) {
2023-04-07 04:28:54 +00:00
showShortToast(
context,
S.of(context).collaborativeLinkCreatedFor(collection.displayName),
);
if (Configuration.instance.getUserID() == collection.owner!.id) {
unawaited(
routeToPage(
context,
ShareCollectionPage(collection),
),
);
} else {
showGenericErrorDialog(context: context);
_logger.severe("Cannot share collections owned by others");
}
}
return result;
}
2023-03-08 02:16:12 +00:00
Future<bool> _showShareCollectionPage(
BuildContext context,
Collection collection,
) {
if (Configuration.instance.getUserID() == collection.owner!.id) {
unawaited(
routeToPage(
context,
ShareCollectionPage(collection),
),
);
} else {
showGenericErrorDialog(context: context);
_logger.severe("Cannot share collections owned by others");
}
return Future.value(true);
}
2023-03-08 02:16:12 +00:00
Future<bool> _addToCollection(
BuildContext context,
int collectionID,
bool showProgressDialog,
) async {
2023-09-22 06:49:50 +00:00
final bool result = await _collectionActions.addToCollection(
2023-08-26 10:25:52 +00:00
context,
collectionID,
showProgressDialog,
selectedFiles: selectedFiles?.files.toList(),
sharedFiles: sharedFiles,
);
if (result) {
2023-03-08 02:16:12 +00:00
selectedFiles?.clearAll();
}
2023-08-26 10:25:52 +00:00
return result;
}
2023-03-08 02:16:12 +00:00
Future<bool> _moveFilesToCollection(
BuildContext context,
int toCollectionID,
) async {
late final String message;
if (actionType == CollectionActionType.moveFiles ||
actionType == CollectionActionType.moveToHiddenCollection) {
message = S.of(context).movingFilesToAlbum;
} else {
message = S.of(context).unhidingFilesToAlbum;
}
final dialog = createProgressDialog(context, message, isDismissible: true);
await dialog.show();
try {
2023-03-08 02:16:12 +00:00
final int fromCollectionID = selectedFiles!.files.first.collectionID!;
await CollectionsService.instance.move(
toCollectionID,
fromCollectionID,
selectedFiles!.files.toList(),
);
await dialog.hide();
RemoteSyncService.instance.sync(silently: true);
2023-03-08 02:16:12 +00:00
selectedFiles?.clearAll();
return true;
} on AssertionError catch (e) {
await dialog.hide();
2023-04-07 04:27:53 +00:00
showErrorDialog(context, S.of(context).oops, e.message as String?);
return false;
} catch (e, s) {
_logger.severe("Could not move to album", e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
return false;
}
}
2023-03-08 02:16:12 +00:00
Future<bool> _restoreFilesToCollection(
BuildContext context,
int toCollectionID,
) async {
final dialog = createProgressDialog(
context,
2023-04-07 04:27:53 +00:00
S.of(context).restoringFiles,
isDismissible: true,
);
await dialog.show();
try {
await CollectionsService.instance
.restore(toCollectionID, selectedFiles!.files.toList());
RemoteSyncService.instance.sync(silently: true);
2023-03-08 02:16:12 +00:00
selectedFiles?.clearAll();
await dialog.hide();
return true;
} on AssertionError catch (e) {
await dialog.hide();
2023-04-07 04:27:53 +00:00
showErrorDialog(context, S.of(context).oops, e.message as String?);
return false;
} catch (e, s) {
_logger.severe("Could not move to album", e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
return false;
}
}
}