ente/lib/ui/viewer/gallery/gallery_app_bar_widget.dart

573 lines
17 KiB
Dart
Raw Normal View History

2020-05-05 12:56:24 +00:00
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
2020-04-18 18:46:38 +00:00
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
2020-11-15 07:19:44 +00:00
import 'package:photos/core/event_bus.dart';
import "package:photos/db/files_db.dart";
2021-02-02 16:35:38 +00:00
import 'package:photos/events/subscription_purchased_event.dart';
2023-04-07 05:06:43 +00:00
import "package:photos/generated/l10n.dart";
import 'package:photos/models/backup_status.dart';
2020-10-29 12:56:30 +00:00
import 'package:photos/models/collection.dart';
import 'package:photos/models/device_collection.dart';
2022-07-03 10:09:01 +00:00
import 'package:photos/models/gallery_type.dart';
import "package:photos/models/metadata/common_keys.dart";
import 'package:photos/models/selected_files.dart';
2020-10-13 05:22:20 +00:00
import 'package:photos/services/collections_service.dart';
import 'package:photos/services/sync_service.dart';
import 'package:photos/services/update_service.dart';
import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
import 'package:photos/ui/components/action_sheet_widget.dart';
import 'package:photos/ui/components/buttons/button_widget.dart';
import 'package:photos/ui/components/dialog_widget.dart';
import 'package:photos/ui/components/models/button_type.dart';
2023-06-15 12:45:26 +00:00
import "package:photos/ui/map/enable_map.dart";
import "package:photos/ui/map/map_screen.dart";
import 'package:photos/ui/sharing/album_participants_page.dart';
2022-11-20 11:55:12 +00:00
import 'package:photos/ui/sharing/share_collection_page.dart';
import 'package:photos/ui/tools/free_space_page.dart';
import 'package:photos/utils/data_util.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/magic_util.dart';
import 'package:photos/utils/navigation_util.dart';
2022-09-13 06:44:10 +00:00
import 'package:photos/utils/toast_util.dart';
2020-04-18 18:46:38 +00:00
class GalleryAppBarWidget extends StatefulWidget {
final GalleryType type;
final String? title;
final SelectedFiles selectedFiles;
final DeviceCollection? deviceCollection;
final Collection? collection;
2020-04-18 18:46:38 +00:00
const GalleryAppBarWidget(
this.type,
this.title,
2020-10-29 12:56:30 +00:00
this.selectedFiles, {
Key? key,
this.deviceCollection,
2020-10-29 12:56:30 +00:00
this.collection,
2022-07-03 07:47:15 +00:00
}) : super(key: key);
2020-04-18 18:46:38 +00:00
@override
2022-07-03 09:45:00 +00:00
State<GalleryAppBarWidget> createState() => _GalleryAppBarWidgetState();
2020-04-18 18:46:38 +00:00
}
class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
final _logger = Logger("GalleryAppBar");
late StreamSubscription _userAuthEventSubscription;
late Function() _selectedFilesListener;
String? _appBarTitle;
late CollectionActions collectionActions;
final GlobalKey shareButtonKey = GlobalKey();
2022-10-11 01:35:09 +00:00
2020-04-30 15:09:41 +00:00
@override
void initState() {
2021-01-08 16:40:03 +00:00
_selectedFilesListener = () {
setState(() {});
2021-01-08 16:40:03 +00:00
};
collectionActions = CollectionActions(CollectionsService.instance);
2021-01-08 16:40:03 +00:00
widget.selectedFiles.addListener(_selectedFilesListener);
2022-07-03 09:49:33 +00:00
_userAuthEventSubscription =
Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
2020-11-15 07:19:44 +00:00
setState(() {});
});
2021-09-23 10:41:48 +00:00
_appBarTitle = widget.title;
2020-04-30 15:09:41 +00:00
super.initState();
}
2020-11-15 07:19:44 +00:00
@override
void dispose() {
_userAuthEventSubscription.cancel();
2021-01-08 16:40:03 +00:00
widget.selectedFiles.removeListener(_selectedFilesListener);
2020-11-15 07:19:44 +00:00
super.dispose();
}
2020-04-18 18:46:38 +00:00
@override
Widget build(BuildContext context) {
return widget.type == GalleryType.homepage
? const SizedBox.shrink()
: AppBar(
backgroundColor: widget.type == GalleryType.homepage
? const Color(0x00000000)
: null,
elevation: 0,
centerTitle: false,
title: widget.type == GalleryType.homepage
? const SizedBox.shrink()
: TextButton(
child: Text(
_appBarTitle!,
style: Theme.of(context)
.textTheme
2023-06-13 06:41:31 +00:00
.headlineSmall!
.copyWith(fontSize: 16),
),
onPressed: () => _renameAlbum(context),
),
actions: _getDefaultActions(context),
);
2020-04-18 18:46:38 +00:00
}
Future<dynamic> _renameAlbum(BuildContext context) async {
2022-07-03 07:47:15 +00:00
if (widget.type != GalleryType.ownedCollection) {
return;
}
final result = await showTextInputDialog(
context,
2023-04-07 05:06:43 +00:00
title: S.of(context).renameAlbum,
submitButtonLabel: S.of(context).rename,
hintText: S.of(context).enterAlbumName,
alwaysShowSuccessState: true,
2023-02-09 05:12:40 +00:00
textCapitalization: TextCapitalization.words,
onSubmit: (String text) async {
2023-02-07 05:13:22 +00:00
// indicates user cancelled the rename request
if (text == "" || text.trim() == _appBarTitle!.trim()) {
return;
}
try {
await CollectionsService.instance.rename(widget.collection!, text);
if (mounted) {
_appBarTitle = text;
setState(() {});
}
} catch (e, s) {
_logger.severe("Failed to rename album", e, s);
rethrow;
2023-02-07 05:13:22 +00:00
}
},
);
if (result is Exception) {
showGenericErrorDialog(context: context);
}
}
2022-10-11 01:35:09 +00:00
Future<dynamic> _leaveAlbum(BuildContext context) async {
final actionResult = await showActionSheet(
context: context,
buttons: [
ButtonWidget(
buttonType: ButtonType.critical,
isInAlert: true,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first,
shouldSurfaceExecutionStates: true,
2023-04-07 05:06:43 +00:00
labelText: S.of(context).leaveAlbum,
onTap: () async {
await CollectionsService.instance.leaveAlbum(widget.collection!);
},
),
2023-04-07 05:06:43 +00:00
ButtonWidget(
buttonType: ButtonType.secondary,
buttonAction: ButtonAction.cancel,
isInAlert: true,
shouldStickToDarkTheme: true,
2023-04-07 05:06:43 +00:00
labelText: S.of(context).cancel,
)
],
2023-04-07 05:06:43 +00:00
title: S.of(context).leaveSharedAlbum,
body: S.of(context).photosAddedByYouWillBeRemovedFromTheAlbum,
2022-10-11 01:35:09 +00:00
);
if (actionResult?.action != null && mounted) {
if (actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
} else if (actionResult.action == ButtonAction.first) {
Navigator.of(context).pop();
}
2022-10-11 01:35:09 +00:00
}
}
// todo: In the new design, clicking on free up space will directly open
// the free up space page and show loading indicator while calculating
// the space which can be claimed up. This code duplication should be removed
// whenever we move to the new design for free up space.
2022-11-24 13:08:27 +00:00
Future<dynamic> _deleteBackedUpFiles(BuildContext context) async {
2023-04-07 05:27:06 +00:00
final dialog = createProgressDialog(context, S.of(context).calculating);
await dialog.show();
BackupStatus status;
try {
status = await SyncService.instance
.getBackupStatus(pathID: widget.deviceCollection!.id);
} catch (e) {
await dialog.hide();
showGenericErrorDialog(context: context);
return;
}
await dialog.hide();
if (status.localIDs.isEmpty) {
showErrorDialog(
context,
2023-04-07 05:06:43 +00:00
S.of(context).allClear,
S.of(context).youveNoFilesInThisAlbumThatCanBeDeleted,
);
} else {
final bool? result = await routeToPage(
2022-11-24 11:09:05 +00:00
context,
FreeSpacePage(status, clearSpaceForFolder: true),
);
if (result == true) {
_showSpaceFreedDialog(status);
}
}
}
void _showSpaceFreedDialog(BackupStatus status) {
final DialogWidget dialog = choiceDialog(
2023-04-07 05:27:06 +00:00
title: S.of(context).success,
body: S.of(context).youHaveSuccessfullyFreedUp(formatBytes(status.size)),
firstButtonLabel: S.of(context).rateUs,
firstButtonOnTap: () async {
UpdateService.instance.launchReviewUrl();
},
firstButtonType: ButtonType.primary,
2023-04-07 05:27:06 +00:00
secondButtonLabel: S.of(context).ok,
secondButtonOnTap: () async {
if (Platform.isIOS) {
showToast(
context,
2023-04-07 05:27:06 +00:00
S.of(context).remindToEmptyDeviceTrash,
);
}
},
);
showConfettiDialog(
context: context,
2022-12-30 01:29:50 +00:00
dialogBuilder: (BuildContext context) {
return dialog;
},
barrierColor: Colors.black87,
confettiAlignment: Alignment.topCenter,
useRootNavigator: true,
);
}
2020-04-30 15:09:41 +00:00
List<Widget> _getDefaultActions(BuildContext context) {
2022-08-29 14:43:31 +00:00
final List<Widget> actions = <Widget>[];
if (Configuration.instance.hasConfiguredAccount() &&
widget.selectedFiles.files.isEmpty &&
(widget.type == GalleryType.ownedCollection ||
widget.type == GalleryType.sharedCollection) &&
widget.collection?.type != CollectionType.favorites) {
2021-09-23 09:52:59 +00:00
actions.add(
Tooltip(
message: "Share",
2021-09-23 09:52:59 +00:00
child: IconButton(
icon: const Icon(Icons.people_outlined),
onPressed: () async {
await _showShareCollectionDialog();
2021-09-23 09:52:59 +00:00
},
),
),
);
2020-04-30 15:09:41 +00:00
}
2022-10-11 01:35:09 +00:00
final List<PopupMenuItem> items = [];
2022-07-03 07:47:15 +00:00
if (widget.type == GalleryType.ownedCollection) {
if (widget.collection!.type != CollectionType.favorites) {
2022-10-11 01:35:09 +00:00
items.add(
PopupMenuItem(
value: 1,
child: Row(
2023-04-07 05:27:06 +00:00
children: [
const Icon(Icons.edit),
const Padding(
2022-10-11 01:35:09 +00:00
padding: EdgeInsets.all(8),
2022-03-21 09:32:24 +00:00
),
2023-04-07 05:27:06 +00:00
Text(S.of(context).renameAlbum),
2022-10-11 01:35:09 +00:00
],
),
),
);
}
if (widget.type == GalleryType.ownedCollection ||
widget.type == GalleryType.sharedCollection) {
items.add(
PopupMenuItem(
value: 8,
child: Row(
2023-06-15 12:53:47 +00:00
children: [
const Icon(Icons.map_outlined),
const Padding(
padding: EdgeInsets.all(8),
),
2023-06-15 12:53:47 +00:00
Text(S.of(context).maps),
],
),
),
);
}
final bool isArchived = widget.collection!.isArchived();
// Do not show archive option for favorite collection. If collection is
// already archived, allow user to unarchive that collection.
if (isArchived || widget.collection!.type != CollectionType.favorites) {
items.add(
PopupMenuItem(
2023-05-26 04:46:31 +00:00
value: 6,
child: Row(
children: [
2023-05-26 04:46:31 +00:00
const Icon(Icons.sort_outlined),
const Padding(
padding: EdgeInsets.all(8),
),
2023-04-18 09:05:21 +00:00
Text(
2023-05-26 04:46:31 +00:00
S.of(context).sortAlbumsBy,
2023-04-18 09:05:21 +00:00
),
],
),
2022-10-11 01:35:09 +00:00
),
);
items.add(
PopupMenuItem(
2023-05-26 04:46:31 +00:00
value: 2,
child: Row(
children: [
2023-05-26 04:46:31 +00:00
Icon(isArchived ? Icons.unarchive : Icons.archive_outlined),
const Padding(
padding: EdgeInsets.all(8),
),
Text(
2023-05-26 04:46:31 +00:00
isArchived
? S.of(context).unarchiveAlbum
: S.of(context).archiveAlbum,
),
],
),
),
);
2023-05-26 04:46:31 +00:00
}
if (widget.collection!.type != CollectionType.favorites) {
2022-10-11 01:35:09 +00:00
items.add(
PopupMenuItem(
value: 3,
child: Row(
2023-04-07 05:27:06 +00:00
children: [
const Icon(Icons.delete_outline),
const Padding(
2022-10-11 01:35:09 +00:00
padding: EdgeInsets.all(8),
2022-09-13 06:44:10 +00:00
),
2023-04-07 05:27:06 +00:00
Text(S.of(context).deleteAlbum),
2022-10-11 01:35:09 +00:00
],
),
),
);
}
} // ownedCollection open ends
if (widget.type == GalleryType.sharedCollection) {
final bool hasShareeArchived = widget.collection!.hasShareeArchived();
2022-10-11 01:35:09 +00:00
items.add(
PopupMenuItem(
value: 4,
child: Row(
2023-04-07 05:27:06 +00:00
children: [
const Icon(Icons.logout),
const Padding(
2022-10-11 01:35:09 +00:00
padding: EdgeInsets.all(8),
),
2023-04-07 05:27:06 +00:00
Text(S.of(context).leaveAlbum),
2022-10-11 01:35:09 +00:00
],
),
2022-06-11 08:23:52 +00:00
),
);
items.add(
PopupMenuItem(
value: 7,
child: Row(
children: [
Icon(
hasShareeArchived ? Icons.unarchive : Icons.archive_outlined,
),
const Padding(
padding: EdgeInsets.all(8),
),
Text(
hasShareeArchived
? S.of(context).unarchiveAlbum
: S.of(context).archiveAlbum,
),
],
),
),
);
2021-09-23 02:34:15 +00:00
}
if (widget.type == GalleryType.localFolder) {
items.add(
PopupMenuItem(
value: 5,
child: Row(
2023-04-07 05:27:06 +00:00
children: [
const Icon(Icons.delete_sweep_outlined),
const Padding(
padding: EdgeInsets.all(8),
),
2023-04-07 05:27:06 +00:00
Text(S.of(context).freeUpDeviceSpace),
],
),
),
);
}
if (items.isNotEmpty) {
actions.add(
PopupMenuButton(
itemBuilder: (context) {
return items;
},
onSelected: (dynamic value) async {
if (value == 1) {
await _renameAlbum(context);
} else if (value == 2) {
await changeCollectionVisibility(
context,
widget.collection!,
widget.collection!.isArchived()
? visibleVisibility
: archiveVisibility,
);
} else if (value == 3) {
await _trashCollection();
} else if (value == 4) {
await _leaveAlbum(context);
} else if (value == 5) {
2022-11-24 13:08:27 +00:00
await _deleteBackedUpFiles(context);
} else if (value == 6) {
await _showSortOption(context);
2023-05-29 13:37:02 +00:00
} else if (value == 7) {
await changeCollectionVisibility(
context,
widget.collection!,
widget.collection!.hasShareeArchived()
? visibleVisibility
: archiveVisibility,
isOwner: false,
);
if (mounted) {
setState(() {});
}
} else if (value == 8) {
await showOnMap();
} else {
2023-04-07 05:27:06 +00:00
showToast(context, S.of(context).somethingWentWrong);
}
},
),
);
}
2022-10-11 01:35:09 +00:00
2020-04-30 15:09:41 +00:00
return actions;
}
Future<void> showOnMap() async {
2023-06-15 12:45:26 +00:00
final bool result = await requestForMapEnable(context);
if (result) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => MapScreen(
filesFutureFn: () async {
return FilesDB.instance.getAllFilesCollection(
widget.collection!.id,
);
},
),
),
2023-06-15 12:45:26 +00:00
);
}
}
Future<void> _showSortOption(BuildContext bContext) async {
final bool? sortByAsc = await showMenu<bool>(
context: bContext,
position: RelativeRect.fromLTRB(
MediaQuery.of(context).size.width,
kToolbarHeight + 12,
12,
0,
),
items: [
PopupMenuItem(
value: false,
child: Text(S.of(context).sortNewestFirst),
),
PopupMenuItem(
value: true,
child: Text(S.of(context).sortOldestFirst),
),
],
);
if (sortByAsc != null) {
changeSortOrder(bContext, widget.collection!, sortByAsc);
}
}
2022-09-13 06:44:10 +00:00
Future<void> _trashCollection() async {
final collectionWithThumbnail =
await CollectionsService.instance.getCollectionsWithThumbnails();
final bool isEmptyCollection = collectionWithThumbnail
.firstWhereOrNull(
(element) => element.collection.id == widget.collection!.id,
)
?.thumbnail ==
null;
if (isEmptyCollection) {
final dialog = createProgressDialog(
context,
2023-04-07 05:27:06 +00:00
S.of(context).pleaseWaitDeletingAlbum,
);
await dialog.show();
try {
await CollectionsService.instance
.trashEmptyCollection(widget.collection!);
await dialog.hide();
Navigator.of(context).pop();
} catch (e, s) {
_logger.severe("failed to trash collection", e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
}
} else {
final bool result = await collectionActions.deleteCollectionSheet(
context,
widget.collection!,
);
if (result == true) {
Navigator.of(context).pop();
} else {
debugPrint("No pop");
}
2022-09-13 06:44:10 +00:00
}
}
2020-05-17 14:23:37 +00:00
Future<void> _showShareCollectionDialog() async {
2022-12-08 11:12:51 +00:00
final collection = widget.collection;
try {
if (collection == null ||
(widget.type != GalleryType.ownedCollection &&
widget.type != GalleryType.sharedCollection)) {
throw Exception(
"Cannot share empty collection of typex ${widget.type}",
);
}
if (Configuration.instance.getUserID() == widget.collection!.owner!.id) {
unawaited(
routeToPage(
context,
ShareCollectionPage(collection),
),
);
} else {
unawaited(
routeToPage(
context,
AlbumParticipantsPage(collection),
),
);
2020-10-29 12:56:30 +00:00
}
} catch (e, s) {
_logger.severe(e, s);
showGenericErrorDialog(context: context);
2020-10-29 12:56:30 +00:00
}
2020-05-17 12:39:38 +00:00
}
2020-04-18 18:46:38 +00:00
}