Merge Collaboration UI Improvements #833

Collaboration UI Improvements
This commit is contained in:
Neeraj Gupta 2023-01-31 23:32:24 +05:30 committed by GitHub
commit cf9e0e4ca7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 182 additions and 112 deletions

View file

@ -181,18 +181,17 @@ class CollectionActions {
showGenericErrorDialog(context: context);
}
return result == ButtonAction.first;
} else {
return false;
}
return false;
}
Future<bool?> addEmailToCollection(
// addEmailToCollection returns true if add operation was successful
Future<bool> addEmailToCollection(
BuildContext context,
Collection collection,
String email, {
CollectionParticipantRole role = CollectionParticipantRole.viewer,
String email,
CollectionParticipantRole role, {
bool showProgress = false,
String? publicKey,
}) async {
if (!isValidEmail(email)) {
await showErrorDialog(
@ -200,31 +199,29 @@ class CollectionActions {
"Invalid email address",
"Please enter a valid email address.",
);
return null;
} else if (email == Configuration.instance.getEmail()) {
return false;
} else if (email.trim() == Configuration.instance.getEmail()) {
await showErrorDialog(context, "Oops", "You cannot share with yourself");
return null;
return false;
}
ProgressDialog? dialog;
if (publicKey == null) {
if (showProgress) {
dialog = createProgressDialog(context, "Searching for user...");
await dialog.show();
}
try {
publicKey = await UserService.instance.getPublicKey(email);
await dialog?.hide();
} catch (e) {
await dialog?.hide();
logger.severe("Failed to get public key", e);
showGenericErrorDialog(context: context);
return false;
}
String? publicKey;
if (showProgress) {
dialog = createProgressDialog(context, "Sharing...", isDismissible: true);
await dialog.show();
}
// getPublicKey can return null
// ignore: unnecessary_null_comparison
try {
publicKey = await UserService.instance.getPublicKey(email);
} catch (e) {
await dialog?.hide();
logger.severe("Failed to get public key", e);
showGenericErrorDialog(context: context);
return false;
}
// getPublicKey can return null when no user is associated with given
// email id
if (publicKey == null || publicKey == '') {
// todo: neeraj replace this as per the design where a new screen
// is used for error. Do this change along with handling of network errors
@ -250,16 +247,8 @@ class CollectionActions {
),
],
);
return null;
return false;
} else {
if (showProgress) {
dialog = createProgressDialog(
context,
"Sharing...",
isDismissible: true,
);
await dialog.show();
}
try {
final newSharees = await CollectionsService.instance
.share(collection.id, email, publicKey, role);
@ -279,6 +268,7 @@ class CollectionActions {
}
}
// deleteCollectionSheet returns true if the album is successfully deleted
Future<bool> deleteCollectionSheet(
BuildContext bContext,
Collection collection,
@ -288,6 +278,13 @@ class CollectionActions {
if (collection.owner!.id != currentUserID) {
throw AssertionError("Can not delete album owned by others");
}
if (collection.hasSharees) {
final bool confirmDelete =
await _confirmSharedAlbumDeletion(bContext, collection);
if (!confirmDelete) {
return false;
}
}
final actionResult = await showActionSheet(
context: bContext,
buttons: [
@ -368,6 +365,37 @@ class CollectionActions {
return false;
}
// _confirmSharedAlbumDeletion should be shown when user tries to delete an
// album shared with other ente users.
Future<bool> _confirmSharedAlbumDeletion(
BuildContext context,
Collection collection,
) async {
final ButtonAction? result = await showActionSheet(
context: context,
buttons: [
const ButtonWidget(
buttonType: ButtonType.critical,
isInAlert: true,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first,
labelText: "Delete album",
),
const ButtonWidget(
buttonType: ButtonType.secondary,
buttonAction: ButtonAction.cancel,
isInAlert: true,
shouldStickToDarkTheme: true,
labelText: "Cancel",
)
],
title: "Delete shared album?",
body: "The album will be deleted for everyone\n\nYou will lose access to "
"shared photos in this album that are owned by others",
);
return result != null && result == ButtonAction.first;
}
/*
_moveFilesFromCurrentCollection removes the file from the current
collection. Based on the file and collection ownership, files will be

View file

@ -187,11 +187,11 @@ class _AddParticipantPage extends State<AddParticipantPage> {
context,
widget.collection,
emailToAdd,
role: widget.isAddingViewer
widget.isAddingViewer
? CollectionParticipantRole.viewer
: CollectionParticipantRole.collaborator,
);
if (result != null && result && mounted) {
if (result && mounted) {
Navigator.of(context).pop(true);
}
},

View file

@ -155,14 +155,15 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
currentUserID: currentUserID,
),
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: isOwner ? Icons.chevron_right : null,
trailingIconIsMuted: true,
onTap: () async {
if (isOwner) {
await _navigateToManageUser(currentUser);
}
},
onTap: isOwner
? () async {
if (isOwner) {
await _navigateToManageUser(currentUser);
}
}
: null,
isTopBorderRadiusRemoved: listIndex > 0,
isBottomBorderRadiusRemoved: true,
borderRadius: 8,
@ -183,7 +184,6 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
),
leadingIcon: Icons.add_outlined,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
onTap: () async {
await _navigateToAddUser(false);
},
@ -229,14 +229,15 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
currentUserID: currentUserID,
),
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: isOwner ? Icons.chevron_right : null,
trailingIconIsMuted: true,
onTap: () async {
if (isOwner) {
await _navigateToManageUser(currentUser);
}
},
onTap: isOwner
? () async {
if (isOwner) {
await _navigateToManageUser(currentUser);
}
}
: null,
isTopBorderRadiusRemoved: listIndex > 0,
isBottomBorderRadiusRemoved: !isLastItem,
borderRadius: 8,
@ -257,7 +258,6 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
),
leadingIcon: Icons.add_outlined,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
onTap: () async {
await _navigateToAddUser(true);
},

View file

@ -4,12 +4,16 @@ import 'package:photos/services/collections_service.dart';
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/ente_theme.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/button_widget.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
import 'package:photos/ui/components/divider_widget.dart';
import 'package:photos/ui/components/menu_item_widget.dart';
import 'package:photos/ui/components/menu_section_description_widget.dart';
import 'package:photos/ui/components/menu_section_title.dart';
import 'package:photos/ui/components/models/button_type.dart';
import 'package:photos/ui/components/title_bar_title_widget.dart';
import 'package:photos/utils/dialog_util.dart';
class ManageIndividualParticipant extends StatefulWidget {
final Collection collection;
@ -34,6 +38,7 @@ class _ManageIndividualParticipantState
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
final textTheme = getEnteTextTheme(context);
bool isConvertToViewSuccess = false;
return Scaffold(
appBar: AppBar(),
body: Padding(
@ -69,7 +74,6 @@ class _ManageIndividualParticipantState
),
leadingIcon: Icons.edit_outlined,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: widget.user.isCollaborator ? Icons.check : null,
onTap: widget.user.isCollaborator
? null
@ -79,10 +83,10 @@ class _ManageIndividualParticipantState
context,
widget.collection,
widget.user.email,
role: CollectionParticipantRole.collaborator,
CollectionParticipantRole.collaborator,
showProgress: true,
);
if ((result ?? false) && mounted) {
if (result && mounted) {
widget.user.role = CollectionParticipantRole
.collaborator
.toStringVal();
@ -102,23 +106,55 @@ class _ManageIndividualParticipantState
leadingIcon: Icons.photo_outlined,
leadingIconColor: getEnteColorScheme(context).strokeBase,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: widget.user.isViewer ? Icons.check : null,
onTap: widget.user.isViewer
? null
: () async {
final result =
await collectionActions.addEmailToCollection(
context,
widget.collection,
widget.user.email,
role: CollectionParticipantRole.viewer,
showProgress: true,
final ButtonAction? result = await showActionSheet(
context: context,
buttons: [
ButtonWidget(
buttonType: ButtonType.critical,
isInAlert: true,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first,
shouldSurfaceExecutionStates: true,
labelText: "Yes, convert to viewer",
onTap: () async {
isConvertToViewSuccess =
await collectionActions.addEmailToCollection(
context,
widget.collection,
widget.user.email,
CollectionParticipantRole.viewer,
);
},
),
const ButtonWidget(
buttonType: ButtonType.secondary,
buttonAction: ButtonAction.cancel,
isInAlert: true,
shouldStickToDarkTheme: true,
labelText: "Cancel",
)
],
title: "Change permissions?",
body:
'${widget.user.email} will not be able to add more photos to this album\n\nThey will still be able to remove existing photos added by them',
);
if ((result ?? false) && mounted) {
widget.user.role =
CollectionParticipantRole.viewer.toStringVal();
setState(() => {});
if (result != null) {
if (result == ButtonAction.error) {
showGenericErrorDialog(context: context);
}
if (result == ButtonAction.first &&
isConvertToViewSuccess &&
mounted) {
// reset value
isConvertToViewSuccess = false;
widget.user.role =
CollectionParticipantRole.viewer.toStringVal();
setState(() => {});
}
}
},
isTopBorderRadiusRemoved: true,
@ -138,7 +174,6 @@ class _ManageIndividualParticipantState
leadingIcon: Icons.not_interested_outlined,
leadingIconColor: warning500,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
onTap: () async {
final result = await collectionActions.removeParticipant(
context,

View file

@ -81,7 +81,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
),
alignCaptionedTextToLeft: true,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingWidget: Switch.adaptive(
value: widget.collection!.publicURLs?.firstOrNull
?.enableCollect ??
@ -153,7 +152,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
isBottomBorderRadiusRemoved: true,
isTopBorderRadiusRemoved: true,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingWidget: Switch.adaptive(
value: widget.collection!.publicURLs?.firstOrNull
?.enableDownload ??
@ -185,7 +183,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
alignCaptionedTextToLeft: true,
isTopBorderRadiusRemoved: true,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingWidget: Switch.adaptive(
value: widget.collection!.publicURLs?.firstOrNull
?.passwordEnabled ??
@ -224,7 +221,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
leadingIcon: Icons.remove_circle_outline,
leadingIconColor: warning500,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
onTap: () async {
final bool result = await sharingActions.disableUrl(
context,

View file

@ -72,7 +72,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
),
leadingIcon: Icons.add,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
borderRadius: 4.0,
isTopBorderRadiusRemoved: _sharees.isNotEmpty,
isBottomBorderRadiusRemoved: true,
@ -102,7 +101,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
),
leadingIcon: Icons.add,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
borderRadius: 4.0,
isTopBorderRadiusRemoved: _sharees.isNotEmpty,
onTap: () async {
@ -132,9 +130,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
height: 24,
),
MenuSectionTitle(
title: hasUrl
? "Public link enabled"
: (_sharees.isEmpty ? "Or share a link" : "Share a link"),
title: hasUrl ? "Public link enabled" : "Share a link",
iconData: Icons.public,
),
]);
@ -149,7 +145,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
leadingIcon: Icons.error_outline,
leadingIconColor: getEnteColorScheme(context).warning500,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
onTap: () async {},
isBottomBorderRadiusRemoved: true,
),
@ -169,7 +164,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
),
leadingIcon: Icons.copy,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
onTap: () async {
await Clipboard.setData(ClipboardData(text: url));
showShortToast(context, "Link copied to clipboard");
@ -187,7 +181,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
),
leadingIcon: Icons.adaptive.share,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
onTap: () async {
shareText(url);
},
@ -212,7 +205,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
leadingIcon: Icons.link,
trailingIcon: Icons.navigate_next,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIconIsMuted: true,
onTap: () async {
routeToPage(
@ -237,7 +229,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
),
leadingIcon: Icons.link,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
isBottomBorderRadiusRemoved: true,
onTap: () async {
final bool result =
@ -247,9 +238,17 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
}
},
),
DividerWidget(
dividerType: DividerType.menu,
bgColor: getEnteColorScheme(context).fillFaint,
_sharees.isEmpty
? const MenuSectionDescriptionWidget(
content: "Share with non-ente users",
)
: const SizedBox.shrink(),
const SizedBox(
height: 24,
),
const MenuSectionTitle(
title: "Collaborative link",
iconData: Icons.public,
),
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
@ -258,7 +257,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
),
leadingIcon: Icons.link,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
onTap: () async {
final bool result = await collectionActions.enableUrl(
context,
@ -270,15 +268,14 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
}
},
),
_sharees.isEmpty
? const MenuSectionDescriptionWidget(
content:
"Create a link to allow people to add and view photos in "
"your shared album without needing an ente app or account. Great for collecting event photos.",
)
: const SizedBox.shrink(),
]);
if (_sharees.isEmpty && !hasUrl) {
children.add(
const MenuSectionDescriptionWidget(
content:
"Links allow people without an ente account to view and add photos to your shared albums.",
),
);
}
}
return Scaffold(
@ -297,6 +294,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
padding:
const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
),
@ -335,7 +333,6 @@ class EmailItemWidget extends StatelessWidget {
),
leadingIconSize: 24,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIconIsMuted: true,
trailingIcon: Icons.chevron_right,
onTap: () async {
@ -361,7 +358,6 @@ class EmailItemWidget extends StatelessWidget {
),
leadingIcon: Icons.people_outline,
menuItemColor: getEnteColorScheme(context).fillFaint,
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIconIsMuted: true,
trailingIcon: Icons.chevron_right,
onTap: () async {

View file

@ -161,8 +161,6 @@ class _AppStorageViewerState extends State<AppStorageViewer> {
),
menuItemColor:
getEnteColorScheme(context).fillFaint,
pressedColor:
getEnteColorScheme(context).fillFaintPressed,
borderRadius: 8,
onTap: () async {
for (var pathItem in paths) {

View file

@ -18,6 +18,7 @@ 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/common/rename_dialog.dart';
import 'package:photos/ui/components/action_sheet_widget.dart';
import 'package:photos/ui/components/button_widget.dart';
import 'package:photos/ui/components/dialog_widget.dart';
import 'package:photos/ui/components/models/button_type.dart';
@ -138,21 +139,37 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
Future<dynamic> _leaveAlbum(BuildContext context) async {
final result = await showChoiceDialog(
context,
title: "Leave shared album",
body: "You will leave the album, and it will stop being visible to you",
firstButtonLabel: "Yes, leave",
isCritical: true,
firstButtonOnTap: () async {
await CollectionsService.instance.leaveAlbum(widget.collection!);
if (mounted) {
Navigator.of(context).pop();
}
},
final ButtonAction? result = await showActionSheet(
context: context,
buttons: [
ButtonWidget(
buttonType: ButtonType.critical,
isInAlert: true,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first,
shouldSurfaceExecutionStates: true,
labelText: "Leave album",
onTap: () async {
await CollectionsService.instance.leaveAlbum(widget.collection!);
},
),
const ButtonWidget(
buttonType: ButtonType.secondary,
buttonAction: ButtonAction.cancel,
isInAlert: true,
shouldStickToDarkTheme: true,
labelText: "Cancel",
)
],
title: "Leave shared album?",
body: "Photos added by you will be removed from the album",
);
if (result == ButtonAction.error) {
showGenericErrorDialog(context: context);
if (result != null && mounted) {
if (result == ButtonAction.error) {
showGenericErrorDialog(context: context);
} else if (result == ButtonAction.first) {
Navigator.of(context).pop();
}
}
}

View file

@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.7.16+416
version: 0.7.18+418
environment:
sdk: '>=2.17.0 <3.0.0'