123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- import 'dart:async';
- import 'dart:io';
- import 'package:flutter/cupertino.dart';
- import 'package:flutter/material.dart';
- import 'package:logging/logging.dart';
- import 'package:page_transition/page_transition.dart';
- import 'package:photos/core/configuration.dart';
- import 'package:photos/core/event_bus.dart';
- import 'package:photos/events/subscription_purchased_event.dart';
- import 'package:photos/models/collection.dart';
- import 'package:photos/models/magic_metadata.dart';
- import 'package:photos/models/selected_files.dart';
- import 'package:photos/services/collections_service.dart';
- import 'package:photos/ui/change_collection_name_dialog.dart';
- import 'package:photos/ui/create_collection_page.dart';
- import 'package:photos/ui/share_collection_widget.dart';
- import 'package:photos/utils/magic_util.dart';
- import 'package:photos/utils/delete_file_util.dart';
- import 'package:photos/utils/dialog_util.dart';
- import 'package:photos/utils/share_util.dart';
- import 'package:photos/utils/toast_util.dart';
- enum GalleryAppBarType {
- homepage,
- archive,
- trash,
- local_folder,
- // indicator for gallery view of collections shared with the user
- shared_collection,
- owned_collection,
- search_results
- }
- class GalleryAppBarWidget extends StatefulWidget {
- final GalleryAppBarType type;
- final String title;
- final SelectedFiles selectedFiles;
- final String path;
- final Collection collection;
- GalleryAppBarWidget(
- this.type,
- this.title,
- this.selectedFiles, {
- this.path,
- this.collection,
- });
- @override
- _GalleryAppBarWidgetState createState() => _GalleryAppBarWidgetState();
- }
- class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
- final _logger = Logger("GalleryAppBar");
- StreamSubscription _userAuthEventSubscription;
- Function() _selectedFilesListener;
- String _appBarTitle;
- @override
- void initState() {
- _selectedFilesListener = () {
- setState(() {});
- };
- widget.selectedFiles.addListener(_selectedFilesListener);
- _userAuthEventSubscription =
- Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
- setState(() {});
- });
- _appBarTitle = widget.title;
- super.initState();
- }
- @override
- void dispose() {
- _userAuthEventSubscription.cancel();
- widget.selectedFiles.removeListener(_selectedFilesListener);
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- if (widget.selectedFiles.files.isEmpty) {
- return AppBar(
- backgroundColor: widget.type == GalleryAppBarType.homepage
- ? Color(0x00000000)
- : null,
- elevation: 0,
- title: widget.type == GalleryAppBarType.homepage
- ? Container()
- : TextButton(
- child: Text(
- _appBarTitle,
- style: TextStyle(
- color: Colors.white.withOpacity(0.80),
- fontWeight: FontWeight.bold,
- fontSize: 16,
- ),
- ),
- onPressed: () => _renameAlbum(context),
- ),
- actions: _getDefaultActions(context),
- );
- }
- return AppBar(
- leading: Tooltip(
- message: "clear selection",
- child: IconButton(
- icon: Icon(Platform.isAndroid ? Icons.clear : CupertinoIcons.clear),
- onPressed: () {
- _clearSelectedFiles();
- },
- ),
- ),
- title: Text(widget.selectedFiles.files.length.toString()),
- actions: _getActions(context),
- );
- }
- Future<dynamic> _renameAlbum(BuildContext context) async {
- if (widget.type != GalleryAppBarType.owned_collection) {
- return;
- }
- final result = await showDialog(
- context: context,
- builder: (BuildContext context) {
- return ChangeCollectionNameDialog(name: _appBarTitle);
- },
- barrierColor: Colors.black.withOpacity(0.85),
- );
- // indicates user cancelled the rename request
- if (result == null) {
- return;
- }
- final dialog = createProgressDialog(context, "changing name...");
- await dialog.show();
- try {
- await CollectionsService.instance.rename(widget.collection, result);
- await dialog.hide();
- if (mounted) {
- _appBarTitle = result;
- setState(() {});
- }
- } catch (e) {
- await dialog.hide();
- showGenericErrorDialog(context);
- }
- }
- List<Widget> _getDefaultActions(BuildContext context) {
- List<Widget> actions = <Widget>[];
- if (Configuration.instance.hasConfiguredAccount() &&
- (widget.type == GalleryAppBarType.local_folder ||
- widget.type == GalleryAppBarType.owned_collection)) {
- actions.add(
- Tooltip(
- message: "share",
- child: IconButton(
- icon: Icon(Icons.person_add),
- onPressed: () {
- _showShareCollectionDialog();
- },
- ),
- ),
- );
- }
- if (widget.type == GalleryAppBarType.owned_collection &&
- widget.collection.type == CollectionType.album) {
- actions.add(PopupMenuButton(
- itemBuilder: (context) {
- final List<PopupMenuItem> items = [];
- items.add(
- PopupMenuItem(
- value: 1,
- child: Row(
- children: const [
- Icon(Icons.edit),
- Padding(
- padding: EdgeInsets.all(8),
- ),
- Text("rename"),
- ],
- ),
- ),
- );
- return items;
- },
- onSelected: (value) async {
- if (value == 1) {
- await _renameAlbum(context);
- }
- },
- ));
- }
- if (widget.type == GalleryAppBarType.trash) {
- actions.add(
- Tooltip(
- message: "empty trash",
- child: IconButton(
- icon: Icon(Icons.delete_forever),
- onPressed: () async {
- await emptyTrash(context);
- },
- ),
- ),
- );
- }
- return actions;
- }
- Future<void> _showShareCollectionDialog() async {
- var collection = widget.collection;
- final dialog = createProgressDialog(context, "please wait...");
- await dialog.show();
- if (collection == null) {
- if (widget.type == GalleryAppBarType.local_folder) {
- collection =
- CollectionsService.instance.getCollectionForPath(widget.path);
- if (collection == null) {
- try {
- collection = await CollectionsService.instance
- .getOrCreateForPath(widget.path);
- } catch (e, s) {
- _logger.severe(e, s);
- await dialog.hide();
- showGenericErrorDialog(context);
- }
- }
- } else {
- throw Exception(
- "Cannot create a collection of type" + widget.type.toString());
- }
- } else {
- final sharees =
- await CollectionsService.instance.getSharees(collection.id);
- collection = collection.copyWith(sharees: sharees);
- }
- await dialog.hide();
- return showDialog<void>(
- context: context,
- builder: (BuildContext context) {
- return SharingDialog(collection);
- },
- );
- }
- Future<void> _createAlbum() async {
- Navigator.push(
- context,
- PageTransition(
- type: PageTransitionType.bottomToTop,
- child: CreateCollectionPage(
- widget.selectedFiles,
- null,
- )));
- }
- Future<void> _moveFiles() async {
- Navigator.push(
- context,
- PageTransition(
- type: PageTransitionType.bottomToTop,
- child: CreateCollectionPage(
- widget.selectedFiles,
- null,
- actionType: CollectionActionType.moveFiles,
- )));
- }
- List<Widget> _getActions(BuildContext context) {
- List<Widget> actions = <Widget>[];
- if (widget.type == GalleryAppBarType.trash) {
- _addTrashAction(actions);
- return actions;
- }
- // skip add button for incoming collection till this feature is implemented
- if (Configuration.instance.hasConfiguredAccount() &&
- widget.type != GalleryAppBarType.shared_collection) {
- String msg = "add";
- IconData iconData =
- Platform.isAndroid ? Icons.add_outlined : CupertinoIcons.add;
- // show upload icon instead of add for files selected in local gallery
- if (widget.type == GalleryAppBarType.local_folder) {
- msg = "upload";
- iconData = Platform.isAndroid
- ? Icons.cloud_upload
- : CupertinoIcons.cloud_upload;
- }
- actions.add(
- Tooltip(
- message: msg,
- child: IconButton(
- icon: Icon(iconData),
- onPressed: () {
- _createAlbum();
- },
- ),
- ),
- );
- }
- if (Configuration.instance.hasConfiguredAccount() &&
- widget.type == GalleryAppBarType.owned_collection &&
- widget.collection.type != CollectionType.favorites) {
- actions.add(
- Tooltip(
- message: "move",
- child: IconButton(
- icon: Icon(Platform.isAndroid
- ? Icons.arrow_forward
- : CupertinoIcons.arrow_right),
- onPressed: () {
- _moveFiles();
- },
- ),
- ),
- );
- }
- actions.add(
- Tooltip(
- message: "share",
- child: IconButton(
- icon: Icon(
- Platform.isAndroid ? Icons.share_outlined : CupertinoIcons.share),
- onPressed: () {
- _shareSelected(context);
- },
- ),
- ),
- );
- if (widget.type == GalleryAppBarType.homepage ||
- widget.type == GalleryAppBarType.archive ||
- widget.type == GalleryAppBarType.local_folder) {
- actions.add(
- Tooltip(
- message: "delete",
- child: IconButton(
- icon: Icon(Platform.isAndroid
- ? Icons.delete_outline
- : CupertinoIcons.delete),
- onPressed: () {
- _showDeleteSheet(context);
- },
- ),
- ),
- );
- } else if (widget.type == GalleryAppBarType.owned_collection) {
- if (widget.collection.type == CollectionType.folder) {
- actions.add(
- Tooltip(
- message: "delete",
- child: IconButton(
- icon: Icon(Platform.isAndroid
- ? Icons.delete_outline
- : CupertinoIcons.delete),
- onPressed: () {
- _showDeleteSheet(context);
- },
- ),
- ),
- );
- } else {
- actions.add(
- Tooltip(
- message: "remove",
- child: IconButton(
- icon: Icon(Icons.remove_circle_outline_rounded),
- onPressed: () {
- _showRemoveFromCollectionSheet(context);
- },
- ),
- ),
- );
- }
- }
- if (widget.type == GalleryAppBarType.homepage ||
- widget.type == GalleryAppBarType.archive) {
- bool showArchive = widget.type == GalleryAppBarType.homepage;
- actions.add(Tooltip(
- message: showArchive ? "archive" : "unarchive",
- child: IconButton(
- icon: Icon(
- Platform.isAndroid
- ? (showArchive
- ? Icons.archive_outlined
- : Icons.unarchive_outlined)
- : (showArchive
- ? CupertinoIcons.archivebox
- : CupertinoIcons.archivebox_fill),
- ),
- onPressed: () {
- _handleVisibilityChangeRequest(
- context, showArchive ? kVisibilityArchive : kVisibilityVisible);
- },
- ),
- ));
- }
- return actions;
- }
- void _addTrashAction(List<Widget> actions) {
- actions.add(Tooltip(
- message: "restore",
- child: IconButton(
- icon: Icon(Icons.restore_outlined),
- onPressed: () {
- Navigator.push(
- context,
- PageTransition(
- type: PageTransitionType.bottomToTop,
- child: CreateCollectionPage(
- widget.selectedFiles,
- null,
- actionType: CollectionActionType.restoreFiles,
- )));
- },
- ),
- ));
- actions.add(
- Tooltip(
- message: "delete permanently",
- child: IconButton(
- icon: Icon(Icons.delete_forever_outlined),
- onPressed: () async {
- if (await deleteFromTrash(
- context, widget.selectedFiles.files.toList())) {
- _clearSelectedFiles();
- }
- },
- ),
- ),
- );
- }
- Future<void> _handleVisibilityChangeRequest(
- BuildContext context, int newVisibility) async {
- try {
- await changeVisibility(
- context, widget.selectedFiles.files.toList(), newVisibility);
- } catch (e, s) {
- _logger.severe("failed to update file visibility", e, s);
- await showGenericErrorDialog(context);
- } finally {
- _clearSelectedFiles();
- }
- }
- void _shareSelected(BuildContext context) {
- share(context, widget.selectedFiles.files.toList());
- }
- void _showRemoveFromCollectionSheet(BuildContext context) {
- final count = widget.selectedFiles.files.length;
- final action = CupertinoActionSheet(
- title: Text("remove " +
- count.toString() +
- " file" +
- (count == 1 ? "" : "s") +
- " from " +
- widget.collection.name +
- "?"),
- actions: <Widget>[
- CupertinoActionSheetAction(
- child: Text("remove"),
- isDestructiveAction: true,
- onPressed: () async {
- Navigator.of(context, rootNavigator: true).pop();
- final dialog = createProgressDialog(context, "removing files...");
- await dialog.show();
- try {
- await CollectionsService.instance.removeFromCollection(
- widget.collection.id, widget.selectedFiles.files.toList());
- await dialog.hide();
- widget.selectedFiles.clearAll();
- } catch (e, s) {
- _logger.severe(e, s);
- await dialog.hide();
- showGenericErrorDialog(context);
- }
- },
- ),
- ],
- cancelButton: CupertinoActionSheetAction(
- child: Text("cancel"),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
- },
- ),
- );
- showCupertinoModalPopup(context: context, builder: (_) => action);
- }
- void _showDeleteSheet(BuildContext context) {
- final count = widget.selectedFiles.files.length;
- bool containsUploadedFile = false, containsLocalFile = false;
- for (final file in widget.selectedFiles.files) {
- if (file.uploadedFileID != null) {
- containsUploadedFile = true;
- }
- if (file.localID != null) {
- containsLocalFile = true;
- }
- }
- final actions = <Widget>[];
- if (containsUploadedFile && containsLocalFile) {
- actions.add(CupertinoActionSheetAction(
- child: Text("device"),
- isDestructiveAction: true,
- onPressed: () async {
- Navigator.of(context, rootNavigator: true).pop();
- await deleteFilesOnDeviceOnly(
- context, widget.selectedFiles.files.toList());
- _clearSelectedFiles();
- showToast("files deleted from device");
- },
- ));
- actions.add(CupertinoActionSheetAction(
- child: Text("ente"),
- isDestructiveAction: true,
- onPressed: () async {
- Navigator.of(context, rootNavigator: true).pop();
- await deleteFilesFromRemoteOnly(
- context, widget.selectedFiles.files.toList());
- _clearSelectedFiles();
- showShortToast("moved to trash");
- },
- ));
- actions.add(CupertinoActionSheetAction(
- child: Text("everywhere"),
- isDestructiveAction: true,
- onPressed: () async {
- Navigator.of(context, rootNavigator: true).pop();
- await deleteFilesFromEverywhere(
- context, widget.selectedFiles.files.toList());
- _clearSelectedFiles();
- },
- ));
- } else {
- actions.add(CupertinoActionSheetAction(
- child: Text("delete"),
- isDestructiveAction: true,
- onPressed: () async {
- Navigator.of(context, rootNavigator: true).pop();
- await deleteFilesFromEverywhere(
- context, widget.selectedFiles.files.toList());
- _clearSelectedFiles();
- },
- ));
- }
- final action = CupertinoActionSheet(
- title: Text("delete " +
- count.toString() +
- " file" +
- (count == 1 ? "" : "s") +
- (containsUploadedFile && containsLocalFile ? " from" : "?")),
- actions: actions,
- cancelButton: CupertinoActionSheetAction(
- child: Text("cancel"),
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop();
- },
- ),
- );
- showCupertinoModalPopup(context: context, builder: (_) => action);
- }
- void _clearSelectedFiles() {
- widget.selectedFiles.clearAll();
- }
- }
|