Revamp Quick link options (#1299)

This commit is contained in:
Neeraj Gupta 2023-08-08 12:20:16 +05:30 committed by GitHub
commit 9ae658c954
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 256 additions and 92 deletions

View file

@ -432,6 +432,8 @@ class MessageLookup extends MessageLookupByLibrary {
"continueLabel": MessageLookupByLibrary.simpleMessage("Continue"),
"continueOnFreeTrial":
MessageLookupByLibrary.simpleMessage("Continue on free trial"),
"convertToAlbum":
MessageLookupByLibrary.simpleMessage("Convert to album"),
"copyEmailAddress":
MessageLookupByLibrary.simpleMessage("Copy email address"),
"copyLink": MessageLookupByLibrary.simpleMessage("Copy link"),

View file

@ -1,6 +1,7 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'intl/messages_all.dart';
// **************************************************************************
@ -5385,6 +5386,16 @@ class S {
);
}
/// `Convert to album`
String get convertToAlbum {
return Intl.message(
'Convert to album',
name: 'convertToAlbum',
desc: '',
args: [],
);
}
/// `Set cover`
String get setCover {
return Intl.message(

View file

@ -776,6 +776,7 @@
},
"deleteAll": "Delete All",
"renameAlbum": "Rename album",
"convertToAlbum": "Convert to album",
"setCover": "Set cover",
"@setCover": {
"description": "Text to set cover photo for an album"

View file

@ -115,8 +115,9 @@ class Collection {
return (magicMetadata.subType ?? 0) == subTypeDefaultHidden;
}
bool isSharedFilesCollection() {
return (magicMetadata.subType ?? 0) == subTypeSharedFilesCollection;
bool isQuickLinkCollection() {
return (magicMetadata.subType ?? 0) == subTypeSharedFilesCollection &&
!hasSharees;
}
List<User> getSharees() {

View file

@ -11,6 +11,7 @@ enum GalleryType {
ownedCollection,
searchResults,
locationTag,
quickLink,
}
extension GalleyTypeExtension on GalleryType {
@ -23,6 +24,7 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.searchResults:
case GalleryType.favorite:
case GalleryType.locationTag:
case GalleryType.quickLink:
return true;
case GalleryType.hidden:
@ -37,6 +39,7 @@ extension GalleyTypeExtension on GalleryType {
switch (this) {
case GalleryType.ownedCollection:
case GalleryType.uncategorized:
case GalleryType.quickLink:
return true;
case GalleryType.hidden:
@ -63,6 +66,7 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.localFolder:
case GalleryType.uncategorized:
case GalleryType.locationTag:
case GalleryType.quickLink:
return true;
case GalleryType.trash:
case GalleryType.archive:
@ -83,6 +87,7 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.hidden:
case GalleryType.localFolder:
case GalleryType.locationTag:
case GalleryType.quickLink:
return true;
case GalleryType.trash:
case GalleryType.sharedCollection:
@ -104,6 +109,7 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.localFolder:
case GalleryType.trash:
case GalleryType.sharedCollection:
case GalleryType.quickLink:
return false;
}
}
@ -112,6 +118,7 @@ extension GalleyTypeExtension on GalleryType {
switch (this) {
case GalleryType.ownedCollection:
case GalleryType.sharedCollection:
case GalleryType.quickLink:
return true;
case GalleryType.hidden:
case GalleryType.uncategorized:
@ -131,9 +138,11 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.ownedCollection:
case GalleryType.homepage:
case GalleryType.uncategorized:
case GalleryType.quickLink:
return true;
case GalleryType.hidden:
case GalleryType.favorite:
case GalleryType.searchResults:
case GalleryType.archive:
@ -157,9 +166,11 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.archive:
case GalleryType.uncategorized:
case GalleryType.locationTag:
case GalleryType.quickLink:
return true;
case GalleryType.hidden:
case GalleryType.localFolder:
case GalleryType.trash:
case GalleryType.favorite:
@ -182,6 +193,7 @@ extension GalleyTypeExtension on GalleryType {
return true;
case GalleryType.hidden:
case GalleryType.quickLink:
case GalleryType.favorite:
case GalleryType.archive:
case GalleryType.localFolder:

View file

@ -328,9 +328,9 @@ class CollectionsService {
final List<Collection> collections = getCollectionsForUI(includedShared: true);
for (final c in collections) {
if (c.owner!.id == Configuration.instance.getUserID()) {
if (c.hasSharees || c.hasLink && !c.isSharedFilesCollection()) {
if (c.hasSharees || c.hasLink && !c.isQuickLinkCollection()) {
outgoing.add(c);
} else if (c.isSharedFilesCollection()) {
} else if (c.isQuickLinkCollection()) {
quickLinks.add(c);
}
} else {
@ -375,7 +375,7 @@ class CollectionsService {
final List<Collection> rest = [];
for (final collection in collections) {
if (collection.type == CollectionType.uncategorized ||
collection.isSharedFilesCollection() ||
collection.isQuickLinkCollection() ||
collection.isHidden()) {
continue;
}
@ -638,7 +638,7 @@ class CollectionsService {
try {
// Note: when collection created to sharing few files is renamed
// convert that collection to a regular collection type.
if (collection.isSharedFilesCollection()) {
if (collection.isQuickLinkCollection()) {
await updateMagicMetadata(collection, {"subType": 0});
}
final encryptedName = CryptoUtil.encryptSync(

View file

@ -70,7 +70,12 @@ class CollectionActions {
shouldSurfaceExecutionStates: true,
labelText: S.of(context).yesRemove,
onTap: () async {
// for quickLink collection, we need to trash the collection
if(collection.isQuickLinkCollection() && !collection.hasSharees) {
await trashCollectionKeepingPhotos(collection, context);
} else {
await CollectionsService.instance.disableShareUrl(collection);
}
},
),
ButtonWidget(
@ -303,11 +308,7 @@ class CollectionActions {
isInAlert: true,
onTap: () async {
try {
final List<File> files =
await FilesDB.instance.getAllFilesCollection(collection.id);
await moveFilesFromCurrentCollection(bContext, collection, files);
// collection should be empty on server now
await collectionsService.trashEmptyCollection(collection);
await trashCollectionKeepingPhotos(collection, bContext);
} catch (e, s) {
logger.severe("Failed to keep photos & delete collection", e, s);
rethrow;
@ -363,6 +364,14 @@ class CollectionActions {
return false;
}
Future<void> trashCollectionKeepingPhotos(Collection collection, BuildContext bContext) async {
final List<File> files =
await FilesDB.instance.getAllFilesCollection(collection.id);
await moveFilesFromCurrentCollection(bContext, collection, files);
// collection should be empty on server now
await collectionsService.trashEmptyCollection(collection);
}
// _confirmSharedAlbumDeletion should be shown when user tries to delete an
// album shared with other ente users.
Future<bool> _confirmSharedAlbumDeletion(

View file

@ -262,7 +262,7 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
final List<Collection> pinned = [];
final List<Collection> unpinned = [];
for (final collection in collections) {
if (collection.isSharedFilesCollection() ||
if (collection.isQuickLinkCollection() ||
collection.type == CollectionType.favorites ||
collection.type == CollectionType.uncategorized) {
continue;

View file

@ -2,7 +2,9 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import "package:fast_base58/fast_base58.dart";
import 'package:flutter/material.dart';
import "package:flutter/services.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/models/api/collection/public_url.dart";
import 'package:photos/models/collection.dart';
@ -20,6 +22,7 @@ import 'package:photos/utils/crypto_util.dart';
import 'package:photos/utils/date_time_util.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/navigation_util.dart';
import "package:photos/utils/share_util.dart";
import 'package:photos/utils/toast_util.dart';
class ManageSharedLinkWidget extends StatefulWidget {
@ -50,6 +53,10 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
widget.collection!.publicURLs?.firstOrNull?.passwordEnabled ?? false;
final enteColorScheme = getEnteColorScheme(context);
final PublicURL url = widget.collection!.publicURLs!.firstOrNull!;
final String collectionKey = Base58Encode(
CollectionsService.instance.getCollectionKey(widget.collection!.id),
);
final String urlValue = "${url.url}#$collectionKey";
return Scaffold(
appBar: AppBar(
elevation: 0,
@ -226,6 +233,54 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
const SizedBox(
height: 24,
),
if (url.isExpired)
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).linkHasExpired,
textColor: getEnteColorScheme(context).warning500,
),
leadingIcon: Icons.error_outline,
leadingIconColor: getEnteColorScheme(context).warning500,
menuItemColor: getEnteColorScheme(context).fillFaint,
isBottomBorderRadiusRemoved: true,
),
if (!url.isExpired)
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).copyLink,
makeTextBold: true,
),
leadingIcon: Icons.copy,
menuItemColor: getEnteColorScheme(context).fillFaint,
showOnlyLoadingState: true,
onTap: () async {
await Clipboard.setData(ClipboardData(text: urlValue));
showShortToast(
context, S.of(context).linkCopiedToClipboard);
},
isBottomBorderRadiusRemoved: true,
),
if (!url.isExpired)
DividerWidget(
dividerType: DividerType.menu,
bgColor: getEnteColorScheme(context).fillFaint,
),
if (!url.isExpired)
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).sendLink,
makeTextBold: true,
),
leadingIcon: Icons.adaptive.share,
menuItemColor: getEnteColorScheme(context).fillFaint,
onTap: () async {
shareText(urlValue);
},
isTopBorderRadiusRemoved: true,
),
const SizedBox(
height: 24,
),
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).removeLink,
@ -243,6 +298,9 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
);
if (result && mounted) {
Navigator.of(context).pop();
if (widget.collection!.isQuickLinkCollection()) {
Navigator.of(context).pop();
}
}
},
),

View file

@ -124,6 +124,8 @@ class CollectionPage extends StatelessWidget {
return GalleryType.uncategorized;
} else if (c.type == CollectionType.favorites) {
return GalleryType.favorite;
} else if (c.isQuickLinkCollection()) {
return GalleryType.quickLink;
}
return appBarType;
}

View file

@ -26,6 +26,7 @@ import 'package:photos/ui/components/models/button_type.dart';
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';
import "package:photos/ui/sharing/manage_links_widget.dart";
import 'package:photos/ui/sharing/share_collection_page.dart';
import 'package:photos/ui/tools/free_space_page.dart';
import "package:photos/ui/viewer/gallery/hooks/add_photos_sheet.dart";
@ -68,6 +69,7 @@ enum AlbumPopupAction {
setCover,
addPhotos,
pinAlbum,
removeLink,
}
class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
@ -77,9 +79,12 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
String? _appBarTitle;
late CollectionActions collectionActions;
final GlobalKey shareButtonKey = GlobalKey();
bool isQuickLink = false;
late GalleryType galleryType;
@override
void initState() {
super.initState();
_selectedFilesListener = () {
setState(() {});
};
@ -90,7 +95,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
setState(() {});
});
_appBarTitle = widget.title;
super.initState();
galleryType = widget.type;
}
@override
@ -102,15 +107,15 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
@override
Widget build(BuildContext context) {
return widget.type == GalleryType.homepage
return galleryType == GalleryType.homepage
? const SizedBox.shrink()
: AppBar(
backgroundColor: widget.type == GalleryType.homepage
backgroundColor: galleryType == GalleryType.homepage
? const Color(0x00000000)
: null,
elevation: 0,
centerTitle: false,
title: widget.type == GalleryType.homepage
title: galleryType == GalleryType.homepage
? const SizedBox.shrink()
: TextButton(
child: Text(
@ -127,13 +132,22 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
Future<dynamic> _renameAlbum(BuildContext context) async {
if (widget.type != GalleryType.ownedCollection) {
if (galleryType != GalleryType.ownedCollection &&
galleryType != GalleryType.quickLink) {
showToast(
context,
'Type of galler $galleryType is not supported for '
'rename',
);
return;
}
final result = await showTextInputDialog(
context,
title: S.of(context).renameAlbum,
submitButtonLabel: S.of(context).rename,
title: isQuickLink
? S.of(context).enterAlbumName
: S.of(context).renameAlbum,
submitButtonLabel:
isQuickLink ? S.of(context).done : S.of(context).rename,
hintText: S.of(context).enterAlbumName,
alwaysShowSuccessState: true,
initialValue: widget.collection?.displayName ?? "",
@ -148,6 +162,11 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
await CollectionsService.instance.rename(widget.collection!, text);
if (mounted) {
_appBarTitle = text;
if (isQuickLink) {
// update the gallery type to owned collection so that correct
// actions are shown
galleryType = GalleryType.ownedCollection;
}
setState(() {});
}
} catch (e, s) {
@ -261,10 +280,12 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
return actions;
}
final int userID = Configuration.instance.getUserID()!;
if ((widget.type == GalleryType.ownedCollection ||
widget.type == GalleryType.sharedCollection) &&
isQuickLink = widget.collection?.isQuickLinkCollection() ?? false;
if ((galleryType == GalleryType.ownedCollection ||
galleryType == GalleryType.sharedCollection ||
isQuickLink) &&
widget.collection?.type != CollectionType.favorites) {
final bool canAddFiles = widget.type == GalleryType.ownedCollection ||
final bool canAddFiles = galleryType == GalleryType.ownedCollection ||
widget.collection!.getRole(userID) ==
CollectionParticipantRole.collaborator;
if (canAddFiles) {
@ -284,7 +305,11 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
Tooltip(
message: "Share",
child: IconButton(
icon: const Icon(Icons.people_outlined),
icon: Icon(
isQuickLink && (widget.collection!.hasLink) ? Icons
.link_outlined : Icons
.people_outlined,
),
onPressed: () async {
await _showShareCollectionDialog();
},
@ -293,22 +318,27 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
);
}
final List<PopupMenuItem<AlbumPopupAction>> items = [];
if (widget.type == GalleryType.ownedCollection) {
if (galleryType == GalleryType.ownedCollection || isQuickLink) {
if (widget.collection!.type != CollectionType.favorites) {
items.add(
PopupMenuItem(
value: AlbumPopupAction.rename,
child: Row(
children: [
const Icon(Icons.edit),
Icon(isQuickLink ? Icons.photo_album_outlined : Icons.edit),
const Padding(
padding: EdgeInsets.all(8),
),
Text(S.of(context).renameAlbum),
Text(
isQuickLink
? S.of(context).convertToAlbum
: S.of(context).renameAlbum,
),
],
),
),
);
if (!isQuickLink) {
items.add(
PopupMenuItem(
value: AlbumPopupAction.setCover,
@ -324,8 +354,10 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
),
);
}
if (widget.type == GalleryType.ownedCollection ||
widget.type == GalleryType.sharedCollection) {
}
if (galleryType == GalleryType.ownedCollection ||
galleryType == GalleryType.sharedCollection ||
isQuickLink) {
items.add(
PopupMenuItem(
value: AlbumPopupAction.map,
@ -361,7 +393,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
),
),
);
if (!isQuickLink) {
items.add(
PopupMenuItem(
value: AlbumPopupAction.pinAlbum,
@ -385,7 +417,9 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
),
),
);
}
if (!isQuickLink) {
items.add(
PopupMenuItem(
value: AlbumPopupAction.ownedArchive,
@ -405,17 +439,28 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
),
);
}
}
if (widget.collection!.type != CollectionType.favorites) {
items.add(
PopupMenuItem(
value: AlbumPopupAction.delete,
value: isQuickLink
? AlbumPopupAction.removeLink
: AlbumPopupAction.delete,
child: Row(
children: [
const Icon(Icons.delete_outline),
Icon(
isQuickLink
? Icons.remove_circle_outline
: Icons.delete_outline,
),
const Padding(
padding: EdgeInsets.all(8),
),
Text(S.of(context).deleteAlbum),
Text(
isQuickLink
? S.of(context).removeLink
: S.of(context).deleteAlbum,
),
],
),
),
@ -423,7 +468,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
} // ownedCollection open ends
if (widget.type == GalleryType.sharedCollection) {
if (galleryType == GalleryType.sharedCollection) {
final bool hasShareeArchived = widget.collection!.hasShareeArchived();
items.add(
PopupMenuItem(
@ -460,7 +505,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
),
);
}
if (widget.type == GalleryType.localFolder) {
if (galleryType == GalleryType.localFolder) {
items.add(
PopupMenuItem(
value: AlbumPopupAction.freeUpSpace,
@ -502,6 +547,8 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
);
} else if (value == AlbumPopupAction.delete) {
await _trashCollection();
} else if (value == AlbumPopupAction.removeLink) {
await _removeQuickLink();
} else if (value == AlbumPopupAction.leave) {
await _leaveAlbum(context);
} else if (value == AlbumPopupAction.freeUpSpace) {
@ -536,8 +583,10 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
Future<void> setCoverPhoto(BuildContext context) async {
final int? coverPhotoID = await showPickCoverPhotoSheet(context, widget
.collection!,);
final int? coverPhotoID = await showPickCoverPhotoSheet(
context,
widget.collection!,
);
if (coverPhotoID != null) {
changeCoverPhoto(context, widget.collection!, coverPhotoID);
}
@ -619,21 +668,40 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
}
Future<void> _removeQuickLink() async {
try {
final bool result =
await CollectionActions(CollectionsService.instance).disableUrl(
context,
widget.collection!,
);
if (result && mounted) {
Navigator.of(context).pop();
}
} catch (e, s) {
_logger.severe("failed to trash collection", e, s);
showGenericErrorDialog(context: context);
}
}
Future<void> _showShareCollectionDialog() async {
final collection = widget.collection;
try {
if (collection == null ||
(widget.type != GalleryType.ownedCollection &&
widget.type != GalleryType.sharedCollection)) {
(galleryType != GalleryType.ownedCollection &&
galleryType != GalleryType.sharedCollection &&
!isQuickLink)) {
throw Exception(
"Cannot share empty collection of typex ${widget.type}",
"Cannot share empty collection of type $galleryType",
);
}
if (Configuration.instance.getUserID() == widget.collection!.owner!.id) {
unawaited(
routeToPage(
context,
ShareCollectionPage(collection),
(isQuickLink && (collection.hasLink)) ? ManageSharedLinkWidget(collection: collection!) :
ShareCollectionPage
(collection),
),
);
} else {