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

379 lines
12 KiB
Dart
Raw Normal View History

// @dart=2.9
2020-05-05 12:56:24 +00:00
import 'dart:async';
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';
import 'package:photos/ente_theme_data.dart';
2021-02-02 16:35:38 +00:00
import 'package:photos/events/subscription_purchased_event.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';
2021-09-20 06:41:38 +00:00
import 'package:photos/models/magic_metadata.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/feature_flag_service.dart';
import 'package:photos/ui/common/dialogs.dart';
import 'package:photos/ui/common/rename_dialog.dart';
2022-07-01 14:39:02 +00:00
import 'package:photos/ui/sharing/share_collection_widget.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/magic_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;
2020-04-18 18:46:38 +00:00
final String title;
final SelectedFiles selectedFiles;
final DeviceCollection deviceCollection;
2020-10-29 12:56:30 +00:00
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, {
2022-07-03 07:47:15 +00:00
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");
2020-11-15 07:19:44 +00:00
StreamSubscription _userAuthEventSubscription;
2021-01-08 16:40:03 +00:00
Function() _selectedFilesListener;
2021-09-23 10:41:48 +00:00
String _appBarTitle;
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
};
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
.headline5
.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 showDialog<String>(
context: context,
builder: (BuildContext context) {
2022-06-09 04:51:59 +00:00
return RenameDialog(_appBarTitle, 'Album');
},
barrierColor: Colors.black.withOpacity(0.85),
);
2021-09-23 10:41:48 +00:00
// indicates user cancelled the rename request
if (result == null || result.trim() == _appBarTitle.trim()) {
return;
}
2022-06-09 04:51:59 +00:00
final dialog = createProgressDialog(context, "Changing name...");
await dialog.show();
try {
2021-09-23 11:33:25 +00:00
await CollectionsService.instance.rename(widget.collection, result);
await dialog.hide();
if (mounted) {
2021-09-23 10:41:48 +00:00
_appBarTitle = result;
setState(() {});
}
} catch (e) {
await dialog.hide();
showGenericErrorDialog(context);
}
}
2022-10-11 01:35:09 +00:00
Future<dynamic> _leaveAlbum(BuildContext context) async {
final DialogUserChoice result = await showChoiceDialog(
context,
"Leave shared album?",
"You will leave the album, and it will stop being visible to you.",
firstAction: "Cancel",
secondAction: "Yes, Leave",
secondActionColor:
Theme.of(context).colorScheme.enteTheme.colorScheme.warning700,
);
if (result != DialogUserChoice.secondChoice) {
return;
}
final dialog = createProgressDialog(context, "Leaving album...");
await dialog.show();
try {
await CollectionsService.instance.leaveAlbum(widget.collection);
await dialog.hide();
if (mounted) {
Navigator.of(context).pop();
}
} catch (e) {
await dialog.hide();
showGenericErrorDialog(context);
}
}
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) {
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 {
final bool showHiddenWarning =
await _shouldShowHiddenFilesWarning(widget.collection);
if (showHiddenWarning) {
final choice = await showChoiceDialog(
context,
'Share hidden items?',
"Looks like you're trying to share an album that has some hidden items.\n\nThese hidden items can be seen by the recipient.",
firstAction: "Cancel",
secondAction: "Share anyway",
secondActionColor:
Theme.of(context).colorScheme.defaultTextColor,
);
if (choice != DialogUserChoice.secondChoice) {
return;
}
}
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) {
2022-10-11 01:35:09 +00:00
if (widget.collection.type != CollectionType.favorites) {
items.add(
PopupMenuItem(
value: 1,
child: Row(
children: const [
Icon(Icons.edit),
Padding(
padding: EdgeInsets.all(8),
2022-03-21 09:32:24 +00:00
),
2022-10-11 01:35:09 +00:00
Text("Rename album"),
],
),
),
);
}
final bool isArchived = widget.collection.isArchived();
items.add(
PopupMenuItem(
value: 2,
child: Row(
children: [
2022-10-31 11:16:04 +00:00
Icon(isArchived ? Icons.unarchive : Icons.archive_outlined),
2022-10-11 01:35:09 +00:00
const Padding(
padding: EdgeInsets.all(8),
2022-03-21 09:32:24 +00:00
),
2022-10-31 11:16:04 +00:00
Text(isArchived ? "Unarchive album" : "Archive album"),
2022-10-11 01:35:09 +00:00
],
),
),
);
if (widget.collection.type != CollectionType.favorites) {
items.add(
PopupMenuItem(
value: 3,
child: Row(
children: const [
Icon(Icons.delete_outline),
Padding(
padding: EdgeInsets.all(8),
2022-09-13 06:44:10 +00:00
),
2022-10-11 01:35:09 +00:00
Text("Delete album"),
],
),
),
);
}
} // ownedCollection open ends
if (widget.type == GalleryType.sharedCollection) {
items.add(
PopupMenuItem(
value: 4,
child: Row(
children: const [
Icon(Icons.logout),
Padding(
padding: EdgeInsets.all(8),
),
Text("Leave album"),
],
),
2022-06-11 08:23:52 +00:00
),
);
2021-09-23 02:34:15 +00:00
}
if (items.isNotEmpty) {
actions.add(
PopupMenuButton(
itemBuilder: (context) {
return items;
},
onSelected: (value) async {
if (value == 1) {
await _renameAlbum(context);
} else if (value == 2) {
await changeCollectionVisibility(
context,
widget.collection,
widget.collection.isArchived()
? visibilityVisible
: visibilityArchive,
);
} else if (value == 3) {
await _trashCollection();
} else if (value == 4) {
await _leaveAlbum(context);
} else {
showToast(context, "Something went wrong");
}
},
),
);
}
2022-10-11 01:35:09 +00:00
2020-04-30 15:09:41 +00:00
return actions;
}
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;
2022-09-13 06:44:10 +00:00
final result = await showChoiceDialog(
context,
"Delete album?",
"Files that are unique to this album "
2022-09-13 07:32:15 +00:00
"will be moved to trash, and this album will be deleted.",
2022-09-13 06:44:10 +00:00
firstAction: "Cancel",
secondAction: "Delete album",
secondActionColor: Colors.red,
);
if (result != DialogUserChoice.secondChoice) {
return;
}
final dialog = createProgressDialog(
context,
"Please wait, deleting album",
);
await dialog.show();
try {
await CollectionsService.instance
.trashCollection(widget.collection, isEmptyCollection);
2022-09-13 06:44:10 +00:00
showShortToast(context, "Successfully deleted album");
await dialog.hide();
Navigator.of(context).pop();
} catch (e, s) {
_logger.severe("failed to trash collection", e, s);
await dialog.hide();
showGenericErrorDialog(context);
rethrow;
}
}
2020-05-17 14:23:37 +00:00
Future<void> _showShareCollectionDialog() async {
2020-10-29 12:56:30 +00:00
var collection = widget.collection;
2022-05-17 11:38:21 +00:00
final dialog = createProgressDialog(context, "Please wait...");
await dialog.show();
try {
if (collection == null || widget.type != GalleryType.ownedCollection) {
throw Exception(
"Cannot share empty collection of type ${widget.type}",
);
2020-10-29 12:56:30 +00:00
} else {
2022-07-03 09:49:33 +00:00
final sharees =
await CollectionsService.instance.getSharees(collection.id);
collection = collection.copyWith(sharees: sharees);
2020-10-29 12:56:30 +00:00
}
await dialog.hide();
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return SharingDialog(
collection,
);
},
);
} catch (e, s) {
_logger.severe(e, s);
await dialog.hide();
showGenericErrorDialog(context);
2020-10-29 12:56:30 +00:00
}
2020-05-17 12:39:38 +00:00
}
Future<bool> _shouldShowHiddenFilesWarning(Collection collection) async {
// collection can be null for device folders which are not marked for
// back up
if (!FeatureFlagService.instance.isInternalUserOrDebugBuild() ||
collection == null) {
return false;
}
2022-09-01 09:40:45 +00:00
// collection is already shared
if (collection.sharees.isNotEmpty || collection.publicURLs.isNotEmpty) {
return false;
}
final collectionIDsWithHiddenFiles =
await FilesDB.instance.getCollectionIDsOfHiddenFiles(
Configuration.instance.getUserID(),
);
return collectionIDsWithHiddenFiles.contains(collection.id);
}
2020-04-18 18:46:38 +00:00
}