From d2282680fd95797f45ccd99475f7959ec2ac7b17 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Mon, 30 Dec 2024 09:12:16 -0800 Subject: [PATCH 1/9] feat: initial refactor for comment bottom sheet --- lib/comment/enums/comment_action.dart | 2 + .../widgets/comment_action_bottom_sheet.dart | 171 +++ lib/comment/widgets/comment_card.dart | 32 +- .../general_comment_action_bottom_sheet.dart | 248 ++++ lib/post/utils/comment_action_helpers.dart | 1225 +++++++++-------- lib/post/widgets/comment_card.dart | 52 +- lib/shared/comment_card_actions.dart | 18 +- 7 files changed, 1105 insertions(+), 643 deletions(-) create mode 100644 lib/comment/widgets/comment_action_bottom_sheet.dart create mode 100644 lib/comment/widgets/general_comment_action_bottom_sheet.dart diff --git a/lib/comment/enums/comment_action.dart b/lib/comment/enums/comment_action.dart index 78672e506..f112da323 100644 --- a/lib/comment/enums/comment_action.dart +++ b/lib/comment/enums/comment_action.dart @@ -6,6 +6,8 @@ enum CommentAction { save(permissionType: PermissionType.user), delete(permissionType: PermissionType.user), report(permissionType: PermissionType.user), + reply(permissionType: PermissionType.user), + edit(permissionType: PermissionType.user), read(permissionType: PermissionType.user), // This is used for inbox items (replies/mentions) /// Moderator level post actions diff --git a/lib/comment/widgets/comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_action_bottom_sheet.dart new file mode 100644 index 000000000..7eaa42753 --- /dev/null +++ b/lib/comment/widgets/comment_action_bottom_sheet.dart @@ -0,0 +1,171 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:back_button_interceptor/back_button_interceptor.dart'; +import 'package:lemmy_api_client/v3.dart'; + +import 'package:thunder/comment/enums/comment_action.dart'; +import 'package:thunder/comment/widgets/general_comment_action_bottom_sheet.dart'; +import 'package:thunder/community/enums/community_action.dart'; +import 'package:thunder/community/widgets/post_card_metadata.dart'; +import 'package:thunder/core/enums/full_name.dart'; +import 'package:thunder/user/enums/user_action.dart'; +import 'package:thunder/utils/instance.dart'; + +/// Programatically show the comment action bottom sheet +void showCommentActionBottomModalSheet( + BuildContext context, + CommentView commentView, { + GeneralCommentAction page = GeneralCommentAction.general, + void Function({CommentAction? commentAction, UserAction? userAction, CommunityAction? communityAction, required CommentView commentView, dynamic value})? onAction, +}) { + showModalBottomSheet( + context: context, + showDragHandle: true, + isScrollControlled: true, + builder: (_) => CommentActionBottomSheet(context: context, initialPage: page, commentView: commentView, onAction: onAction), + ); +} + +class CommentActionBottomSheet extends StatefulWidget { + const CommentActionBottomSheet({super.key, required this.context, required this.commentView, this.initialPage = GeneralCommentAction.general, required this.onAction}); + + /// The parent context + final BuildContext context; + + /// The comment that is being acted on + final CommentView commentView; + + /// The initial page of the bottom sheet + final GeneralCommentAction initialPage; + + /// The callback that is called when an action is performed + final void Function({CommentAction? commentAction, UserAction? userAction, CommunityAction? communityAction, required CommentView commentView, dynamic value})? onAction; + + @override + State createState() => _CommentActionBottomSheetState(); +} + +class _CommentActionBottomSheetState extends State { + GeneralCommentAction currentPage = GeneralCommentAction.general; + + FutureOr _handleBack(bool stopDefaultButtonEvent, RouteInfo routeInfo) { + if (currentPage != GeneralCommentAction.general) { + setState(() => currentPage = GeneralCommentAction.general); + return true; + } + + return false; + } + + @override + void initState() { + super.initState(); + currentPage = widget.initialPage; + BackButtonInterceptor.add(_handleBack); + } + + @override + void dispose() { + BackButtonInterceptor.remove(_handleBack); + super.dispose(); + } + + String? generateSubtitle(GeneralCommentAction page) { + CommentView commentView = widget.commentView; + + String? communityInstance = fetchInstanceNameFromUrl(commentView.community.actorId); + String? userInstance = fetchInstanceNameFromUrl(commentView.creator.actorId); + + switch (page) { + case GeneralCommentAction.user: + return generateUserFullName(context, commentView.creator.name, commentView.creator.displayName, fetchInstanceNameFromUrl(commentView.creator.actorId)); + case GeneralCommentAction.instance: + return (communityInstance == userInstance) ? '$communityInstance' : '$communityInstance • $userInstance'; + default: + return null; + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + Widget actions = switch (currentPage) { + GeneralCommentAction.general => GeneralCommentActionBottomSheetPage( + context: widget.context, + commentView: widget.commentView, + onSwitchActivePage: (page) => setState(() => currentPage = page), + onAction: (CommentAction commentAction, CommentView? updatedCommentView, dynamic value) { + widget.onAction?.call(commentAction: commentAction, commentView: widget.commentView, value: value); + }, + ), + _ => SizedBox(), + + // GeneralCommentAction.post => PostPostActionBottomSheet( + // context: widget.context, + // postViewMedia: widget.commentView, + // onAction: (PostAction postAction, PostViewMedia? updatedPostViewMedia) { + // widget.onAction?.call(postAction: postAction, postViewMedia: widget.commentView); + // }, + // ), + // GeneralCommentAction.user => UserPostActionBottomSheet( + // context: widget.context, + // postViewMedia: widget.commentView, + // onAction: (UserAction userAction, PersonView? updatedPersonView) { + // widget.onAction?.call(userAction: userAction, postViewMedia: widget.commentView); + // }, + // ), + // GeneralCommentAction.instance => InstancePostActionBottomSheet( + // postViewMedia: widget.commentView, + // onAction: () {}, + // ), + // GeneralCommentAction.share => SharePostActionBottomSheet( + // context: widget.context, + // postViewMedia: widget.commentView, + // onAction: () {}, + // ), + }; + + return SafeArea( + child: AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOutCubicEmphasized, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + currentPage != GeneralCommentAction.general + ? IconButton(onPressed: () => setState(() => currentPage = GeneralCommentAction.general), icon: const Icon(Icons.chevron_left_rounded)) + : const SizedBox(width: 12.0), + Wrap( + direction: Axis.vertical, + children: [ + Text(currentPage.title, style: theme.textTheme.titleLarge), + if (currentPage != GeneralCommentAction.general && currentPage != GeneralCommentAction.share && currentPage != GeneralCommentAction.comment) + Text(generateSubtitle(currentPage) ?? ''), + ], + ), + ], + ), + if (currentPage == GeneralCommentAction.general) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0), + child: LanguagePostCardMetaData(languageId: widget.commentView.comment.languageId), + ), + const SizedBox(height: 16.0), + actions, + ], + ), + ), + ), + ); + } +} diff --git a/lib/comment/widgets/comment_card.dart b/lib/comment/widgets/comment_card.dart index 835ccebf8..9602a850f 100644 --- a/lib/comment/widgets/comment_card.dart +++ b/lib/comment/widgets/comment_card.dart @@ -4,13 +4,13 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:lemmy_api_client/v3.dart'; -import 'package:thunder/comment/utils/navigate_comment.dart'; +import 'package:thunder/comment/utils/navigate_comment.dart'; +import 'package:thunder/comment/widgets/comment_action_bottom_sheet.dart'; import 'package:thunder/core/auth/bloc/auth_bloc.dart'; import 'package:thunder/core/enums/nested_comment_indicator.dart'; import 'package:thunder/core/enums/swipe_action.dart'; import 'package:thunder/post/bloc/post_bloc.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/post/utils/comment_actions.dart'; import 'package:thunder/shared/comment_content.dart'; import 'package:thunder/shared/text/scalable_text.dart'; @@ -306,20 +306,20 @@ class _CommentCardState extends State with SingleTickerProviderStat showCommentActionBottomModalSheet( context, widget.commentView, - widget.onSaveAction ?? () {}, - widget.onDeleteAction ?? () {}, - widget.onVoteAction ?? () {}, - (CommentView commentView, bool isEdit) { - return navigateToCreateCommentPage( - context, - commentView: isEdit ? commentView : null, - parentCommentView: isEdit ? null : commentView, - onCommentSuccess: (commentView, isEdit) => widget.onReplyEditAction?.call(commentView, isEdit), - ); - }, - widget.onReportAction ?? () {}, - () => setState(() => viewSource = !viewSource), - viewSource, + // widget.onSaveAction ?? () {}, + // widget.onDeleteAction ?? () {}, + // widget.onVoteAction ?? () {}, + // (CommentView commentView, bool isEdit) { + // return navigateToCreateCommentPage( + // context, + // commentView: isEdit ? commentView : null, + // parentCommentView: isEdit ? null : commentView, + // onCommentSuccess: (commentView, isEdit) => widget.onReplyEditAction?.call(commentView, isEdit), + // ); + // }, + // widget.onReportAction ?? () {}, + // () => setState(() => viewSource = !viewSource), + // viewSource, ); }, onTap: () { diff --git a/lib/comment/widgets/general_comment_action_bottom_sheet.dart b/lib/comment/widgets/general_comment_action_bottom_sheet.dart new file mode 100644 index 000000000..e39f267ed --- /dev/null +++ b/lib/comment/widgets/general_comment_action_bottom_sheet.dart @@ -0,0 +1,248 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lemmy_api_client/v3.dart'; + +import 'package:thunder/comment/enums/comment_action.dart'; +import 'package:thunder/core/auth/bloc/auth_bloc.dart'; +import 'package:thunder/core/enums/full_name.dart'; +import 'package:thunder/post/enums/post_action.dart'; +import 'package:thunder/post/widgets/post_action_bottom_sheet.dart'; +import 'package:thunder/shared/bottom_sheet_action.dart'; +import 'package:thunder/shared/multi_picker_item.dart'; +import 'package:thunder/thunder/bloc/thunder_bloc.dart'; +import 'package:thunder/utils/instance.dart'; + +/// Defines the general actions that can be taken on a comment +enum GeneralCommentAction { + general(icon: Icons.more_horiz), + comment(icon: Icons.comment_rounded), + user(icon: Icons.person_rounded), + instance(icon: Icons.language_rounded), + share(icon: Icons.share); + + String get name => switch (this) { + GeneralCommentAction.general => l10n.actions, + GeneralCommentAction.comment => l10n.comment, + GeneralCommentAction.user => l10n.user, + GeneralCommentAction.instance => l10n.instance(1), + GeneralCommentAction.share => l10n.share, + }; + + /// The title to use for the action. This is shown when the given page is active + String get title => switch (this) { + GeneralCommentAction.general => l10n.actions, + GeneralCommentAction.comment => 'Comment Actions', + GeneralCommentAction.user => l10n.userActions, + GeneralCommentAction.instance => l10n.instanceActions, + GeneralCommentAction.share => l10n.share, + }; + + /// The icon to use for the action + final IconData icon; + + const GeneralCommentAction({required this.icon}); +} + +enum GeneralQuickCommentAction { + upvote(enabledIcon: Icons.arrow_upward_rounded, disabledIcon: Icons.arrow_upward_rounded, permissionType: PermissionType.user, requiresAuthentication: true), + downvote(enabledIcon: Icons.arrow_downward_rounded, disabledIcon: Icons.arrow_downward_rounded, permissionType: PermissionType.user, requiresAuthentication: true), + save(enabledIcon: Icons.star_rounded, disabledIcon: Icons.star_outline_rounded, permissionType: PermissionType.user, requiresAuthentication: true), + reply(enabledIcon: Icons.reply_rounded, disabledIcon: Icons.reply_outlined, permissionType: PermissionType.user, requiresAuthentication: true), + edit(enabledIcon: Icons.edit_rounded, disabledIcon: Icons.edit_outlined, permissionType: PermissionType.user, requiresAuthentication: true), + ; + + /// The icon to use for the action when it is enabled + final IconData enabledIcon; + + /// The icon to use for the action when it is disabled + final IconData disabledIcon; + + /// The permission type to use for the action + final PermissionType permissionType; + + /// Whether or not the action requires user authentication + final bool requiresAuthentication; + + const GeneralQuickCommentAction({required this.enabledIcon, required this.disabledIcon, required this.permissionType, required this.requiresAuthentication}); +} + +/// Defines the general top-level actions that can be taken on a comment. +/// Given a [commentView] and a [onSwitchActivePage] callback, this widget will display a list of actions that can be taken on the comment. +class GeneralCommentActionBottomSheetPage extends StatefulWidget { + const GeneralCommentActionBottomSheetPage({super.key, required this.context, required this.commentView, required this.onSwitchActivePage, required this.onAction}); + + /// The outer context + final BuildContext context; + + /// The comment information + final CommentView commentView; + + /// Called when the active page is changed + final Function(GeneralCommentAction page) onSwitchActivePage; + + /// Called when an action is selected + final Function(CommentAction commentAction, CommentView? commentView, dynamic value) onAction; + + @override + State createState() => _GeneralCommentActionBottomSheetPageState(); +} + +class _GeneralCommentActionBottomSheetPageState extends State { + String? generateSubtitle(GeneralCommentAction page) { + CommentView commentView = widget.commentView; + + String? communityInstance = fetchInstanceNameFromUrl(commentView.community.actorId); + String? userInstance = fetchInstanceNameFromUrl(commentView.creator.actorId); + + switch (page) { + case GeneralCommentAction.user: + return generateUserFullName(context, commentView.creator.name, commentView.creator.displayName, fetchInstanceNameFromUrl(commentView.creator.actorId)); + case GeneralCommentAction.instance: + return (communityInstance == userInstance) ? '$communityInstance' : '$communityInstance • $userInstance'; + default: + return null; + } + } + + void performAction(GeneralQuickCommentAction action) { + final commentView = widget.commentView; + + switch (action) { + case GeneralQuickCommentAction.upvote: + widget.onAction(CommentAction.vote, commentView, commentView.myVote == 1 ? 0 : 1); + // widget.context.read().add(FeedItemActionedEvent(postAction: PostAction.vote, postId: postViewMedia.postView.post.id, value: postViewMedia.postView.myVote == 1 ? 0 : 1)); + break; + case GeneralQuickCommentAction.downvote: + break; + case GeneralQuickCommentAction.save: + break; + case GeneralQuickCommentAction.reply: + break; + case GeneralQuickCommentAction.edit: + break; + } + + Navigator.of(context).pop(); + } + + IconData getIcon(GeneralQuickCommentAction action) { + final commentView = widget.commentView; + + switch (action) { + case GeneralQuickCommentAction.upvote: + return commentView.myVote == 1 ? GeneralQuickCommentAction.upvote.enabledIcon : GeneralQuickCommentAction.upvote.disabledIcon; + case GeneralQuickCommentAction.downvote: + return commentView.myVote == -1 ? GeneralQuickCommentAction.downvote.enabledIcon : GeneralQuickCommentAction.downvote.disabledIcon; + case GeneralQuickCommentAction.save: + return commentView.saved ? GeneralQuickCommentAction.save.enabledIcon : GeneralQuickCommentAction.save.disabledIcon; + case GeneralQuickCommentAction.reply: + return GeneralQuickCommentAction.reply.enabledIcon; + case GeneralQuickCommentAction.edit: + return GeneralQuickCommentAction.edit.enabledIcon; + } + } + + String getLabel(GeneralQuickCommentAction action) { + final commentView = widget.commentView; + + switch (action) { + case GeneralQuickCommentAction.upvote: + return commentView.myVote == 1 ? l10n.upvoted : l10n.upvote; + case GeneralQuickCommentAction.downvote: + return commentView.myVote == -1 ? l10n.downvoted : l10n.downvote; + case GeneralQuickCommentAction.save: + return commentView.saved ? l10n.saved : l10n.save; + case GeneralQuickCommentAction.reply: + return l10n.reply(1); + case GeneralQuickCommentAction.edit: + return l10n.edit; + } + } + + Color? getBackgroundColor(GeneralQuickCommentAction action) { + final state = context.read().state; + + switch (action) { + case GeneralQuickCommentAction.upvote: + return state.upvoteColor.color; + case GeneralQuickCommentAction.downvote: + return state.downvoteColor.color; + case GeneralQuickCommentAction.save: + return state.saveColor.color; + case GeneralQuickCommentAction.reply: + return state.replyColor.color; + case GeneralQuickCommentAction.edit: + return state.replyColor.color; + } + } + + Color? getForegroundColor(GeneralQuickCommentAction action) { + final state = context.read().state; + final commentView = widget.commentView; + + switch (action) { + case GeneralQuickCommentAction.upvote: + return commentView.myVote == 1 ? state.upvoteColor.color : null; + case GeneralQuickCommentAction.downvote: + return commentView.myVote == -1 ? state.downvoteColor.color : null; + case GeneralQuickCommentAction.save: + return commentView.saved ? state.saveColor.color : null; + default: + return null; + } + } + + @override + Widget build(BuildContext context) { + final authState = context.read().state; + final isLoggedIn = authState.isLoggedIn; + + List quickActions = GeneralQuickCommentAction.values.where((element) => element.permissionType == PermissionType.user).toList(); + + if (!isLoggedIn) { + quickActions = quickActions.where((action) => action.requiresAuthentication == false).toList(); + } else { + // Hide downvoted if instance does not support it + if (!authState.downvotesEnabled) { + quickActions = quickActions.where((action) => action != GeneralQuickCommentAction.downvote).toList(); + } + + // Hide edit if the comment is not made by the current user + if (widget.commentView.creator.actorId != authState.account?.actorId) { + quickActions = quickActions.where((action) => action != GeneralQuickCommentAction.edit).toList(); + } + } + + // Determine the available sub-menus to display + List submenus = GeneralCommentAction.values.where((page) => page != GeneralCommentAction.general).toList(); + + return Column( + children: [ + if (quickActions.isNotEmpty) + MultiPickerItem( + pickerItems: quickActions + .map((generalQuickCommentAction) => PickerItemData( + icon: getIcon(generalQuickCommentAction), + label: getLabel(generalQuickCommentAction), + foregroundColor: getForegroundColor(generalQuickCommentAction), + backgroundColor: getBackgroundColor(generalQuickCommentAction), + onSelected: isLoggedIn ? () => performAction(generalQuickCommentAction) : null, + )) + .toList(), + ), + ...submenus + .map( + (page) => BottomSheetAction( + leading: Icon(page.icon), + trailing: const Icon(Icons.chevron_right_rounded), + title: page.name, + subtitle: generateSubtitle(page), + onTap: () => widget.onSwitchActivePage(page), + ), + ) + .toList() as List, + ], + ); + } +} diff --git a/lib/post/utils/comment_action_helpers.dart b/lib/post/utils/comment_action_helpers.dart index 8b9a3723a..1228e68a7 100644 --- a/lib/post/utils/comment_action_helpers.dart +++ b/lib/post/utils/comment_action_helpers.dart @@ -1,622 +1,629 @@ -import 'dart:async'; +// import 'dart:async'; + +// import 'package:back_button_interceptor/back_button_interceptor.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter/services.dart'; +// import 'package:flutter_bloc/flutter_bloc.dart'; +// import 'package:lemmy_api_client/v3.dart'; +// import 'package:share_plus/share_plus.dart'; +// import 'package:thunder/account/models/user_label.dart'; +// import 'package:thunder/comment/utils/comment.dart'; +// import 'package:thunder/community/widgets/post_card_metadata.dart'; +// import 'package:thunder/core/enums/full_name.dart'; +// import 'package:thunder/core/singletons/lemmy_client.dart'; +// import 'package:thunder/feed/utils/utils.dart'; +// import 'package:thunder/feed/view/feed_page.dart'; +// import 'package:thunder/instance/bloc/instance_bloc.dart'; +// import 'package:thunder/instance/enums/instance_action.dart'; +// import 'package:thunder/modlog/utils/navigate_modlog.dart'; +// import 'package:thunder/post/bloc/post_bloc.dart'; +// import 'package:thunder/post/utils/user_label_utils.dart'; +// import 'package:thunder/post/widgets/report_comment_dialog.dart'; +// import 'package:thunder/shared/multi_picker_item.dart'; +// import 'package:thunder/shared/picker_item.dart'; +// import 'package:thunder/shared/snackbar.dart'; +// import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +// import 'package:thunder/shared/text/selectable_text_modal.dart'; +// import 'package:thunder/thunder/bloc/thunder_bloc.dart'; +// import 'package:thunder/user/bloc/user_bloc.dart'; +// import 'package:thunder/user/enums/user_action.dart'; +// import 'package:thunder/utils/global_context.dart'; +// import 'package:thunder/utils/instance.dart'; +// import 'package:thunder/instance/utils/navigate_instance.dart'; + +// import '../../core/auth/bloc/auth_bloc.dart'; + +// enum CommentCardAction { +// save, +// share, +// shareLink, +// shareLinkLocal, +// delete, +// upvote, +// downvote, +// reply, +// edit, +// textActions, +// selectText, +// copyText, +// viewSource, +// viewModlog, +// report, +// userActions, +// visitProfile, +// blockUser, +// userLabel, +// instanceActions, +// visitInstance, +// blockInstance, +// } + +// class ExtendedCommentCardActions { +// const ExtendedCommentCardActions({ +// required this.commentCardAction, +// required this.icon, +// this.getTrailingIcon, +// required this.label, +// this.getColor, +// this.getForegroundColor, +// this.getOverrideIcon, +// this.getOverrideLabel, +// this.getSubtitleLabel, +// this.shouldShow, +// this.shouldEnable, +// }); + +// final CommentCardAction commentCardAction; +// final IconData icon; +// final IconData Function()? getTrailingIcon; +// final String label; +// final Color Function(BuildContext context)? getColor; +// final Color? Function(BuildContext context, CommentView commentView)? getForegroundColor; +// final IconData? Function(CommentView commentView)? getOverrideIcon; +// final String Function(BuildContext context, CommentView commentView, bool viewSource)? getOverrideLabel; +// final String Function(BuildContext context, CommentView commentView)? getSubtitleLabel; +// final bool Function(BuildContext context, CommentView commentView)? shouldShow; +// final bool Function(bool isUserLoggedIn)? shouldEnable; +// } + +// final List commentCardDefaultActionItems = [ +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.userActions, +// icon: Icons.person_rounded, +// label: l10n.user, +// getSubtitleLabel: (context, commentView) => generateUserFullName( +// context, +// commentView.creator.name, +// commentView.creator.displayName, +// fetchInstanceNameFromUrl(commentView.creator.actorId), +// ), +// getTrailingIcon: () => Icons.chevron_right_rounded, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.visitProfile, +// icon: Icons.person_search_rounded, +// label: AppLocalizations.of(GlobalContext.context)!.visitUserProfile, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.blockUser, +// icon: Icons.block, +// label: AppLocalizations.of(GlobalContext.context)!.blockUser, +// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.userLabel, +// icon: Icons.label_rounded, +// label: AppLocalizations.of(GlobalContext.context)!.addUserLabel, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.instanceActions, +// icon: Icons.language_rounded, +// label: l10n.instance(1), +// getSubtitleLabel: (context, postView) => fetchInstanceNameFromUrl(postView.creator.actorId) ?? '', +// getTrailingIcon: () => Icons.chevron_right_rounded, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.visitInstance, +// icon: Icons.language, +// label: AppLocalizations.of(GlobalContext.context)!.visitInstance, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.blockInstance, +// icon: Icons.block_rounded, +// label: AppLocalizations.of(GlobalContext.context)!.blockInstance, +// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.textActions, +// icon: Icons.comment_rounded, +// label: l10n.textActions, +// getTrailingIcon: () => Icons.chevron_right_rounded, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.selectText, +// icon: Icons.select_all_rounded, +// label: l10n.selectText, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.copyText, +// icon: Icons.copy_rounded, +// label: AppLocalizations.of(GlobalContext.context)!.copyComment, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.viewSource, +// icon: Icons.edit_document, +// label: l10n.viewCommentSource, +// getOverrideLabel: (context, commentView, viewSource) => viewSource ? l10n.viewOriginal : l10n.viewCommentSource, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.viewModlog, +// icon: Icons.shield_rounded, +// label: AppLocalizations.of(GlobalContext.context)!.viewModlog, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.report, +// icon: Icons.report_outlined, +// label: AppLocalizations.of(GlobalContext.context)!.reportComment, +// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.shareLink, +// icon: Icons.share_rounded, +// label: l10n.shareComment, +// getSubtitleLabel: (context, commentView) => commentView.comment.apId, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.shareLinkLocal, +// icon: Icons.share_rounded, +// label: l10n.shareCommentLocal, +// getSubtitleLabel: (context, commentView) => LemmyClient.instance.generateCommentUrl(commentView.comment.id), +// ), +// ]; + +// final List commentCardDefaultMultiActionItems = [ +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.upvote, +// label: AppLocalizations.of(GlobalContext.context)!.upvote, +// icon: Icons.arrow_upward_rounded, +// getColor: (context) => context.read().state.upvoteColor.color, +// getForegroundColor: (context, commentView) => commentView.myVote == 1 ? context.read().state.upvoteColor.color : null, +// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.downvote, +// label: AppLocalizations.of(GlobalContext.context)!.downvote, +// icon: Icons.arrow_downward_rounded, +// getColor: (context) => context.read().state.downvoteColor.color, +// getForegroundColor: (context, commentView) => commentView.myVote == -1 ? context.read().state.downvoteColor.color : null, +// shouldShow: (context, commentView) => context.read().state.downvotesEnabled, +// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.save, +// label: AppLocalizations.of(GlobalContext.context)!.save, +// icon: Icons.star_border_rounded, +// getColor: (context) => context.read().state.saveColor.color, +// getForegroundColor: (context, commentView) => commentView.saved ? context.read().state.saveColor.color : null, +// getOverrideIcon: (commentView) => commentView.saved ? Icons.star_rounded : null, +// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.reply, +// label: AppLocalizations.of(GlobalContext.context)!.reply(0), +// icon: Icons.reply_rounded, +// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.edit, +// label: AppLocalizations.of(GlobalContext.context)!.edit, +// icon: Icons.edit, +// shouldShow: (context, commentView) => commentView.creator.id == context.read().state.account?.userId, +// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, +// ), +// ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.share, +// icon: Icons.share_rounded, +// label: l10n.share, +// ), +// ]; + +// enum CommentActionBottomSheetPage { +// general, +// user, +// instance, +// share, +// text, +// } + +// void showCommentActionBottomModalSheet( +// BuildContext context, +// CommentView commentView, +// Function onSaveAction, +// Function onDeleteAction, +// Function onVoteAction, +// Function onReplyEditAction, +// Function onReportAction, +// Function onViewSourceToggled, +// bool viewSource, +// ) { +// final bool isOwnComment = commentView.creator.id == context.read().state.account?.userId; +// bool isDeleted = commentView.comment.deleted; + +// // Generate the list of default actions for the general page +// final List defaultCommentCardActions = commentCardDefaultActionItems +// .where((extendedAction) => [ +// CommentCardAction.userActions, +// CommentCardAction.instanceActions, +// CommentCardAction.textActions, +// CommentCardAction.report, +// CommentCardAction.delete, +// ].contains(extendedAction.commentCardAction)) +// .toList(); + +// // Add the ability to delete one's own comment +// if (isOwnComment) { +// defaultCommentCardActions.add(ExtendedCommentCardActions( +// commentCardAction: CommentCardAction.delete, +// icon: isDeleted ? Icons.restore_from_trash_rounded : Icons.delete_rounded, +// label: isDeleted ? AppLocalizations.of(GlobalContext.context)!.restore : AppLocalizations.of(GlobalContext.context)!.delete, +// )); +// } + +// // Hide the ability to block instance if not supported -- todo change this to instance list +// if (defaultCommentCardActions.any((c) => c.commentCardAction == CommentCardAction.blockInstance) && !LemmyClient.instance.supportsFeature(LemmyFeature.blockInstance)) { +// defaultCommentCardActions.removeWhere((c) => c.commentCardAction == CommentCardAction.blockInstance); +// } + +// // Generate list of user actions +// final List userActions = commentCardDefaultActionItems +// .where((extendedAction) => [ +// CommentCardAction.visitProfile, +// CommentCardAction.blockUser, +// CommentCardAction.userLabel, +// ].contains(extendedAction.commentCardAction)) +// .toList(); + +// // Generate list of instance actions +// final List instanceActions = commentCardDefaultActionItems +// .where((extendedAction) => [ +// CommentCardAction.visitInstance, +// CommentCardAction.blockInstance, +// ].contains(extendedAction.commentCardAction)) +// .toList(); + +// // Generate the list of share actions +// final List shareActions = commentCardDefaultActionItems +// .where((extendedAction) => [ +// CommentCardAction.shareLink, +// if (commentView.comment.apId != LemmyClient.instance.generateCommentUrl(commentView.comment.id)) CommentCardAction.shareLinkLocal, +// ].contains(extendedAction.commentCardAction)) +// .toList(); + +// // Generate list of text actions +// final List textActions = commentCardDefaultActionItems +// .where((extendedAction) => [ +// CommentCardAction.selectText, +// CommentCardAction.copyText, +// CommentCardAction.viewSource, +// if (commentView.comment.removed && LemmyClient.instance.supportsFeature(LemmyFeature.commentModLog)) CommentCardAction.viewModlog, +// ].contains(extendedAction.commentCardAction)) +// .toList(); + +// showModalBottomSheet( +// showDragHandle: true, +// isScrollControlled: true, +// context: context, +// builder: (builderContext) => CommentActionPicker( +// outerContext: context, +// commentView: commentView, +// titles: { +// CommentActionBottomSheetPage.general: l10n.actions, +// CommentActionBottomSheetPage.user: l10n.userActions, +// CommentActionBottomSheetPage.instance: l10n.instanceActions, +// CommentActionBottomSheetPage.share: l10n.share, +// CommentActionBottomSheetPage.text: l10n.textActions, +// }, +// multiCommentCardActions: {CommentActionBottomSheetPage.general: commentCardDefaultMultiActionItems}, +// commentCardActions: { +// CommentActionBottomSheetPage.general: defaultCommentCardActions, +// CommentActionBottomSheetPage.user: userActions, +// CommentActionBottomSheetPage.instance: instanceActions, +// CommentActionBottomSheetPage.share: shareActions, +// CommentActionBottomSheetPage.text: textActions, +// }, +// onSaveAction: onSaveAction, +// onDeleteAction: onDeleteAction, +// onVoteAction: onVoteAction, +// onReplyEditAction: onReplyEditAction, +// onReportAction: onReportAction, +// onViewSourceToggled: onViewSourceToggled, +// viewSource: viewSource, +// ), +// ); +// } + +// class CommentActionPicker extends StatefulWidget { +// /// The comment +// final CommentView commentView; + +// /// This is the set of titles to show for each page +// final Map titles; + +// /// This is the list of quick actions that are shown horizontally across the top of the sheet +// final Map> multiCommentCardActions; + +// /// This is the set of full actions to display vertically in a list +// final Map> commentCardActions; + +// /// The context from whoever invoked this sheet (useful for blocs that would otherwise be missing) +// final BuildContext outerContext; + +// // Callback functions +// final Function onSaveAction; +// final Function onDeleteAction; +// final Function onVoteAction; +// final Function onReplyEditAction; +// final Function onReportAction; +// final Function onViewSourceToggled; +// final bool viewSource; + +// const CommentActionPicker({ +// super.key, +// required this.outerContext, +// required this.commentView, +// required this.titles, +// required this.multiCommentCardActions, +// required this.commentCardActions, +// required this.onSaveAction, +// required this.onDeleteAction, +// required this.onVoteAction, +// required this.onReplyEditAction, +// required this.onReportAction, +// required this.onViewSourceToggled, +// required this.viewSource, +// }); + +// @override +// State createState() => _CommentActionPickerState(); +// } + +// class _CommentActionPickerState extends State { +// /// The current page +// CommentActionBottomSheetPage page = CommentActionBottomSheetPage.general; + +// @override +// void initState() { +// super.initState(); + +// BackButtonInterceptor.add(_handleBack); +// } + +// @override +// void dispose() { +// BackButtonInterceptor.remove(_handleBack); + +// super.dispose(); +// } + +// @override +// Widget build(BuildContext context) { +// final AppLocalizations l10n = AppLocalizations.of(context)!; +// final ThemeData theme = Theme.of(context); +// final bool isUserLoggedIn = context.read().state.isLoggedIn; + +// return SingleChildScrollView( +// child: AnimatedSize( +// duration: const Duration(milliseconds: 100), +// curve: Curves.easeInOut, +// child: SingleChildScrollView( +// child: Column( +// mainAxisAlignment: MainAxisAlignment.start, +// mainAxisSize: MainAxisSize.max, +// children: [ +// Semantics( +// label: '${widget.titles[page] ?? l10n.actions}, ${page == CommentActionBottomSheetPage.general ? '' : l10n.backButton}', +// child: Padding( +// padding: const EdgeInsets.only(left: 10, right: 10), +// child: Material( +// borderRadius: BorderRadius.circular(50), +// color: Colors.transparent, +// child: InkWell( +// borderRadius: BorderRadius.circular(50), +// onTap: page == CommentActionBottomSheetPage.general ? null : () => setState(() => page = CommentActionBottomSheetPage.general), +// child: Padding( +// padding: const EdgeInsets.fromLTRB(12.0, 10, 16.0, 10.0), +// child: Align( +// alignment: Alignment.centerLeft, +// child: Row( +// children: [ +// if (page != CommentActionBottomSheetPage.general) ...[ +// const Icon(Icons.chevron_left, size: 30), +// const SizedBox(width: 12), +// ], +// Semantics( +// excludeSemantics: true, +// child: Text( +// widget.titles[page] ?? l10n.actions, +// style: theme.textTheme.titleLarge, +// ), +// ), +// ], +// ), +// ), +// ), +// ), +// ), +// ), +// ), +// // Post metadata chips +// Row( +// children: [ +// const SizedBox(width: 20), +// LanguagePostCardMetaData(languageId: widget.commentView.comment.languageId), +// ], +// ), +// if (widget.multiCommentCardActions[page]?.isNotEmpty == true) +// MultiPickerItem( +// pickerItems: [ +// ...widget.multiCommentCardActions[page]!.where((a) => a.shouldShow?.call(context, widget.commentView) ?? true).map( +// (a) { +// return PickerItemData( +// label: a.label, +// icon: a.getOverrideIcon?.call(widget.commentView) ?? a.icon, +// backgroundColor: a.getColor?.call(context), +// foregroundColor: a.getForegroundColor?.call(context, widget.commentView), +// onSelected: (a.shouldEnable?.call(isUserLoggedIn) ?? true) ? () => onSelected(a.commentCardAction) : null, +// ); +// }, +// ), +// ], +// ), +// if (widget.commentCardActions[page]?.isNotEmpty == true) +// ListView.builder( +// shrinkWrap: true, +// physics: const NeverScrollableScrollPhysics(), +// itemCount: widget.commentCardActions[page]!.length, +// itemBuilder: (BuildContext itemBuilderContext, int index) { +// return PickerItem( +// label: widget.commentCardActions[page]![index].getOverrideLabel?.call(context, widget.commentView, widget.viewSource) ?? widget.commentCardActions[page]![index].label, +// subtitle: widget.commentCardActions[page]![index].getSubtitleLabel?.call(context, widget.commentView), +// icon: widget.commentCardActions[page]![index].getOverrideIcon?.call(widget.commentView) ?? widget.commentCardActions[page]![index].icon, +// trailingIcon: widget.commentCardActions[page]![index].getTrailingIcon?.call(), +// onSelected: +// (widget.commentCardActions[page]![index].shouldEnable?.call(isUserLoggedIn) ?? true) ? () => onSelected(widget.commentCardActions[page]![index].commentCardAction) : null, +// ); +// }, +// ), +// const SizedBox(height: 16.0), +// ], +// ), +// ), +// ), +// ); +// } + +// void onSelected(CommentCardAction commentCardAction) async { +// bool pop = true; +// Function action; + +// switch (commentCardAction) { +// case CommentCardAction.save: +// action = () => widget.onSaveAction(widget.commentView.comment.id, !(widget.commentView.saved)); +// break; +// case CommentCardAction.share: +// pop = false; +// action = () => setState(() => page = CommentActionBottomSheetPage.share); +// break; +// case CommentCardAction.shareLink: +// action = () => Share.share(widget.commentView.comment.apId); +// break; +// case CommentCardAction.shareLinkLocal: +// action = () => Share.share(LemmyClient.instance.generateCommentUrl(widget.commentView.comment.id)); +// break; +// case CommentCardAction.delete: +// action = () => widget.onDeleteAction(widget.commentView.comment.id, !(widget.commentView.comment.deleted)); +// case CommentCardAction.upvote: +// action = () => widget.onVoteAction(widget.commentView.comment.id, widget.commentView.myVote == 1 ? 0 : 1); +// break; +// case CommentCardAction.downvote: +// action = () => widget.onVoteAction(widget.commentView.comment.id, widget.commentView.myVote == -1 ? 0 : -1); +// break; +// case CommentCardAction.reply: +// action = () => widget.onReplyEditAction(widget.commentView, false); +// break; +// case CommentCardAction.edit: +// action = () => widget.onReplyEditAction(widget.commentView, true); +// break; +// case CommentCardAction.textActions: +// action = () => setState(() => page = CommentActionBottomSheetPage.text); +// pop = false; +// break; +// case CommentCardAction.selectText: +// action = () => showSelectableTextModal( +// context, +// text: widget.commentView.comment.content, +// ); +// break; +// case CommentCardAction.copyText: +// action = () => Clipboard.setData(ClipboardData(text: cleanCommentContent(widget.commentView.comment))).then((_) { +// showSnackbar(AppLocalizations.of(widget.outerContext)!.copiedToClipboard); +// }); +// break; +// case CommentCardAction.viewSource: +// action = widget.onViewSourceToggled; +// break; +// case CommentCardAction.viewModlog: +// action = () async { +// await navigateToModlogPage( +// context, +// subtitle: Text(l10n.removedComment), +// modlogActionType: ModlogActionType.modRemoveComment, +// commentId: widget.commentView.comment.id, +// ); +// }; +// break; + +// case CommentCardAction.report: +// action = () => widget.onReportAction(widget.commentView.comment.id); +// break; +// case CommentCardAction.userActions: +// action = () => setState(() => page = CommentActionBottomSheetPage.user); +// pop = false; +// break; +// case CommentCardAction.visitProfile: +// action = () => navigateToFeedPage(widget.outerContext, feedType: FeedType.user, userId: widget.commentView.creator.id); +// break; +// case CommentCardAction.blockUser: +// action = () => widget.outerContext.read().add(UserActionEvent(userAction: UserAction.block, userId: widget.commentView.creator.id, value: true)); +// break; +// case CommentCardAction.userLabel: +// action = () async { +// await showUserLabelEditorDialog(context, UserLabel.usernameFromParts(widget.commentView.creator.name, widget.commentView.creator.actorId)); +// }; +// break; +// case CommentCardAction.instanceActions: +// action = () => setState(() => page = CommentActionBottomSheetPage.instance); +// pop = false; + +// case CommentCardAction.visitInstance: +// action = () => navigateToInstancePage(widget.outerContext, instanceHost: fetchInstanceNameFromUrl(widget.commentView.creator.actorId)!, instanceId: widget.commentView.community.instanceId); +// break; +// case CommentCardAction.blockInstance: +// action = () => widget.outerContext.read().add(InstanceActionEvent( +// instanceAction: InstanceAction.block, +// instanceId: widget.commentView.creator.instanceId, +// domain: fetchInstanceNameFromUrl(widget.commentView.creator.actorId), +// value: true, +// )); +// break; +// } + +// if (pop) { +// Navigator.of(context).pop(); +// } + +// action(); +// } + +// FutureOr _handleBack(bool stopDefaultButtonEvent, RouteInfo routeInfo) { +// if (page != CommentActionBottomSheetPage.general) { +// setState(() => page = CommentActionBottomSheetPage.general); +// return true; +// } + +// return false; +// } +// } -import 'package:back_button_interceptor/back_button_interceptor.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:lemmy_api_client/v3.dart'; -import 'package:share_plus/share_plus.dart'; -import 'package:thunder/account/models/user_label.dart'; -import 'package:thunder/comment/utils/comment.dart'; -import 'package:thunder/community/widgets/post_card_metadata.dart'; -import 'package:thunder/core/enums/full_name.dart'; -import 'package:thunder/core/singletons/lemmy_client.dart'; -import 'package:thunder/feed/utils/utils.dart'; -import 'package:thunder/feed/view/feed_page.dart'; -import 'package:thunder/instance/bloc/instance_bloc.dart'; -import 'package:thunder/instance/enums/instance_action.dart'; -import 'package:thunder/modlog/utils/navigate_modlog.dart'; import 'package:thunder/post/bloc/post_bloc.dart'; -import 'package:thunder/post/utils/user_label_utils.dart'; import 'package:thunder/post/widgets/report_comment_dialog.dart'; -import 'package:thunder/shared/multi_picker_item.dart'; -import 'package:thunder/shared/picker_item.dart'; -import 'package:thunder/shared/snackbar.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:thunder/shared/text/selectable_text_modal.dart'; -import 'package:thunder/thunder/bloc/thunder_bloc.dart'; -import 'package:thunder/user/bloc/user_bloc.dart'; -import 'package:thunder/user/enums/user_action.dart'; import 'package:thunder/utils/global_context.dart'; -import 'package:thunder/utils/instance.dart'; -import 'package:thunder/instance/utils/navigate_instance.dart'; - -import '../../core/auth/bloc/auth_bloc.dart'; - -enum CommentCardAction { - save, - share, - shareLink, - shareLinkLocal, - delete, - upvote, - downvote, - reply, - edit, - textActions, - selectText, - copyText, - viewSource, - viewModlog, - report, - userActions, - visitProfile, - blockUser, - userLabel, - instanceActions, - visitInstance, - blockInstance, -} - -class ExtendedCommentCardActions { - const ExtendedCommentCardActions({ - required this.commentCardAction, - required this.icon, - this.getTrailingIcon, - required this.label, - this.getColor, - this.getForegroundColor, - this.getOverrideIcon, - this.getOverrideLabel, - this.getSubtitleLabel, - this.shouldShow, - this.shouldEnable, - }); - - final CommentCardAction commentCardAction; - final IconData icon; - final IconData Function()? getTrailingIcon; - final String label; - final Color Function(BuildContext context)? getColor; - final Color? Function(BuildContext context, CommentView commentView)? getForegroundColor; - final IconData? Function(CommentView commentView)? getOverrideIcon; - final String Function(BuildContext context, CommentView commentView, bool viewSource)? getOverrideLabel; - final String Function(BuildContext context, CommentView commentView)? getSubtitleLabel; - final bool Function(BuildContext context, CommentView commentView)? shouldShow; - final bool Function(bool isUserLoggedIn)? shouldEnable; -} final l10n = AppLocalizations.of(GlobalContext.context)!; -final List commentCardDefaultActionItems = [ - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.userActions, - icon: Icons.person_rounded, - label: l10n.user, - getSubtitleLabel: (context, commentView) => generateUserFullName( - context, - commentView.creator.name, - commentView.creator.displayName, - fetchInstanceNameFromUrl(commentView.creator.actorId), - ), - getTrailingIcon: () => Icons.chevron_right_rounded, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.visitProfile, - icon: Icons.person_search_rounded, - label: AppLocalizations.of(GlobalContext.context)!.visitUserProfile, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.blockUser, - icon: Icons.block, - label: AppLocalizations.of(GlobalContext.context)!.blockUser, - shouldEnable: (isUserLoggedIn) => isUserLoggedIn, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.userLabel, - icon: Icons.label_rounded, - label: AppLocalizations.of(GlobalContext.context)!.addUserLabel, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.instanceActions, - icon: Icons.language_rounded, - label: l10n.instance(1), - getSubtitleLabel: (context, postView) => fetchInstanceNameFromUrl(postView.creator.actorId) ?? '', - getTrailingIcon: () => Icons.chevron_right_rounded, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.visitInstance, - icon: Icons.language, - label: AppLocalizations.of(GlobalContext.context)!.visitInstance, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.blockInstance, - icon: Icons.block_rounded, - label: AppLocalizations.of(GlobalContext.context)!.blockInstance, - shouldEnable: (isUserLoggedIn) => isUserLoggedIn, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.textActions, - icon: Icons.comment_rounded, - label: l10n.textActions, - getTrailingIcon: () => Icons.chevron_right_rounded, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.selectText, - icon: Icons.select_all_rounded, - label: l10n.selectText, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.copyText, - icon: Icons.copy_rounded, - label: AppLocalizations.of(GlobalContext.context)!.copyComment, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.viewSource, - icon: Icons.edit_document, - label: l10n.viewCommentSource, - getOverrideLabel: (context, commentView, viewSource) => viewSource ? l10n.viewOriginal : l10n.viewCommentSource, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.viewModlog, - icon: Icons.shield_rounded, - label: AppLocalizations.of(GlobalContext.context)!.viewModlog, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.report, - icon: Icons.report_outlined, - label: AppLocalizations.of(GlobalContext.context)!.reportComment, - shouldEnable: (isUserLoggedIn) => isUserLoggedIn, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.shareLink, - icon: Icons.share_rounded, - label: l10n.shareComment, - getSubtitleLabel: (context, commentView) => commentView.comment.apId, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.shareLinkLocal, - icon: Icons.share_rounded, - label: l10n.shareCommentLocal, - getSubtitleLabel: (context, commentView) => LemmyClient.instance.generateCommentUrl(commentView.comment.id), - ), -]; - -final List commentCardDefaultMultiActionItems = [ - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.upvote, - label: AppLocalizations.of(GlobalContext.context)!.upvote, - icon: Icons.arrow_upward_rounded, - getColor: (context) => context.read().state.upvoteColor.color, - getForegroundColor: (context, commentView) => commentView.myVote == 1 ? context.read().state.upvoteColor.color : null, - shouldEnable: (isUserLoggedIn) => isUserLoggedIn, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.downvote, - label: AppLocalizations.of(GlobalContext.context)!.downvote, - icon: Icons.arrow_downward_rounded, - getColor: (context) => context.read().state.downvoteColor.color, - getForegroundColor: (context, commentView) => commentView.myVote == -1 ? context.read().state.downvoteColor.color : null, - shouldShow: (context, commentView) => context.read().state.downvotesEnabled, - shouldEnable: (isUserLoggedIn) => isUserLoggedIn, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.save, - label: AppLocalizations.of(GlobalContext.context)!.save, - icon: Icons.star_border_rounded, - getColor: (context) => context.read().state.saveColor.color, - getForegroundColor: (context, commentView) => commentView.saved ? context.read().state.saveColor.color : null, - getOverrideIcon: (commentView) => commentView.saved ? Icons.star_rounded : null, - shouldEnable: (isUserLoggedIn) => isUserLoggedIn, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.reply, - label: AppLocalizations.of(GlobalContext.context)!.reply(0), - icon: Icons.reply_rounded, - shouldEnable: (isUserLoggedIn) => isUserLoggedIn, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.edit, - label: AppLocalizations.of(GlobalContext.context)!.edit, - icon: Icons.edit, - shouldShow: (context, commentView) => commentView.creator.id == context.read().state.account?.userId, - shouldEnable: (isUserLoggedIn) => isUserLoggedIn, - ), - ExtendedCommentCardActions( - commentCardAction: CommentCardAction.share, - icon: Icons.share_rounded, - label: l10n.share, - ), -]; - -enum CommentActionBottomSheetPage { - general, - user, - instance, - share, - text, -} - -void showCommentActionBottomModalSheet( - BuildContext context, - CommentView commentView, - Function onSaveAction, - Function onDeleteAction, - Function onVoteAction, - Function onReplyEditAction, - Function onReportAction, - Function onViewSourceToggled, - bool viewSource, -) { - final bool isOwnComment = commentView.creator.id == context.read().state.account?.userId; - bool isDeleted = commentView.comment.deleted; - - // Generate the list of default actions for the general page - final List defaultCommentCardActions = commentCardDefaultActionItems - .where((extendedAction) => [ - CommentCardAction.userActions, - CommentCardAction.instanceActions, - CommentCardAction.textActions, - CommentCardAction.report, - CommentCardAction.delete, - ].contains(extendedAction.commentCardAction)) - .toList(); - - // Add the ability to delete one's own comment - if (isOwnComment) { - defaultCommentCardActions.add(ExtendedCommentCardActions( - commentCardAction: CommentCardAction.delete, - icon: isDeleted ? Icons.restore_from_trash_rounded : Icons.delete_rounded, - label: isDeleted ? AppLocalizations.of(GlobalContext.context)!.restore : AppLocalizations.of(GlobalContext.context)!.delete, - )); - } - - // Hide the ability to block instance if not supported -- todo change this to instance list - if (defaultCommentCardActions.any((c) => c.commentCardAction == CommentCardAction.blockInstance) && !LemmyClient.instance.supportsFeature(LemmyFeature.blockInstance)) { - defaultCommentCardActions.removeWhere((c) => c.commentCardAction == CommentCardAction.blockInstance); - } - - // Generate list of user actions - final List userActions = commentCardDefaultActionItems - .where((extendedAction) => [ - CommentCardAction.visitProfile, - CommentCardAction.blockUser, - CommentCardAction.userLabel, - ].contains(extendedAction.commentCardAction)) - .toList(); - - // Generate list of instance actions - final List instanceActions = commentCardDefaultActionItems - .where((extendedAction) => [ - CommentCardAction.visitInstance, - CommentCardAction.blockInstance, - ].contains(extendedAction.commentCardAction)) - .toList(); - - // Generate the list of share actions - final List shareActions = commentCardDefaultActionItems - .where((extendedAction) => [ - CommentCardAction.shareLink, - if (commentView.comment.apId != LemmyClient.instance.generateCommentUrl(commentView.comment.id)) CommentCardAction.shareLinkLocal, - ].contains(extendedAction.commentCardAction)) - .toList(); - - // Generate list of text actions - final List textActions = commentCardDefaultActionItems - .where((extendedAction) => [ - CommentCardAction.selectText, - CommentCardAction.copyText, - CommentCardAction.viewSource, - if (commentView.comment.removed && LemmyClient.instance.supportsFeature(LemmyFeature.commentModLog)) CommentCardAction.viewModlog, - ].contains(extendedAction.commentCardAction)) - .toList(); - - showModalBottomSheet( - showDragHandle: true, - isScrollControlled: true, - context: context, - builder: (builderContext) => CommentActionPicker( - outerContext: context, - commentView: commentView, - titles: { - CommentActionBottomSheetPage.general: l10n.actions, - CommentActionBottomSheetPage.user: l10n.userActions, - CommentActionBottomSheetPage.instance: l10n.instanceActions, - CommentActionBottomSheetPage.share: l10n.share, - CommentActionBottomSheetPage.text: l10n.textActions, - }, - multiCommentCardActions: {CommentActionBottomSheetPage.general: commentCardDefaultMultiActionItems}, - commentCardActions: { - CommentActionBottomSheetPage.general: defaultCommentCardActions, - CommentActionBottomSheetPage.user: userActions, - CommentActionBottomSheetPage.instance: instanceActions, - CommentActionBottomSheetPage.share: shareActions, - CommentActionBottomSheetPage.text: textActions, - }, - onSaveAction: onSaveAction, - onDeleteAction: onDeleteAction, - onVoteAction: onVoteAction, - onReplyEditAction: onReplyEditAction, - onReportAction: onReportAction, - onViewSourceToggled: onViewSourceToggled, - viewSource: viewSource, - ), - ); -} - -class CommentActionPicker extends StatefulWidget { - /// The comment - final CommentView commentView; - - /// This is the set of titles to show for each page - final Map titles; - - /// This is the list of quick actions that are shown horizontally across the top of the sheet - final Map> multiCommentCardActions; - - /// This is the set of full actions to display vertically in a list - final Map> commentCardActions; - - /// The context from whoever invoked this sheet (useful for blocs that would otherwise be missing) - final BuildContext outerContext; - - // Callback functions - final Function onSaveAction; - final Function onDeleteAction; - final Function onVoteAction; - final Function onReplyEditAction; - final Function onReportAction; - final Function onViewSourceToggled; - final bool viewSource; - - const CommentActionPicker({ - super.key, - required this.outerContext, - required this.commentView, - required this.titles, - required this.multiCommentCardActions, - required this.commentCardActions, - required this.onSaveAction, - required this.onDeleteAction, - required this.onVoteAction, - required this.onReplyEditAction, - required this.onReportAction, - required this.onViewSourceToggled, - required this.viewSource, - }); - - @override - State createState() => _CommentActionPickerState(); -} - -class _CommentActionPickerState extends State { - /// The current page - CommentActionBottomSheetPage page = CommentActionBottomSheetPage.general; - - @override - void initState() { - super.initState(); - - BackButtonInterceptor.add(_handleBack); - } - - @override - void dispose() { - BackButtonInterceptor.remove(_handleBack); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final AppLocalizations l10n = AppLocalizations.of(context)!; - final ThemeData theme = Theme.of(context); - final bool isUserLoggedIn = context.read().state.isLoggedIn; - - return SingleChildScrollView( - child: AnimatedSize( - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Semantics( - label: '${widget.titles[page] ?? l10n.actions}, ${page == CommentActionBottomSheetPage.general ? '' : l10n.backButton}', - child: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: Material( - borderRadius: BorderRadius.circular(50), - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(50), - onTap: page == CommentActionBottomSheetPage.general ? null : () => setState(() => page = CommentActionBottomSheetPage.general), - child: Padding( - padding: const EdgeInsets.fromLTRB(12.0, 10, 16.0, 10.0), - child: Align( - alignment: Alignment.centerLeft, - child: Row( - children: [ - if (page != CommentActionBottomSheetPage.general) ...[ - const Icon(Icons.chevron_left, size: 30), - const SizedBox(width: 12), - ], - Semantics( - excludeSemantics: true, - child: Text( - widget.titles[page] ?? l10n.actions, - style: theme.textTheme.titleLarge, - ), - ), - ], - ), - ), - ), - ), - ), - ), - ), - // Post metadata chips - Row( - children: [ - const SizedBox(width: 20), - LanguagePostCardMetaData(languageId: widget.commentView.comment.languageId), - ], - ), - if (widget.multiCommentCardActions[page]?.isNotEmpty == true) - MultiPickerItem( - pickerItems: [ - ...widget.multiCommentCardActions[page]!.where((a) => a.shouldShow?.call(context, widget.commentView) ?? true).map( - (a) { - return PickerItemData( - label: a.label, - icon: a.getOverrideIcon?.call(widget.commentView) ?? a.icon, - backgroundColor: a.getColor?.call(context), - foregroundColor: a.getForegroundColor?.call(context, widget.commentView), - onSelected: (a.shouldEnable?.call(isUserLoggedIn) ?? true) ? () => onSelected(a.commentCardAction) : null, - ); - }, - ), - ], - ), - if (widget.commentCardActions[page]?.isNotEmpty == true) - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: widget.commentCardActions[page]!.length, - itemBuilder: (BuildContext itemBuilderContext, int index) { - return PickerItem( - label: widget.commentCardActions[page]![index].getOverrideLabel?.call(context, widget.commentView, widget.viewSource) ?? widget.commentCardActions[page]![index].label, - subtitle: widget.commentCardActions[page]![index].getSubtitleLabel?.call(context, widget.commentView), - icon: widget.commentCardActions[page]![index].getOverrideIcon?.call(widget.commentView) ?? widget.commentCardActions[page]![index].icon, - trailingIcon: widget.commentCardActions[page]![index].getTrailingIcon?.call(), - onSelected: - (widget.commentCardActions[page]![index].shouldEnable?.call(isUserLoggedIn) ?? true) ? () => onSelected(widget.commentCardActions[page]![index].commentCardAction) : null, - ); - }, - ), - const SizedBox(height: 16.0), - ], - ), - ), - ), - ); - } - - void onSelected(CommentCardAction commentCardAction) async { - bool pop = true; - Function action; - - switch (commentCardAction) { - case CommentCardAction.save: - action = () => widget.onSaveAction(widget.commentView.comment.id, !(widget.commentView.saved)); - break; - case CommentCardAction.share: - pop = false; - action = () => setState(() => page = CommentActionBottomSheetPage.share); - break; - case CommentCardAction.shareLink: - action = () => Share.share(widget.commentView.comment.apId); - break; - case CommentCardAction.shareLinkLocal: - action = () => Share.share(LemmyClient.instance.generateCommentUrl(widget.commentView.comment.id)); - break; - case CommentCardAction.delete: - action = () => widget.onDeleteAction(widget.commentView.comment.id, !(widget.commentView.comment.deleted)); - case CommentCardAction.upvote: - action = () => widget.onVoteAction(widget.commentView.comment.id, widget.commentView.myVote == 1 ? 0 : 1); - break; - case CommentCardAction.downvote: - action = () => widget.onVoteAction(widget.commentView.comment.id, widget.commentView.myVote == -1 ? 0 : -1); - break; - case CommentCardAction.reply: - action = () => widget.onReplyEditAction(widget.commentView, false); - break; - case CommentCardAction.edit: - action = () => widget.onReplyEditAction(widget.commentView, true); - break; - case CommentCardAction.textActions: - action = () => setState(() => page = CommentActionBottomSheetPage.text); - pop = false; - break; - case CommentCardAction.selectText: - action = () => showSelectableTextModal( - context, - text: widget.commentView.comment.content, - ); - break; - case CommentCardAction.copyText: - action = () => Clipboard.setData(ClipboardData(text: cleanCommentContent(widget.commentView.comment))).then((_) { - showSnackbar(AppLocalizations.of(widget.outerContext)!.copiedToClipboard); - }); - break; - case CommentCardAction.viewSource: - action = widget.onViewSourceToggled; - break; - case CommentCardAction.viewModlog: - action = () async { - await navigateToModlogPage( - context, - subtitle: Text(l10n.removedComment), - modlogActionType: ModlogActionType.modRemoveComment, - commentId: widget.commentView.comment.id, - ); - }; - break; - - case CommentCardAction.report: - action = () => widget.onReportAction(widget.commentView.comment.id); - break; - case CommentCardAction.userActions: - action = () => setState(() => page = CommentActionBottomSheetPage.user); - pop = false; - break; - case CommentCardAction.visitProfile: - action = () => navigateToFeedPage(widget.outerContext, feedType: FeedType.user, userId: widget.commentView.creator.id); - break; - case CommentCardAction.blockUser: - action = () => widget.outerContext.read().add(UserActionEvent(userAction: UserAction.block, userId: widget.commentView.creator.id, value: true)); - break; - case CommentCardAction.userLabel: - action = () async { - await showUserLabelEditorDialog(context, UserLabel.usernameFromParts(widget.commentView.creator.name, widget.commentView.creator.actorId)); - }; - break; - case CommentCardAction.instanceActions: - action = () => setState(() => page = CommentActionBottomSheetPage.instance); - pop = false; - - case CommentCardAction.visitInstance: - action = () => navigateToInstancePage(widget.outerContext, instanceHost: fetchInstanceNameFromUrl(widget.commentView.creator.actorId)!, instanceId: widget.commentView.community.instanceId); - break; - case CommentCardAction.blockInstance: - action = () => widget.outerContext.read().add(InstanceActionEvent( - instanceAction: InstanceAction.block, - instanceId: widget.commentView.creator.instanceId, - domain: fetchInstanceNameFromUrl(widget.commentView.creator.actorId), - value: true, - )); - break; - } - - if (pop) { - Navigator.of(context).pop(); - } - - action(); - } - - FutureOr _handleBack(bool stopDefaultButtonEvent, RouteInfo routeInfo) { - if (page != CommentActionBottomSheetPage.general) { - setState(() => page = CommentActionBottomSheetPage.general); - return true; - } - - return false; - } -} - void showReportCommentActionBottomSheet( BuildContext context, { required int commentId, diff --git a/lib/post/widgets/comment_card.dart b/lib/post/widgets/comment_card.dart index 9d7cf2a32..23933eafc 100644 --- a/lib/post/widgets/comment_card.dart +++ b/lib/post/widgets/comment_card.dart @@ -2,8 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:lemmy_api_client/v3.dart'; +import 'package:thunder/comment/enums/comment_action.dart'; +import 'package:thunder/comment/widgets/comment_action_bottom_sheet.dart'; import 'package:thunder/core/enums/nested_comment_indicator.dart'; import 'package:thunder/core/enums/swipe_action.dart'; import 'package:thunder/post/bloc/post_bloc.dart'; @@ -13,8 +16,6 @@ import 'package:thunder/core/models/comment_view_tree.dart'; import 'package:thunder/shared/text/scalable_text.dart'; import 'package:thunder/thunder/bloc/thunder_bloc.dart'; import '../../shared/comment_content.dart'; -import '../utils/comment_action_helpers.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class CommentCard extends StatefulWidget { final Function(int, int) onVoteAction; @@ -353,13 +354,46 @@ class _CommentCardState extends State with SingleTickerProviderStat showCommentActionBottomModalSheet( context, widget.commentViewTree.commentView!, - widget.onSaveAction, - widget.onDeleteAction, - widget.onVoteAction, - widget.onReplyEditAction, - widget.onReportAction, - () => setState(() => viewSource = !viewSource), - viewSource, + onAction: ({commentAction, required commentView, communityAction, userAction, value}) { + if (commentAction != null) { + switch (commentAction) { + case CommentAction.reply: + widget.onReplyEditAction(commentView, false); + break; + case CommentAction.edit: + widget.onReplyEditAction(commentView, true); + break; + case CommentAction.save: + widget.onSaveAction(commentView.comment.id, value); + break; + case CommentAction.vote: + widget.onVoteAction(commentView.comment.id, value); + break; + case CommentAction.delete: + widget.onDeleteAction(commentView.comment.id, value); + break; + case CommentAction.report: + widget.onReportAction(commentView.comment.id); + break; + // case CommentAction.viewSource: + // setState(() => viewSource = !viewSource); + // break; + default: + break; + } + } else if (communityAction != null) { + // @todo - implement community actions + } else if (userAction != null) { + // @todo - implement user actions + } + }, + // widget.onSaveAction, + // widget.onDeleteAction, + // widget.onVoteAction, + // widget.onReplyEditAction, + // widget.onReportAction, + // () => setState(() => viewSource = !viewSource), + // viewSource, ); }, onTap: () { diff --git a/lib/shared/comment_card_actions.dart b/lib/shared/comment_card_actions.dart index db824867d..9fbca6a34 100644 --- a/lib/shared/comment_card_actions.dart +++ b/lib/shared/comment_card_actions.dart @@ -3,9 +3,9 @@ import 'package:flutter/services.dart'; import 'package:lemmy_api_client/v3.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/core/auth/bloc/auth_bloc.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; +import 'package:thunder/comment/widgets/comment_action_bottom_sheet.dart'; +import 'package:thunder/core/auth/bloc/auth_bloc.dart'; import 'package:thunder/thunder/bloc/thunder_bloc.dart'; class CommentCardActions extends StatelessWidget { @@ -59,13 +59,13 @@ class CommentCardActions extends StatelessWidget { showCommentActionBottomModalSheet( context, commentView, - onSaveAction, - onDeleteAction, - onVoteAction, - onReplyEditAction, - onReportAction, - onViewSourceToggled, - viewSource, + // onSaveAction, + // onDeleteAction, + // onVoteAction, + // onReplyEditAction, + // onReportAction, + // onViewSourceToggled, + // viewSource, ); HapticFeedback.mediumImpact(); }), From 2bfc443cfc4eedb85e0cc928a7f5ae396cf4ef36 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Tue, 31 Dec 2024 12:30:08 -0800 Subject: [PATCH 2/9] feat: refactored user bottom sheet to be more generic, fixed comment bottom sheet quick actions --- .../widgets/comment_action_bottom_sheet.dart | 19 ++- .../general_comment_action_bottom_sheet.dart | 11 +- lib/post/widgets/comment_card.dart | 12 +- .../widgets/post_action_bottom_sheet.dart | 9 +- .../widgets/user_action_bottom_sheet.dart} | 126 ++++++++++-------- 5 files changed, 104 insertions(+), 73 deletions(-) rename lib/{post/widgets/user_post_action_bottom_sheet.dart => user/widgets/user_action_bottom_sheet.dart} (71%) diff --git a/lib/comment/widgets/comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_action_bottom_sheet.dart index 7eaa42753..675143fbf 100644 --- a/lib/comment/widgets/comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_action_bottom_sheet.dart @@ -11,6 +11,7 @@ import 'package:thunder/community/enums/community_action.dart'; import 'package:thunder/community/widgets/post_card_metadata.dart'; import 'package:thunder/core/enums/full_name.dart'; import 'package:thunder/user/enums/user_action.dart'; +import 'package:thunder/user/widgets/user_action_bottom_sheet.dart'; import 'package:thunder/utils/instance.dart'; /// Programatically show the comment action bottom sheet @@ -101,6 +102,16 @@ class _CommentActionBottomSheetState extends State { widget.onAction?.call(commentAction: commentAction, commentView: widget.commentView, value: value); }, ), + GeneralCommentAction.user => UserActionBottomSheet( + context: widget.context, + user: widget.commentView.creator, + communityId: widget.commentView.community.id, + isUserCommunityModerator: widget.commentView.creatorIsModerator, + isUserBannedFromCommunity: widget.commentView.creatorBannedFromCommunity, + onAction: (UserAction userAction, PersonView? updatedPersonView) { + widget.onAction?.call(userAction: userAction, commentView: widget.commentView); + }, + ), _ => SizedBox(), // GeneralCommentAction.post => PostPostActionBottomSheet( @@ -110,13 +121,7 @@ class _CommentActionBottomSheetState extends State { // widget.onAction?.call(postAction: postAction, postViewMedia: widget.commentView); // }, // ), - // GeneralCommentAction.user => UserPostActionBottomSheet( - // context: widget.context, - // postViewMedia: widget.commentView, - // onAction: (UserAction userAction, PersonView? updatedPersonView) { - // widget.onAction?.call(userAction: userAction, postViewMedia: widget.commentView); - // }, - // ), + // GeneralCommentAction.instance => InstancePostActionBottomSheet( // postViewMedia: widget.commentView, // onAction: () {}, diff --git a/lib/comment/widgets/general_comment_action_bottom_sheet.dart b/lib/comment/widgets/general_comment_action_bottom_sheet.dart index e39f267ed..d21ab70d8 100644 --- a/lib/comment/widgets/general_comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/general_comment_action_bottom_sheet.dart @@ -111,16 +111,21 @@ class _GeneralCommentActionBottomSheetPageState extends State().add(FeedItemActionedEvent(postAction: PostAction.vote, postId: postViewMedia.postView.post.id, value: postViewMedia.postView.myVote == 1 ? 0 : 1)); break; case GeneralQuickCommentAction.downvote: + widget.onAction(CommentAction.vote, commentView, commentView.myVote == -1 ? 0 : -1); break; case GeneralQuickCommentAction.save: + widget.onAction(CommentAction.save, commentView, !commentView.saved); break; case GeneralQuickCommentAction.reply: - break; + Navigator.of(context).pop(); + widget.onAction(CommentAction.reply, commentView, null); + return; case GeneralQuickCommentAction.edit: - break; + Navigator.of(context).pop(); + widget.onAction(CommentAction.edit, commentView, null); + return; } Navigator.of(context).pop(); diff --git a/lib/post/widgets/comment_card.dart b/lib/post/widgets/comment_card.dart index 23933eafc..c0108eba1 100644 --- a/lib/post/widgets/comment_card.dart +++ b/lib/post/widgets/comment_card.dart @@ -357,18 +357,18 @@ class _CommentCardState extends State with SingleTickerProviderStat onAction: ({commentAction, required commentView, communityAction, userAction, value}) { if (commentAction != null) { switch (commentAction) { + case CommentAction.vote: + widget.onVoteAction(commentView.comment.id, value); + break; + case CommentAction.save: + widget.onSaveAction(commentView.comment.id, value); + break; case CommentAction.reply: widget.onReplyEditAction(commentView, false); break; case CommentAction.edit: widget.onReplyEditAction(commentView, true); break; - case CommentAction.save: - widget.onSaveAction(commentView.comment.id, value); - break; - case CommentAction.vote: - widget.onVoteAction(commentView.comment.id, value); - break; case CommentAction.delete: widget.onDeleteAction(commentView.comment.id, value); break; diff --git a/lib/post/widgets/post_action_bottom_sheet.dart b/lib/post/widgets/post_action_bottom_sheet.dart index b8e5c9858..344252dc4 100644 --- a/lib/post/widgets/post_action_bottom_sheet.dart +++ b/lib/post/widgets/post_action_bottom_sheet.dart @@ -16,7 +16,7 @@ import 'package:thunder/post/widgets/general_post_action_bottom_sheet.dart'; import 'package:thunder/post/widgets/instance_post_action_bottom_sheet.dart'; import 'package:thunder/post/widgets/post_post_action_bottom_sheet.dart'; import 'package:thunder/post/widgets/share_post_action_bottom_sheet.dart'; -import 'package:thunder/post/widgets/user_post_action_bottom_sheet.dart'; +import 'package:thunder/user/widgets/user_action_bottom_sheet.dart'; import 'package:thunder/user/enums/user_action.dart'; import 'package:thunder/utils/instance.dart'; import 'package:thunder/utils/global_context.dart'; @@ -120,9 +120,12 @@ class _PostActionBottomSheetState extends State { widget.onAction?.call(postAction: postAction, postViewMedia: widget.postViewMedia); }, ), - GeneralPostAction.user => UserPostActionBottomSheet( + GeneralPostAction.user => UserActionBottomSheet( context: widget.context, - postViewMedia: widget.postViewMedia, + user: widget.postViewMedia.postView.creator, + communityId: widget.postViewMedia.postView.community.id, + isUserCommunityModerator: widget.postViewMedia.postView.creatorIsModerator, + isUserBannedFromCommunity: widget.postViewMedia.postView.creatorBannedFromCommunity, onAction: (UserAction userAction, PersonView? updatedPersonView) { widget.onAction?.call(userAction: userAction, postViewMedia: widget.postViewMedia); }, diff --git a/lib/post/widgets/user_post_action_bottom_sheet.dart b/lib/user/widgets/user_action_bottom_sheet.dart similarity index 71% rename from lib/post/widgets/user_post_action_bottom_sheet.dart rename to lib/user/widgets/user_action_bottom_sheet.dart index 850851af7..5bfeefa2e 100644 --- a/lib/post/widgets/user_post_action_bottom_sheet.dart +++ b/lib/user/widgets/user_action_bottom_sheet.dart @@ -5,11 +5,11 @@ import 'package:lemmy_api_client/v3.dart'; import 'package:thunder/core/auth/bloc/auth_bloc.dart'; import 'package:thunder/core/enums/user_type.dart'; -import 'package:thunder/core/models/post_view_media.dart'; import 'package:thunder/feed/utils/utils.dart'; import 'package:thunder/feed/view/feed_page.dart'; import 'package:thunder/post/enums/post_action.dart'; import 'package:thunder/post/utils/comment_action_helpers.dart'; +import 'package:thunder/post/utils/user_label_utils.dart'; import 'package:thunder/shared/avatars/user_avatar.dart'; import 'package:thunder/shared/bottom_sheet_action.dart'; import 'package:thunder/shared/chips/user_chip.dart'; @@ -19,12 +19,15 @@ import 'package:thunder/thunder/thunder_icons.dart'; import 'package:thunder/user/bloc/user_bloc.dart'; import 'package:thunder/user/enums/user_action.dart'; +import '../../account/models/user_label.dart'; + /// Defines the actions that can be taken on a user /// TODO: Implement admin-level actions -enum UserPostAction { +enum UserBottomSheetAction { viewProfile(icon: Icons.person_search_rounded, permissionType: PermissionType.user, requiresAuthentication: false), blockUser(icon: Icons.block_rounded, permissionType: PermissionType.user, requiresAuthentication: true), unblockUser(icon: Icons.block_rounded, permissionType: PermissionType.user, requiresAuthentication: true), + addUserLabel(icon: Icons.label_rounded, permissionType: PermissionType.user, requiresAuthentication: false), banUserFromCommunity(icon: Icons.block, permissionType: PermissionType.moderator, requiresAuthentication: true), unbanUserFromCommunity(icon: Icons.block, permissionType: PermissionType.moderator, requiresAuthentication: true), addUserAsCommunityModerator(icon: Icons.person_add_rounded, permissionType: PermissionType.moderator, requiresAuthentication: true), @@ -37,13 +40,14 @@ enum UserPostAction { ; String get name => switch (this) { - UserPostAction.viewProfile => l10n.visitUserProfile, - UserPostAction.blockUser => l10n.blockUser, - UserPostAction.unblockUser => l10n.unblockUser, - UserPostAction.banUserFromCommunity => l10n.banFromCommunity, - UserPostAction.unbanUserFromCommunity => l10n.unbanFromCommunity, - UserPostAction.addUserAsCommunityModerator => l10n.addAsCommunityModerator, - UserPostAction.removeUserAsCommunityModerator => l10n.removeAsCommunityModerator, + UserBottomSheetAction.viewProfile => l10n.visitUserProfile, + UserBottomSheetAction.blockUser => l10n.blockUser, + UserBottomSheetAction.unblockUser => l10n.unblockUser, + UserBottomSheetAction.addUserLabel => l10n.addUserLabel, + UserBottomSheetAction.banUserFromCommunity => l10n.banFromCommunity, + UserBottomSheetAction.unbanUserFromCommunity => l10n.unbanFromCommunity, + UserBottomSheetAction.addUserAsCommunityModerator => l10n.addAsCommunityModerator, + UserBottomSheetAction.removeUserAsCommunityModerator => l10n.removeAsCommunityModerator, // UserPostAction.banUser => "Ban From Instance", // UserPostAction.unbanUser => "Unban User From Instance", // UserPostAction.purgeUser => "Purge User", @@ -60,77 +64,91 @@ enum UserPostAction { /// Whether or not the action requires user authentication final bool requiresAuthentication; - const UserPostAction({required this.icon, required this.permissionType, required this.requiresAuthentication}); + const UserBottomSheetAction({required this.icon, required this.permissionType, required this.requiresAuthentication}); } -/// A bottom sheet that allows the user to perform actions on a user. +/// A bottom sheet that allows the current user to perform actions on another user. /// -/// Given a [postViewMedia] and a [onAction] callback, this widget will display a list of actions that can be taken on the user. +/// Given an [onAction] callback, this widget will display a list of actions that can be taken on the user. /// The [onAction] callback will be triggered when an action is performed. This is useful if the parent widget requires an updated [PersonView]. -class UserPostActionBottomSheet extends StatefulWidget { - const UserPostActionBottomSheet({super.key, required this.context, required this.postViewMedia, required this.onAction}); +class UserActionBottomSheet extends StatefulWidget { + const UserActionBottomSheet({super.key, required this.context, required this.user, this.communityId, this.isUserCommunityModerator, this.isUserBannedFromCommunity, required this.onAction}); /// The outer context final BuildContext context; - /// The post information - final PostViewMedia postViewMedia; + /// The user that we are interacting with + final Person user; + + /// The community that the user has interacted with + /// This is useful for community-specific actions such as banning/unbanning the user from a community, or adding/removing them as a moderator of a community + final int? communityId; + + /// Whether or not the user is a moderator of the community. This is only applicable if [communityId] is not null + final bool? isUserCommunityModerator; + + /// Whether or not the user is banned from the community. This is only applicable if [communityId] is not null + final bool? isUserBannedFromCommunity; /// Called when an action is selected final Function(UserAction userAction, PersonView? personView) onAction; @override - State createState() => _UserPostActionBottomSheetState(); + State createState() => _UserActionBottomSheetState(); } -class _UserPostActionBottomSheetState extends State { +class _UserActionBottomSheetState extends State { UserAction? _userAction; - void performAction(UserPostAction action) { + void performAction(UserBottomSheetAction action) async { switch (action) { - case UserPostAction.viewProfile: + case UserBottomSheetAction.viewProfile: Navigator.of(context).pop(); - navigateToFeedPage(context, feedType: FeedType.user, userId: widget.postViewMedia.postView.creator.id); + navigateToFeedPage(context, feedType: FeedType.user, userId: widget.user.id); break; - case UserPostAction.blockUser: - context.read().add(UserActionEvent(userId: widget.postViewMedia.postView.creator.id, userAction: UserAction.block, value: true)); + case UserBottomSheetAction.blockUser: + context.read().add(UserActionEvent(userId: widget.user.id, userAction: UserAction.block, value: true)); setState(() => _userAction = UserAction.block); break; - case UserPostAction.unblockUser: - context.read().add(UserActionEvent(userId: widget.postViewMedia.postView.creator.id, userAction: UserAction.block, value: false)); + case UserBottomSheetAction.unblockUser: + context.read().add(UserActionEvent(userId: widget.user.id, userAction: UserAction.block, value: false)); setState(() => _userAction = UserAction.block); break; - case UserPostAction.banUserFromCommunity: + case UserBottomSheetAction.addUserLabel: + await showUserLabelEditorDialog(context, UserLabel.usernameFromParts(widget.user.name, widget.user.actorId)); + Navigator.of(context).pop(); + break; + case UserBottomSheetAction.banUserFromCommunity: showBanUserDialog(); break; - case UserPostAction.unbanUserFromCommunity: + case UserBottomSheetAction.unbanUserFromCommunity: context.read().add( UserActionEvent( - userId: widget.postViewMedia.postView.creator.id, + userId: widget.user.id, userAction: UserAction.banFromCommunity, value: false, metadata: { - "communityId": widget.postViewMedia.postView.community.id, + "communityId": widget.communityId, }, ), ); setState(() => _userAction = UserAction.banFromCommunity); break; - case UserPostAction.addUserAsCommunityModerator: + case UserBottomSheetAction.addUserAsCommunityModerator: context.read().add(UserActionEvent( - userId: widget.postViewMedia.postView.creator.id, + userId: widget.user.id, userAction: UserAction.addModerator, value: true, - metadata: {"communityId": widget.postViewMedia.postView.community.id}, + metadata: {"communityId": widget.communityId}, )); setState(() => _userAction = UserAction.addModerator); break; - case UserPostAction.removeUserAsCommunityModerator: + case UserBottomSheetAction.removeUserAsCommunityModerator: context.read().add(UserActionEvent( - userId: widget.postViewMedia.postView.creator.id, + userId: widget.user.id, userAction: UserAction.addModerator, value: false, - metadata: {"communityId": widget.postViewMedia.postView.community.id}, + metadata: {"communityId": widget.communityId}, )); setState(() => _userAction = UserAction.addModerator); break; @@ -151,11 +169,11 @@ class _UserPostActionBottomSheetState extends State { onPrimaryButtonPressed: (dialogContext, setPrimaryButtonEnabled) { widget.context.read().add( UserActionEvent( - userId: widget.postViewMedia.postView.creator.id, + userId: widget.user.id, userAction: UserAction.banFromCommunity, value: true, metadata: { - "communityId": widget.postViewMedia.postView.community.id, + "communityId": widget.communityId, "reason": messageController.text, "removeData": removeData, }, @@ -173,8 +191,8 @@ class _UserPostActionBottomSheetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ UserChip( - person: widget.postViewMedia.postView.creator, - personAvatar: UserAvatar(person: widget.postViewMedia.postView.creator), + person: widget.user, + personAvatar: UserAvatar(person: widget.user), userGroups: const [UserType.op], includeInstance: true, ), @@ -214,48 +232,48 @@ class _UserPostActionBottomSheetState extends State { final theme = Theme.of(context); final authState = context.read().state; - List userActions = UserPostAction.values.where((element) => element.permissionType == PermissionType.user).toList(); - List moderatorActions = UserPostAction.values.where((element) => element.permissionType == PermissionType.moderator).toList(); + List userActions = UserBottomSheetAction.values.where((element) => element.permissionType == PermissionType.user).toList(); + List moderatorActions = UserBottomSheetAction.values.where((element) => element.permissionType == PermissionType.moderator).toList(); // List adminActions = UserPostAction.values.where((element) => element.permissionType == PermissionType.admin).toList(); final account = authState.getSiteResponse?.myUser?.localUserView.person; final moderatedCommunities = authState.getSiteResponse?.myUser?.moderates ?? []; - final isModerator = moderatedCommunities.where((communityModeratorView) => communityModeratorView.community.actorId == widget.postViewMedia.postView.community.actorId).isNotEmpty; + final isModerator = moderatedCommunities.where((communityModeratorView) => communityModeratorView.community.id == widget.communityId).isNotEmpty; // final isAdmin = authState.getSiteResponse?.admins.where((personView) => personView.person.actorId == account?.actorId).isNotEmpty ?? false; final isLoggedIn = authState.isLoggedIn; final blockedUsers = authState.getSiteResponse?.myUser?.personBlocks ?? []; - final isUserBlocked = blockedUsers.where((personBlockView) => personBlockView.person.actorId == widget.postViewMedia.postView.creator.actorId).isNotEmpty; - final isUserCommunityModerator = widget.postViewMedia.postView.creatorIsModerator ?? false; - final isUserBannedFromCommunity = widget.postViewMedia.postView.creatorBannedFromCommunity; + final isUserBlocked = blockedUsers.where((personBlockView) => personBlockView.person.actorId == widget.user.actorId).isNotEmpty; + final isUserCommunityModerator = widget.isUserCommunityModerator ?? false; + final isUserBannedFromCommunity = widget.isUserBannedFromCommunity ?? false; // final isUserBannedFromInstance = widget.postViewMedia.postView.creator.banned; // final isUserAdmin = widget.postViewMedia.postView.creatorIsAdmin ?? false; if (!isLoggedIn) { userActions = userActions.where((action) => action.requiresAuthentication == false).toList(); } else { - if (account?.actorId == widget.postViewMedia.postView.creator.actorId) { - userActions = userActions.where((action) => action != UserPostAction.blockUser && action != UserPostAction.unblockUser).toList(); + if (account?.actorId == widget.user.actorId) { + userActions = userActions.where((action) => action != UserBottomSheetAction.blockUser && action != UserBottomSheetAction.unblockUser).toList(); } if (isUserBlocked) { - userActions = userActions.where((action) => action != UserPostAction.blockUser).toList(); + userActions = userActions.where((action) => action != UserBottomSheetAction.blockUser).toList(); } else { - userActions = userActions.where((action) => action != UserPostAction.unblockUser).toList(); + userActions = userActions.where((action) => action != UserBottomSheetAction.unblockUser).toList(); } if (isUserCommunityModerator) { - moderatorActions = moderatorActions.where((action) => action != UserPostAction.addUserAsCommunityModerator).toList(); - moderatorActions = moderatorActions.where((action) => action != UserPostAction.banUserFromCommunity && action != UserPostAction.unbanUserFromCommunity).toList(); + moderatorActions = moderatorActions.where((action) => action != UserBottomSheetAction.addUserAsCommunityModerator).toList(); + moderatorActions = moderatorActions.where((action) => action != UserBottomSheetAction.banUserFromCommunity && action != UserBottomSheetAction.unbanUserFromCommunity).toList(); } else { - moderatorActions = moderatorActions.where((action) => action != UserPostAction.removeUserAsCommunityModerator).toList(); + moderatorActions = moderatorActions.where((action) => action != UserBottomSheetAction.removeUserAsCommunityModerator).toList(); } if (isUserBannedFromCommunity) { - moderatorActions = moderatorActions.where((action) => action != UserPostAction.banUserFromCommunity).toList(); + moderatorActions = moderatorActions.where((action) => action != UserBottomSheetAction.banUserFromCommunity).toList(); } else { - moderatorActions = moderatorActions.where((action) => action != UserPostAction.unbanUserFromCommunity).toList(); + moderatorActions = moderatorActions.where((action) => action != UserBottomSheetAction.unbanUserFromCommunity).toList(); } // if (isUserBannedFromInstance) { From a16a0557cb78db64e41895faca91029fb298b42e Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Tue, 31 Dec 2024 13:43:09 -0800 Subject: [PATCH 3/9] feat: generalize instance bottom action sheet, add instance actions to comment bottom sheet --- .../widgets/comment_action_bottom_sheet.dart | 10 +- .../instance_action_bottom_sheet.dart} | 144 +++++++++++------- .../widgets/post_action_bottom_sheet.dart | 9 +- 3 files changed, 101 insertions(+), 62 deletions(-) rename lib/{post/widgets/instance_post_action_bottom_sheet.dart => instance/widgets/instance_action_bottom_sheet.dart} (57%) diff --git a/lib/comment/widgets/comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_action_bottom_sheet.dart index 675143fbf..405d615ee 100644 --- a/lib/comment/widgets/comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_action_bottom_sheet.dart @@ -10,6 +10,7 @@ import 'package:thunder/comment/widgets/general_comment_action_bottom_sheet.dart import 'package:thunder/community/enums/community_action.dart'; import 'package:thunder/community/widgets/post_card_metadata.dart'; import 'package:thunder/core/enums/full_name.dart'; +import 'package:thunder/instance/widgets/instance_action_bottom_sheet.dart'; import 'package:thunder/user/enums/user_action.dart'; import 'package:thunder/user/widgets/user_action_bottom_sheet.dart'; import 'package:thunder/utils/instance.dart'; @@ -112,6 +113,11 @@ class _CommentActionBottomSheetState extends State { widget.onAction?.call(userAction: userAction, commentView: widget.commentView); }, ), + GeneralCommentAction.instance => InstanceActionBottomSheet( + userInstanceId: widget.commentView.creator.instanceId, + userInstanceUrl: widget.commentView.creator.actorId, + onAction: () {}, + ), _ => SizedBox(), // GeneralCommentAction.post => PostPostActionBottomSheet( @@ -122,10 +128,6 @@ class _CommentActionBottomSheetState extends State { // }, // ), - // GeneralCommentAction.instance => InstancePostActionBottomSheet( - // postViewMedia: widget.commentView, - // onAction: () {}, - // ), // GeneralCommentAction.share => SharePostActionBottomSheet( // context: widget.context, // postViewMedia: widget.commentView, diff --git a/lib/post/widgets/instance_post_action_bottom_sheet.dart b/lib/instance/widgets/instance_action_bottom_sheet.dart similarity index 57% rename from lib/post/widgets/instance_post_action_bottom_sheet.dart rename to lib/instance/widgets/instance_action_bottom_sheet.dart index 40e6c8da4..fb3253696 100644 --- a/lib/post/widgets/instance_post_action_bottom_sheet.dart +++ b/lib/instance/widgets/instance_action_bottom_sheet.dart @@ -3,19 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:thunder/core/auth/bloc/auth_bloc.dart'; -import 'package:thunder/core/models/post_view_media.dart'; import 'package:thunder/instance/bloc/instance_bloc.dart'; import 'package:thunder/instance/enums/instance_action.dart'; import 'package:thunder/instance/utils/navigate_instance.dart'; import 'package:thunder/post/enums/post_action.dart'; import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/shared/bottom_sheet_action.dart'; -// import 'package:thunder/shared/divider.dart'; -// import 'package:thunder/thunder/thunder_icons.dart'; import 'package:thunder/utils/instance.dart'; -/// Defines the actions that can be taken on a community -enum InstancePostAction { +/// Defines the actions that can be taken on an instance +enum InstanceBottomSheetAction { visitCommunityInstance(icon: Icons.language_rounded, permissionType: PermissionType.user, requiresAuthentication: false), blockCommunityInstance(icon: Icons.block_rounded, permissionType: PermissionType.user, requiresAuthentication: true), unblockCommunityInstance(icon: Icons.block_rounded, permissionType: PermissionType.user, requiresAuthentication: true), @@ -25,12 +22,12 @@ enum InstancePostAction { ; String get name => switch (this) { - InstancePostAction.visitCommunityInstance => l10n.visitCommunityInstance, - InstancePostAction.blockCommunityInstance => l10n.blockCommunityInstance, - InstancePostAction.unblockCommunityInstance => l10n.unblockCommunityInstance, - InstancePostAction.visitUserInstance => l10n.visitUserInstance, - InstancePostAction.blockUserInstance => l10n.blockUserInstance, - InstancePostAction.unblockUserInstance => l10n.unblockUserInstance, + InstanceBottomSheetAction.visitCommunityInstance => l10n.visitCommunityInstance, + InstanceBottomSheetAction.blockCommunityInstance => l10n.blockCommunityInstance, + InstanceBottomSheetAction.unblockCommunityInstance => l10n.unblockCommunityInstance, + InstanceBottomSheetAction.visitUserInstance => l10n.visitUserInstance, + InstanceBottomSheetAction.blockUserInstance => l10n.blockUserInstance, + InstanceBottomSheetAction.unblockUserInstance => l10n.unblockUserInstance, }; /// The icon to use for the action @@ -42,63 +39,79 @@ enum InstancePostAction { /// Whether or not the action requires user authentication final bool requiresAuthentication; - const InstancePostAction({required this.icon, required this.permissionType, required this.requiresAuthentication}); + const InstanceBottomSheetAction({required this.icon, required this.permissionType, required this.requiresAuthentication}); } /// A bottom sheet that allows the user to perform actions on a instance. /// -/// Given a [postViewMedia] and a [onAction] callback, this widget will display a list of actions that can be taken on the instance. -class InstancePostActionBottomSheet extends StatefulWidget { - const InstancePostActionBottomSheet({super.key, required this.postViewMedia, required this.onAction}); +/// Given an [onAction] callback, this widget will display a list of actions that can be taken on the instance. +class InstanceActionBottomSheet extends StatefulWidget { + const InstanceActionBottomSheet({ + super.key, + this.communityInstanceId, + this.communityInstanceUrl, + this.userInstanceId, + this.userInstanceUrl, + required this.onAction, + }); - /// The post information - final PostViewMedia postViewMedia; + /// The instance id for the given community + final int? communityInstanceId; + + /// The community actor id + final String? communityInstanceUrl; + + /// The instance id for the given user + final int? userInstanceId; + + /// The user actor id + final String? userInstanceUrl; /// Called when an action is selected final Function() onAction; @override - State createState() => _InstancePostActionBottomSheetState(); + State createState() => _InstanceActionBottomSheetState(); } -class _InstancePostActionBottomSheetState extends State { - void performAction(InstancePostAction action) { +class _InstanceActionBottomSheetState extends State { + void performAction(InstanceBottomSheetAction action) { switch (action) { - case InstancePostAction.visitCommunityInstance: - navigateToInstancePage(context, instanceHost: fetchInstanceNameFromUrl(widget.postViewMedia.postView.community.actorId)!, instanceId: widget.postViewMedia.postView.community.instanceId); + case InstanceBottomSheetAction.visitCommunityInstance: + navigateToInstancePage(context, instanceHost: fetchInstanceNameFromUrl(widget.communityInstanceUrl)!, instanceId: widget.communityInstanceId); break; - case InstancePostAction.blockCommunityInstance: + case InstanceBottomSheetAction.blockCommunityInstance: context.read().add(InstanceActionEvent( instanceAction: InstanceAction.block, - instanceId: widget.postViewMedia.postView.community.instanceId, - domain: fetchInstanceNameFromUrl(widget.postViewMedia.postView.community.actorId), + instanceId: widget.communityInstanceId!, + domain: fetchInstanceNameFromUrl(widget.communityInstanceUrl), value: true, )); break; - case InstancePostAction.unblockCommunityInstance: + case InstanceBottomSheetAction.unblockCommunityInstance: context.read().add(InstanceActionEvent( instanceAction: InstanceAction.block, - instanceId: widget.postViewMedia.postView.community.instanceId, - domain: fetchInstanceNameFromUrl(widget.postViewMedia.postView.community.actorId), + instanceId: widget.communityInstanceId!, + domain: fetchInstanceNameFromUrl(widget.communityInstanceUrl), value: false, )); break; - case InstancePostAction.visitUserInstance: - navigateToInstancePage(context, instanceHost: fetchInstanceNameFromUrl(widget.postViewMedia.postView.creator.actorId)!, instanceId: widget.postViewMedia.postView.creator.instanceId); + case InstanceBottomSheetAction.visitUserInstance: + navigateToInstancePage(context, instanceHost: fetchInstanceNameFromUrl(widget.userInstanceUrl)!, instanceId: widget.userInstanceId); break; - case InstancePostAction.blockUserInstance: + case InstanceBottomSheetAction.blockUserInstance: context.read().add(InstanceActionEvent( instanceAction: InstanceAction.block, - instanceId: widget.postViewMedia.postView.creator.instanceId, - domain: fetchInstanceNameFromUrl(widget.postViewMedia.postView.creator.actorId), + instanceId: widget.userInstanceId!, + domain: fetchInstanceNameFromUrl(widget.userInstanceUrl), value: true, )); break; - case InstancePostAction.unblockUserInstance: + case InstanceBottomSheetAction.unblockUserInstance: context.read().add(InstanceActionEvent( instanceAction: InstanceAction.block, - instanceId: widget.postViewMedia.postView.creator.instanceId, - domain: fetchInstanceNameFromUrl(widget.postViewMedia.postView.creator.actorId), + instanceId: widget.userInstanceId!, + domain: fetchInstanceNameFromUrl(widget.userInstanceUrl), value: false, )); break; @@ -109,7 +122,7 @@ class _InstancePostActionBottomSheetState extends State().state; - List userActions = InstancePostAction.values.where((element) => element.permissionType == PermissionType.user).toList(); + List userActions = InstanceBottomSheetAction.values.where((element) => element.permissionType == PermissionType.user).toList(); // List moderatorActions = InstancePostAction.values.where((element) => element.permissionType == PermissionType.moderator).toList(); // List adminActions = InstancePostAction.values.where((element) => element.permissionType == PermissionType.admin).toList(); @@ -121,39 +134,60 @@ class _InstancePostActionBottomSheetState extends State ibv.instance.id == widget.postViewMedia.postView.community.instanceId).isNotEmpty; - final isUserInstanceBlocked = blockedInstances.where((ibv) => ibv.instance.id == widget.postViewMedia.postView.creator.instanceId).isNotEmpty; + final isCommunityInstanceBlocked = blockedInstances.where((ibv) => ibv.instance.id == widget.communityInstanceId).isNotEmpty; + final isUserInstanceBlocked = blockedInstances.where((ibv) => ibv.instance.id == widget.userInstanceId).isNotEmpty; + + // Filter out actions that don't have the proper information passed in + if (widget.communityInstanceId == null || widget.communityInstanceUrl == null) { + userActions = userActions + .where( + (action) => + action != InstanceBottomSheetAction.visitCommunityInstance && + action != InstanceBottomSheetAction.blockCommunityInstance && + action != InstanceBottomSheetAction.unblockCommunityInstance, + ) + .toList(); + } + + if (widget.userInstanceId == null || widget.userInstanceUrl == null) { + userActions = userActions + .where((action) => action != InstanceBottomSheetAction.visitUserInstance && action != InstanceBottomSheetAction.blockUserInstance && action != InstanceBottomSheetAction.unblockUserInstance) + .toList(); + } if (!isLoggedIn) { userActions = userActions.where((action) => action.requiresAuthentication == false).toList(); } else { + // Filter out actions that the user can't perform if (isCommunityInstanceBlocked) { - userActions = userActions.where((action) => action != InstancePostAction.blockCommunityInstance).toList(); + userActions = userActions.where((action) => action != InstanceBottomSheetAction.blockCommunityInstance).toList(); } else { - userActions = userActions.where((action) => action != InstancePostAction.unblockCommunityInstance).toList(); + userActions = userActions.where((action) => action != InstanceBottomSheetAction.unblockCommunityInstance).toList(); } if (isUserInstanceBlocked) { - userActions = userActions.where((action) => action != InstancePostAction.blockUserInstance).toList(); + userActions = userActions.where((action) => action != InstanceBottomSheetAction.blockUserInstance).toList(); } else { - userActions = userActions.where((action) => action != InstancePostAction.unblockUserInstance).toList(); + userActions = userActions.where((action) => action != InstanceBottomSheetAction.unblockUserInstance).toList(); } } - if (communityInstance == userInstance) { - userActions.removeWhere((action) => action == InstancePostAction.visitUserInstance || action == InstancePostAction.blockUserInstance); + // Remove duplicate actions + if (userInstance == communityInstance) { + userActions.removeWhere((action) => action == InstanceBottomSheetAction.visitUserInstance || action == InstanceBottomSheetAction.blockUserInstance); } + // Filter out any instances that match the account instance, and prevent the user from blocking their own instance if (communityInstance == accountInstance) { - userActions.removeWhere((action) => action == InstancePostAction.blockCommunityInstance); + userActions.removeWhere((action) => action == InstanceBottomSheetAction.blockCommunityInstance); } if (userInstance == accountInstance) { - userActions.removeWhere((action) => action == InstancePostAction.blockUserInstance); + userActions.removeWhere((action) => action == InstanceBottomSheetAction.blockUserInstance); } return BlocListener( @@ -171,12 +205,12 @@ class _InstancePostActionBottomSheetState extends State BottomSheetAction( leading: Icon(instancePostAction.icon), subtitle: switch (instancePostAction) { - InstancePostAction.visitCommunityInstance => communityInstance, - InstancePostAction.blockCommunityInstance => communityInstance, - InstancePostAction.unblockCommunityInstance => communityInstance, - InstancePostAction.visitUserInstance => userInstance, - InstancePostAction.blockUserInstance => userInstance, - InstancePostAction.unblockUserInstance => userInstance, + InstanceBottomSheetAction.visitCommunityInstance => communityInstance, + InstanceBottomSheetAction.blockCommunityInstance => communityInstance, + InstanceBottomSheetAction.unblockCommunityInstance => communityInstance, + InstanceBottomSheetAction.visitUserInstance => userInstance, + InstanceBottomSheetAction.blockUserInstance => userInstance, + InstanceBottomSheetAction.unblockUserInstance => userInstance, }, title: instancePostAction.name, onTap: () => performAction(instancePostAction), diff --git a/lib/post/widgets/post_action_bottom_sheet.dart b/lib/post/widgets/post_action_bottom_sheet.dart index 344252dc4..ff23cb7cd 100644 --- a/lib/post/widgets/post_action_bottom_sheet.dart +++ b/lib/post/widgets/post_action_bottom_sheet.dart @@ -13,7 +13,7 @@ import 'package:thunder/core/models/post_view_media.dart'; import 'package:thunder/post/enums/post_action.dart'; import 'package:thunder/post/widgets/community_post_action_bottom_sheet.dart'; import 'package:thunder/post/widgets/general_post_action_bottom_sheet.dart'; -import 'package:thunder/post/widgets/instance_post_action_bottom_sheet.dart'; +import 'package:thunder/instance/widgets/instance_action_bottom_sheet.dart'; import 'package:thunder/post/widgets/post_post_action_bottom_sheet.dart'; import 'package:thunder/post/widgets/share_post_action_bottom_sheet.dart'; import 'package:thunder/user/widgets/user_action_bottom_sheet.dart'; @@ -136,8 +136,11 @@ class _PostActionBottomSheetState extends State { widget.onAction?.call(communityAction: communityAction, postViewMedia: widget.postViewMedia); }, ), - GeneralPostAction.instance => InstancePostActionBottomSheet( - postViewMedia: widget.postViewMedia, + GeneralPostAction.instance => InstanceActionBottomSheet( + userInstanceId: widget.postViewMedia.postView.creator.instanceId, + userInstanceUrl: widget.postViewMedia.postView.creator.actorId, + communityInstanceId: widget.postViewMedia.postView.community.instanceId, + communityInstanceUrl: widget.postViewMedia.postView.community.actorId, onAction: () {}, ), GeneralPostAction.share => SharePostActionBottomSheet( From 87c16357aec8e2dcf10bb96b15335dba38f60aa4 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Wed, 1 Jan 2025 12:10:35 -0800 Subject: [PATCH 4/9] feat: generalize share bottom sheet, add share actions to comments --- .../widgets/comment_action_bottom_sheet.dart | 27 ++- .../widgets/post_action_bottom_sheet.dart | 4 +- .../share_post_action_bottom_sheet.dart | 187 ---------------- .../{ => share}/advanced_share_sheet.dart | 0 .../share/share_action_bottom_sheet.dart | 207 ++++++++++++++++++ 5 files changed, 222 insertions(+), 203 deletions(-) delete mode 100644 lib/post/widgets/share_post_action_bottom_sheet.dart rename lib/shared/{ => share}/advanced_share_sheet.dart (100%) create mode 100644 lib/shared/share/share_action_bottom_sheet.dart diff --git a/lib/comment/widgets/comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_action_bottom_sheet.dart index 405d615ee..065b370eb 100644 --- a/lib/comment/widgets/comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_action_bottom_sheet.dart @@ -11,6 +11,7 @@ import 'package:thunder/community/enums/community_action.dart'; import 'package:thunder/community/widgets/post_card_metadata.dart'; import 'package:thunder/core/enums/full_name.dart'; import 'package:thunder/instance/widgets/instance_action_bottom_sheet.dart'; +import 'package:thunder/shared/share/share_action_bottom_sheet.dart'; import 'package:thunder/user/enums/user_action.dart'; import 'package:thunder/user/widgets/user_action_bottom_sheet.dart'; import 'package:thunder/utils/instance.dart'; @@ -103,6 +104,13 @@ class _CommentActionBottomSheetState extends State { widget.onAction?.call(commentAction: commentAction, commentView: widget.commentView, value: value); }, ), + // GeneralCommentAction.post => PostPostActionBottomSheet( + // context: widget.context, + // postViewMedia: widget.commentView, + // onAction: (PostAction postAction, PostViewMedia? updatedPostViewMedia) { + // widget.onAction?.call(postAction: postAction, postViewMedia: widget.commentView); + // }, + // ), GeneralCommentAction.user => UserActionBottomSheet( context: widget.context, user: widget.commentView.creator, @@ -118,21 +126,12 @@ class _CommentActionBottomSheetState extends State { userInstanceUrl: widget.commentView.creator.actorId, onAction: () {}, ), + GeneralCommentAction.share => ShareActionBottomSheet( + context: widget.context, + commentView: widget.commentView, + onAction: () {}, + ), _ => SizedBox(), - - // GeneralCommentAction.post => PostPostActionBottomSheet( - // context: widget.context, - // postViewMedia: widget.commentView, - // onAction: (PostAction postAction, PostViewMedia? updatedPostViewMedia) { - // widget.onAction?.call(postAction: postAction, postViewMedia: widget.commentView); - // }, - // ), - - // GeneralCommentAction.share => SharePostActionBottomSheet( - // context: widget.context, - // postViewMedia: widget.commentView, - // onAction: () {}, - // ), }; return SafeArea( diff --git a/lib/post/widgets/post_action_bottom_sheet.dart b/lib/post/widgets/post_action_bottom_sheet.dart index ff23cb7cd..fa104e02b 100644 --- a/lib/post/widgets/post_action_bottom_sheet.dart +++ b/lib/post/widgets/post_action_bottom_sheet.dart @@ -15,7 +15,7 @@ import 'package:thunder/post/widgets/community_post_action_bottom_sheet.dart'; import 'package:thunder/post/widgets/general_post_action_bottom_sheet.dart'; import 'package:thunder/instance/widgets/instance_action_bottom_sheet.dart'; import 'package:thunder/post/widgets/post_post_action_bottom_sheet.dart'; -import 'package:thunder/post/widgets/share_post_action_bottom_sheet.dart'; +import 'package:thunder/shared/share/share_action_bottom_sheet.dart'; import 'package:thunder/user/widgets/user_action_bottom_sheet.dart'; import 'package:thunder/user/enums/user_action.dart'; import 'package:thunder/utils/instance.dart'; @@ -143,7 +143,7 @@ class _PostActionBottomSheetState extends State { communityInstanceUrl: widget.postViewMedia.postView.community.actorId, onAction: () {}, ), - GeneralPostAction.share => SharePostActionBottomSheet( + GeneralPostAction.share => ShareActionBottomSheet( context: widget.context, postViewMedia: widget.postViewMedia, onAction: () {}, diff --git a/lib/post/widgets/share_post_action_bottom_sheet.dart b/lib/post/widgets/share_post_action_bottom_sheet.dart deleted file mode 100644 index 24579f38f..000000000 --- a/lib/post/widgets/share_post_action_bottom_sheet.dart +++ /dev/null @@ -1,187 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; - -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -import 'package:share_plus/share_plus.dart'; - -import 'package:thunder/core/enums/media_type.dart'; -import 'package:thunder/core/models/post_view_media.dart'; -import 'package:thunder/core/singletons/lemmy_client.dart'; -import 'package:thunder/instance/bloc/instance_bloc.dart'; -import 'package:thunder/post/enums/post_action.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; -import 'package:thunder/shared/advanced_share_sheet.dart'; -import 'package:thunder/shared/bottom_sheet_action.dart'; -import 'package:thunder/shared/snackbar.dart'; - -/// Defines the actions that can be taken on a post when sharing -enum SharePostAction { - sharePost(icon: Icons.share_rounded, permissionType: PermissionType.user, requiresAuthentication: false), - sharePostLocal(icon: Icons.share_rounded, permissionType: PermissionType.user, requiresAuthentication: false), - shareImage(icon: Icons.image_rounded, permissionType: PermissionType.user, requiresAuthentication: false), - shareMedia(icon: Icons.personal_video_rounded, permissionType: PermissionType.user, requiresAuthentication: false), - shareLink(icon: Icons.link_rounded, permissionType: PermissionType.user, requiresAuthentication: false), - shareAdvanced(icon: Icons.screen_share_rounded, permissionType: PermissionType.user, requiresAuthentication: false), - ; - - String get name => switch (this) { - SharePostAction.sharePost => l10n.sharePost, - SharePostAction.sharePostLocal => l10n.sharePostLocal, - SharePostAction.shareImage => l10n.shareImage, - SharePostAction.shareMedia => l10n.shareMediaLink, - SharePostAction.shareLink => l10n.shareLink, - SharePostAction.shareAdvanced => l10n.advanced, - }; - - /// The icon to use for the action - final IconData icon; - - /// The permission type to use for the action - final PermissionType permissionType; - - /// Whether or not the action requires user authentication - final bool requiresAuthentication; - - const SharePostAction({required this.icon, required this.permissionType, required this.requiresAuthentication}); -} - -/// A bottom sheet that allows the user to perform actions on a instance. -/// -/// Given a [postViewMedia] and a [onAction] callback, this widget will display a list of actions that can be taken on the instance. -class SharePostActionBottomSheet extends StatefulWidget { - const SharePostActionBottomSheet({super.key, required this.context, required this.postViewMedia, required this.onAction}); - - /// The parent context - final BuildContext context; - - /// The post information - final PostViewMedia postViewMedia; - - /// Called when an action is selected - final Function() onAction; - - @override - State createState() => _SharePostActionBottomSheetState(); -} - -class _SharePostActionBottomSheetState extends State { - void retrieveMedia(String? url) async { - if (url == null) return; - - try { - // Try to get the cached image first - var media = await DefaultCacheManager().getFileFromCache(url); - File? mediaFile = media?.file; - - if (media == null) { - showSnackbar(l10n.downloadingMedia); - mediaFile = await DefaultCacheManager().getSingleFile(url); - } - - await Share.shareXFiles([XFile(mediaFile!.path)]); - } catch (e) { - showSnackbar(l10n.errorDownloadingMedia(e)); - } - } - - void performAction(SharePostAction action) { - switch (action) { - case SharePostAction.sharePost: - Share.share(widget.postViewMedia.postView.post.apId); - break; - case SharePostAction.sharePostLocal: - Share.share(LemmyClient.instance.generatePostUrl(widget.postViewMedia.postView.post.id)); - break; - case SharePostAction.shareImage: - retrieveMedia(widget.postViewMedia.media.first.imageUrl!); - break; - case SharePostAction.shareMedia: - Share.share(widget.postViewMedia.media.first.mediaUrl!); - break; - case SharePostAction.shareLink: - if (widget.postViewMedia.media.first.originalUrl != null) Share.share(widget.postViewMedia.media.first.originalUrl!); - break; - case SharePostAction.shareAdvanced: - showAdvancedShareSheet(widget.context, widget.postViewMedia); - break; - default: - break; - } - } - - String? generateSubtitle(SharePostAction action) { - PostViewMedia postViewMedia = widget.postViewMedia; - - switch (action) { - case SharePostAction.sharePost: - return postViewMedia.postView.post.apId; - case SharePostAction.sharePostLocal: - return LemmyClient.instance.generatePostUrl(postViewMedia.postView.post.id); - case SharePostAction.shareImage: - return postViewMedia.media.first.imageUrl; - case SharePostAction.shareMedia: - return postViewMedia.media.first.mediaUrl; - case SharePostAction.shareLink: - return postViewMedia.media.first.originalUrl; - case SharePostAction.shareAdvanced: - return l10n.useAdvancedShareSheet; - default: - return null; - } - } - - @override - Widget build(BuildContext context) { - List userActions = SharePostAction.values.where((element) => element.permissionType == PermissionType.user).toList(); - - // Remove the share link option if there is no link or if the media link is the same as the external link - if (widget.postViewMedia.media.isEmpty || - widget.postViewMedia.media.first.mediaType == MediaType.text || - widget.postViewMedia.media.first.originalUrl == widget.postViewMedia.media.first.imageUrl || - widget.postViewMedia.media.first.originalUrl == widget.postViewMedia.media.first.mediaUrl) { - userActions.removeWhere((action) => action == SharePostAction.shareLink); - } - - // Remove the share image option if there is no image - if (widget.postViewMedia.media.isEmpty || widget.postViewMedia.media.first.imageUrl?.isNotEmpty != true) { - userActions.removeWhere((action) => action == SharePostAction.shareImage); - } - - // Remove the share media option if there is no media - if (widget.postViewMedia.media.isEmpty || widget.postViewMedia.media.first.mediaUrl?.isNotEmpty != true) { - userActions.removeWhere((action) => action == SharePostAction.shareMedia); - } - - // Remove the share local option if it is the same as the original - if (widget.postViewMedia.postView.post.apId == LemmyClient.instance.generatePostUrl(widget.postViewMedia.postView.post.id)) { - userActions.removeWhere((action) => action == SharePostAction.sharePostLocal); - } - - return BlocListener( - listener: (context, state) { - if (state.status == InstanceStatus.success) { - Navigator.of(context).pop(); - widget.onAction(); - } - }, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...userActions - .map( - (sharePostAction) => BottomSheetAction( - leading: Icon(sharePostAction.icon), - trailing: sharePostAction == SharePostAction.shareAdvanced ? const Icon(Icons.chevron_right_rounded) : null, - subtitle: generateSubtitle(sharePostAction), - title: sharePostAction.name, - onTap: () => performAction(sharePostAction), - ), - ) - .toList() as List, - ], - ), - ); - } -} diff --git a/lib/shared/advanced_share_sheet.dart b/lib/shared/share/advanced_share_sheet.dart similarity index 100% rename from lib/shared/advanced_share_sheet.dart rename to lib/shared/share/advanced_share_sheet.dart diff --git a/lib/shared/share/share_action_bottom_sheet.dart b/lib/shared/share/share_action_bottom_sheet.dart new file mode 100644 index 000000000..6e61819d3 --- /dev/null +++ b/lib/shared/share/share_action_bottom_sheet.dart @@ -0,0 +1,207 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:lemmy_api_client/v3.dart'; +import 'package:share_plus/share_plus.dart'; + +import 'package:thunder/core/enums/media_type.dart'; +import 'package:thunder/core/models/post_view_media.dart'; +import 'package:thunder/core/singletons/lemmy_client.dart'; +import 'package:thunder/post/enums/post_action.dart'; +import 'package:thunder/post/utils/comment_action_helpers.dart'; +import 'package:thunder/shared/share/advanced_share_sheet.dart'; +import 'package:thunder/shared/bottom_sheet_action.dart'; +import 'package:thunder/shared/snackbar.dart'; + +/// Defines the actions that can be taken on a post when sharing +enum ShareBottomSheetAction { + shareComment(icon: Icons.comment_rounded, permissionType: PermissionType.user, requiresAuthentication: false), + shareCommentLocal(icon: Icons.comment_rounded, permissionType: PermissionType.user, requiresAuthentication: false), + sharePost(icon: Icons.share_rounded, permissionType: PermissionType.user, requiresAuthentication: false), + sharePostLocal(icon: Icons.share_rounded, permissionType: PermissionType.user, requiresAuthentication: false), + shareImage(icon: Icons.image_rounded, permissionType: PermissionType.user, requiresAuthentication: false), + shareMedia(icon: Icons.personal_video_rounded, permissionType: PermissionType.user, requiresAuthentication: false), + shareLink(icon: Icons.link_rounded, permissionType: PermissionType.user, requiresAuthentication: false), + shareAdvanced(icon: Icons.screen_share_rounded, permissionType: PermissionType.user, requiresAuthentication: false), + ; + + String get name => switch (this) { + ShareBottomSheetAction.shareComment => l10n.shareComment, + ShareBottomSheetAction.shareCommentLocal => l10n.shareCommentLocal, + ShareBottomSheetAction.sharePost => l10n.sharePost, + ShareBottomSheetAction.sharePostLocal => l10n.sharePostLocal, + ShareBottomSheetAction.shareImage => l10n.shareImage, + ShareBottomSheetAction.shareMedia => l10n.shareMediaLink, + ShareBottomSheetAction.shareLink => l10n.shareLink, + ShareBottomSheetAction.shareAdvanced => l10n.advanced, + }; + + /// The icon to use for the action + final IconData icon; + + /// The permission type to use for the action + final PermissionType permissionType; + + /// Whether or not the action requires user authentication + final bool requiresAuthentication; + + const ShareBottomSheetAction({required this.icon, required this.permissionType, required this.requiresAuthentication}); +} + +/// A bottom sheet that allows the user to perform share actions. +/// +/// Given a [postViewMedia] or a [commentView], and a [onAction] callback, this widget will display a list of share actions that can be taken. +class ShareActionBottomSheet extends StatefulWidget { + const ShareActionBottomSheet({super.key, required this.context, this.postViewMedia, this.commentView, required this.onAction}); + + /// The parent context + final BuildContext context; + + /// The post information + final PostViewMedia? postViewMedia; + + /// The comment information + final CommentView? commentView; + + /// Called when an action is selected + final Function() onAction; + + @override + State createState() => _ShareActionBottomSheetState(); +} + +class _ShareActionBottomSheetState extends State { + void retrieveMedia(String? url) async { + if (url == null) return; + + try { + // Try to get the cached image first + var media = await DefaultCacheManager().getFileFromCache(url); + File? mediaFile = media?.file; + + if (media == null) { + showSnackbar(l10n.downloadingMedia); + mediaFile = await DefaultCacheManager().getSingleFile(url); + } + + await Share.shareXFiles([XFile(mediaFile!.path)]); + } catch (e) { + showSnackbar(l10n.errorDownloadingMedia(e)); + } + } + + void performAction(ShareBottomSheetAction action) { + PostViewMedia? postViewMedia = widget.postViewMedia; + CommentView? commentView = widget.commentView; + + switch (action) { + case ShareBottomSheetAction.shareComment: + Share.share(commentView!.comment.apId); + break; + case ShareBottomSheetAction.shareCommentLocal: + Share.share(LemmyClient.instance.generateCommentUrl(commentView!.comment.id)); + break; + case ShareBottomSheetAction.sharePost: + Share.share(postViewMedia!.postView.post.apId); + break; + case ShareBottomSheetAction.sharePostLocal: + Share.share(LemmyClient.instance.generatePostUrl(postViewMedia!.postView.post.id)); + break; + case ShareBottomSheetAction.shareImage: + retrieveMedia(postViewMedia!.media.first.imageUrl!); + break; + case ShareBottomSheetAction.shareMedia: + Share.share(postViewMedia!.media.first.mediaUrl!); + break; + case ShareBottomSheetAction.shareLink: + if (postViewMedia!.media.first.originalUrl != null) Share.share(postViewMedia.media.first.originalUrl!); + break; + case ShareBottomSheetAction.shareAdvanced: + showAdvancedShareSheet(widget.context, postViewMedia!); + break; + } + } + + String? generateSubtitle(ShareBottomSheetAction action) { + PostViewMedia? postViewMedia = widget.postViewMedia; + CommentView? commentView = widget.commentView; + + switch (action) { + case ShareBottomSheetAction.shareComment: + return commentView!.comment.apId; + case ShareBottomSheetAction.shareCommentLocal: + return LemmyClient.instance.generateCommentUrl(commentView!.comment.id); + case ShareBottomSheetAction.sharePost: + return postViewMedia!.postView.post.apId; + case ShareBottomSheetAction.sharePostLocal: + return LemmyClient.instance.generatePostUrl(postViewMedia!.postView.post.id); + case ShareBottomSheetAction.shareImage: + return postViewMedia!.media.first.imageUrl; + case ShareBottomSheetAction.shareMedia: + return postViewMedia!.media.first.mediaUrl; + case ShareBottomSheetAction.shareLink: + return postViewMedia!.media.first.originalUrl; + case ShareBottomSheetAction.shareAdvanced: + return l10n.useAdvancedShareSheet; + } + } + + @override + Widget build(BuildContext context) { + // Check to see if we are sharing a post or a comment. + List userActions = []; + + if (widget.commentView != null) { + userActions = [ShareBottomSheetAction.shareComment, ShareBottomSheetAction.shareCommentLocal]; + + // Remove the share local option if it is the same as the original + if (widget.commentView!.comment.apId == LemmyClient.instance.generateCommentUrl(widget.commentView!.comment.id)) { + userActions.removeWhere((action) => action == ShareBottomSheetAction.shareCommentLocal); + } + } else if (widget.postViewMedia != null) { + userActions = ShareBottomSheetAction.values.where((element) => element != ShareBottomSheetAction.shareComment && element != ShareBottomSheetAction.shareCommentLocal).toList(); + + // Remove the share link option if there is no link or if the media link is the same as the external link + if (widget.postViewMedia!.media.isEmpty || + widget.postViewMedia!.media.first.mediaType == MediaType.text || + widget.postViewMedia!.media.first.originalUrl == widget.postViewMedia!.media.first.imageUrl || + widget.postViewMedia!.media.first.originalUrl == widget.postViewMedia!.media.first.mediaUrl) { + userActions.removeWhere((action) => action == ShareBottomSheetAction.shareLink); + } + + // Remove the share image option if there is no image + if (widget.postViewMedia!.media.isEmpty || widget.postViewMedia!.media.first.imageUrl?.isNotEmpty != true) { + userActions.removeWhere((action) => action == ShareBottomSheetAction.shareImage); + } + + // Remove the share media option if there is no media + if (widget.postViewMedia!.media.isEmpty || widget.postViewMedia!.media.first.mediaUrl?.isNotEmpty != true) { + userActions.removeWhere((action) => action == ShareBottomSheetAction.shareMedia); + } + + // Remove the share local option if it is the same as the original + if (widget.postViewMedia!.postView.post.apId == LemmyClient.instance.generatePostUrl(widget.postViewMedia!.postView.post.id)) { + userActions.removeWhere((action) => action == ShareBottomSheetAction.sharePostLocal); + } + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...userActions + .map( + (sharePostAction) => BottomSheetAction( + leading: Icon(sharePostAction.icon), + trailing: sharePostAction == ShareBottomSheetAction.shareAdvanced ? const Icon(Icons.chevron_right_rounded) : null, + subtitle: generateSubtitle(sharePostAction), + title: sharePostAction.name, + onTap: () => performAction(sharePostAction), + ), + ) + .toList() as List, + ], + ); + } +} From ea237cf88ecc1357886b65916af6bfe3d7ee23a3 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Thu, 2 Jan 2025 10:05:28 -0800 Subject: [PATCH 5/9] feat: add general comment actions --- lib/comment/enums/comment_action.dart | 2 + .../widgets/comment_action_bottom_sheet.dart | 15 +- .../comment_comment_action_bottom_sheet.dart | 295 ++++++++++++++++++ lib/post/enums/post_action.dart | 2 +- lib/post/widgets/comment_card.dart | 6 +- 5 files changed, 309 insertions(+), 11 deletions(-) create mode 100644 lib/comment/widgets/comment_comment_action_bottom_sheet.dart diff --git a/lib/comment/enums/comment_action.dart b/lib/comment/enums/comment_action.dart index f112da323..deab31eac 100644 --- a/lib/comment/enums/comment_action.dart +++ b/lib/comment/enums/comment_action.dart @@ -1,6 +1,8 @@ import 'package:thunder/post/enums/post_action.dart'; enum CommentAction { + viewSource(permissionType: PermissionType.all), + /// User level comment actions vote(permissionType: PermissionType.user), save(permissionType: PermissionType.user), diff --git a/lib/comment/widgets/comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_action_bottom_sheet.dart index 065b370eb..e64656a18 100644 --- a/lib/comment/widgets/comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_action_bottom_sheet.dart @@ -6,6 +6,7 @@ import 'package:back_button_interceptor/back_button_interceptor.dart'; import 'package:lemmy_api_client/v3.dart'; import 'package:thunder/comment/enums/comment_action.dart'; +import 'package:thunder/comment/widgets/comment_comment_action_bottom_sheet.dart'; import 'package:thunder/comment/widgets/general_comment_action_bottom_sheet.dart'; import 'package:thunder/community/enums/community_action.dart'; import 'package:thunder/community/widgets/post_card_metadata.dart'; @@ -104,13 +105,13 @@ class _CommentActionBottomSheetState extends State { widget.onAction?.call(commentAction: commentAction, commentView: widget.commentView, value: value); }, ), - // GeneralCommentAction.post => PostPostActionBottomSheet( - // context: widget.context, - // postViewMedia: widget.commentView, - // onAction: (PostAction postAction, PostViewMedia? updatedPostViewMedia) { - // widget.onAction?.call(postAction: postAction, postViewMedia: widget.commentView); - // }, - // ), + GeneralCommentAction.comment => CommentCommentActionBottomSheet( + context: widget.context, + commentView: widget.commentView, + onAction: (CommentAction commentAction, CommentView? updatedCommentView, dynamic value) { + widget.onAction?.call(commentAction: commentAction, commentView: widget.commentView, value: value); + }, + ), GeneralCommentAction.user => UserActionBottomSheet( context: widget.context, user: widget.commentView.creator, diff --git a/lib/comment/widgets/comment_comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart new file mode 100644 index 000000000..12d169f06 --- /dev/null +++ b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart @@ -0,0 +1,295 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lemmy_api_client/v3.dart'; + +import 'package:thunder/comment/enums/comment_action.dart'; +import 'package:thunder/core/auth/bloc/auth_bloc.dart'; +import 'package:thunder/modlog/utils/navigate_modlog.dart'; +import 'package:thunder/post/enums/post_action.dart'; +import 'package:thunder/post/utils/comment_action_helpers.dart'; +import 'package:thunder/shared/bottom_sheet_action.dart'; +import 'package:thunder/shared/divider.dart'; +import 'package:thunder/shared/text/selectable_text_modal.dart'; +import 'package:thunder/thunder/thunder_icons.dart'; + +/// Defines the actions that can be taken on a comment +/// TODO: Implement admin-level actions +enum CommentBottomSheetAction { + selectCommentText(icon: Icons.select_all_rounded, permissionType: PermissionType.all, requiresAuthentication: false), + viewCommentSource(icon: Icons.code_rounded, permissionType: PermissionType.all, requiresAuthentication: false), + viewModlog(icon: Icons.history_rounded, permissionType: PermissionType.all, requiresAuthentication: true), + reportComment(icon: Icons.flag_rounded, permissionType: PermissionType.user, requiresAuthentication: true), + editComment(icon: Icons.edit_rounded, permissionType: PermissionType.user, requiresAuthentication: true), + deleteComment(icon: Icons.delete_rounded, permissionType: PermissionType.user, requiresAuthentication: true), + restoreComment(icon: Icons.restore_rounded, permissionType: PermissionType.user, requiresAuthentication: true), + removeComment(icon: Icons.delete_rounded, permissionType: PermissionType.moderator, requiresAuthentication: true), + restoreCommentAsModerator(icon: Icons.restore_rounded, permissionType: PermissionType.moderator, requiresAuthentication: true), + ; + + String get name => switch (this) { + CommentBottomSheetAction.selectCommentText => l10n.selectText, + CommentBottomSheetAction.viewCommentSource => l10n.viewCommentSource, + CommentBottomSheetAction.viewModlog => l10n.viewModlog, + CommentBottomSheetAction.reportComment => l10n.reportComment, + CommentBottomSheetAction.editComment => l10n.editComment, + CommentBottomSheetAction.deleteComment => "Delete Comment", + CommentBottomSheetAction.restoreComment => "Restore Comment", + CommentBottomSheetAction.removeComment => "Remove Comment", + CommentBottomSheetAction.restoreCommentAsModerator => "Restore Comment", + }; + + /// The icon to use for the action + final IconData icon; + + /// The permission type to use for the action + final PermissionType permissionType; + + /// Whether or not the action requires user authentication + final bool requiresAuthentication; + + const CommentBottomSheetAction({required this.icon, required this.permissionType, required this.requiresAuthentication}); +} + +/// A bottom sheet that allows the user to perform actions on the comment. +/// +/// Given a [commentView] and a [onAction] callback, this widget will display a list of actions that can be taken on the comment. +/// The [onAction] callback will be triggered when an action is performed. +class CommentCommentActionBottomSheet extends StatefulWidget { + const CommentCommentActionBottomSheet({super.key, required this.context, required this.commentView, required this.onAction}); + + /// The outer context + final BuildContext context; + + /// The comment information + final CommentView commentView; + + /// Called when an action is selected + final Function(CommentAction commentAction, CommentView? commentView, dynamic value) onAction; + + @override + State createState() => _CommentCommentActionBottomSheetState(); +} + +class _CommentCommentActionBottomSheetState extends State { + void performAction(CommentBottomSheetAction action) async { + final commentView = widget.commentView; + + switch (action) { + case CommentBottomSheetAction.selectCommentText: + Navigator.of(context).pop(); + showSelectableTextModal(context, text: commentView.comment.content); + return; + case CommentBottomSheetAction.viewCommentSource: + widget.onAction(CommentAction.viewSource, commentView, null); + break; + case CommentBottomSheetAction.viewModlog: + Navigator.of(context).pop(); + await navigateToModlogPage( + context, + subtitle: Text(l10n.removedComment), + modlogActionType: ModlogActionType.modRemoveComment, + commentId: widget.commentView.comment.id, + ); + return; + case CommentBottomSheetAction.reportComment: + Navigator.of(context).pop(); + widget.onAction(CommentAction.report, commentView, null); + return; + case CommentBottomSheetAction.editComment: + Navigator.of(context).pop(); + widget.onAction(CommentAction.edit, commentView, null); + return; + case CommentBottomSheetAction.deleteComment: + widget.onAction(CommentAction.delete, commentView, true); + break; + case CommentBottomSheetAction.restoreComment: + widget.onAction(CommentAction.delete, commentView, false); + break; + case CommentBottomSheetAction.removeComment: + // TODO: Implement remove comment + break; + case CommentBottomSheetAction.restoreCommentAsModerator: + // TODO: Implement restore comment as moderator + break; + } + + Navigator.of(context).pop(); + } + + // void showReportPostDialog() { + // Navigator.of(context).pop(); + // final TextEditingController messageController = TextEditingController(); + + // showThunderDialog( + // context: widget.context, + // title: l10n.reportPost, + // primaryButtonText: l10n.report(1), + // onPrimaryButtonPressed: (dialogContext, setPrimaryButtonEnabled) { + // widget.context.read().add( + // FeedItemActionedEvent( + // postAction: PostAction.report, + // postId: widget.postViewMedia.postView.post.id, + // value: messageController.text, + // ), + // ); + // Navigator.of(dialogContext).pop(); + // }, + // secondaryButtonText: l10n.cancel, + // onSecondaryButtonPressed: (context) => Navigator.of(context).pop(), + // contentWidgetBuilder: (_) => TextFormField( + // decoration: InputDecoration( + // border: const OutlineInputBorder(), + // labelText: l10n.message(0), + // ), + // autofocus: true, + // controller: messageController, + // maxLines: 4, + // ), + // ); + // } + + // void showRemovePostReasonDialog() { + // Navigator.of(context).pop(); + // final TextEditingController messageController = TextEditingController(); + + // showThunderDialog( + // context: widget.context, + // title: widget.postViewMedia.postView.post.removed ? l10n.restorePost : l10n.removalReason, + // primaryButtonText: widget.postViewMedia.postView.post.removed ? l10n.restore : l10n.remove, + // onPrimaryButtonPressed: (dialogContext, setPrimaryButtonEnabled) { + // widget.context.read().add( + // FeedItemActionedEvent( + // postAction: PostAction.remove, + // postId: widget.postViewMedia.postView.post.id, + // value: { + // 'remove': !widget.postViewMedia.postView.post.removed, + // 'reason': messageController.text, + // }, + // ), + // ); + // Navigator.of(dialogContext).pop(); + // }, + // secondaryButtonText: l10n.cancel, + // onSecondaryButtonPressed: (context) => Navigator.of(context).pop(), + // contentWidgetBuilder: (_) => TextFormField( + // decoration: InputDecoration( + // border: const OutlineInputBorder(), + // labelText: l10n.message(0), + // ), + // autofocus: true, + // controller: messageController, + // maxLines: 4, + // ), + // ); + // } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final authState = context.read().state; + + List generalActions = CommentBottomSheetAction.values.where((element) => element.permissionType == PermissionType.all).toList(); + List userActions = CommentBottomSheetAction.values.where((element) => element.permissionType == PermissionType.user).toList(); + List moderatorActions = CommentBottomSheetAction.values.where((element) => element.permissionType == PermissionType.moderator).toList(); + // List adminActions = CommentBottomSheetAction.values.where((element) => element.permissionType == PermissionType.admin).toList(); + + final account = authState.getSiteResponse?.myUser?.localUserView.person; + final moderatedCommunities = authState.getSiteResponse?.myUser?.moderates ?? []; + final isModerator = moderatedCommunities.where((communityModeratorView) => communityModeratorView.community.actorId == widget.commentView.community.actorId).isNotEmpty; + // final isAdmin = authState.getSiteResponse?.admins.where((personView) => personView.person.actorId == account?.actorId).isNotEmpty ?? false; + + final isLoggedIn = authState.isLoggedIn; + final isCommentDeleted = widget.commentView.comment.deleted; // Deleted by the user + final isCommentRemoved = widget.commentView.comment.removed; // Removed by a moderator + + if (!isLoggedIn) { + userActions = userActions.where((action) => action.requiresAuthentication == false).toList(); + } else { + if (account?.actorId == widget.commentView.creator.actorId) { + userActions = userActions.where((action) => action != CommentBottomSheetAction.reportComment).toList(); + } else { + userActions = userActions + .where((action) => action != CommentBottomSheetAction.editComment && action != CommentBottomSheetAction.deleteComment && action != CommentBottomSheetAction.restoreComment) + .toList(); + } + + if (isCommentDeleted) { + userActions = userActions.where((action) => action != CommentBottomSheetAction.deleteComment).toList(); + } else { + userActions = userActions.where((action) => action != CommentBottomSheetAction.restoreComment).toList(); + } + + if (isCommentRemoved) { + moderatorActions = moderatorActions.where((action) => action != CommentBottomSheetAction.removeComment).toList(); + } else { + moderatorActions = moderatorActions.where((action) => action != CommentBottomSheetAction.restoreCommentAsModerator).toList(); + } + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...generalActions + .map( + (postPostAction) => BottomSheetAction( + leading: Icon(postPostAction.icon), + title: postPostAction.name, + onTap: () => performAction(postPostAction), + ), + ) + .toList() as List, + const ThunderDivider(sliver: false, padding: false), + ...userActions + .map( + (postPostAction) => BottomSheetAction( + leading: Icon(postPostAction.icon), + title: postPostAction.name, + onTap: () => performAction(postPostAction), + ), + ) + .toList() as List, + if (isModerator && moderatorActions.isNotEmpty) ...[ + const ThunderDivider(sliver: false, padding: false), + ...moderatorActions + .map( + (postPostAction) => BottomSheetAction( + leading: Icon(postPostAction.icon), + trailing: Padding( + padding: const EdgeInsets.only(left: 1), + child: Icon( + Thunder.shield, + size: 20, + color: Color.alphaBlend(theme.colorScheme.primary.withValues(alpha: 0.4), Colors.green), + ), + ), + title: postPostAction.name, + onTap: () => performAction(postPostAction), + ), + ) + .toList() as List, + ], + // if (isAdmin && adminActions.isNotEmpty) ...[ + // const ThunderDivider(sliver: false, padding: false), + // ...adminActions + // .map( + // (postPostAction) => BottomSheetAction( + // leading: Icon(postPostAction.icon), + // trailing: Padding( + // padding: const EdgeInsets.only(left: 1), + // child: Icon( + // Thunder.shield_crown, + // size: 20, + // color: Color.alphaBlend(theme.colorScheme.primary.withOpacity(0.4), Colors.red), + // ), + // ), + // title: postPostAction.name, + // onTap: () => performAction(postPostAction), + // ), + // ) + // .toList() as List, + // ], + ], + ); + } +} diff --git a/lib/post/enums/post_action.dart b/lib/post/enums/post_action.dart index ca9076103..dc9179e8d 100644 --- a/lib/post/enums/post_action.dart +++ b/lib/post/enums/post_action.dart @@ -1,4 +1,4 @@ -enum PermissionType { user, moderator, admin } +enum PermissionType { all, user, moderator, admin } enum PostAction { /// User level post actions diff --git a/lib/post/widgets/comment_card.dart b/lib/post/widgets/comment_card.dart index c0108eba1..7f6513eeb 100644 --- a/lib/post/widgets/comment_card.dart +++ b/lib/post/widgets/comment_card.dart @@ -375,9 +375,9 @@ class _CommentCardState extends State with SingleTickerProviderStat case CommentAction.report: widget.onReportAction(commentView.comment.id); break; - // case CommentAction.viewSource: - // setState(() => viewSource = !viewSource); - // break; + case CommentAction.viewSource: + setState(() => viewSource = !viewSource); + break; default: break; } From a9ec5d45ecce39537db4969340da3a017f65819c Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Thu, 2 Jan 2025 11:10:00 -0800 Subject: [PATCH 6/9] refactor: refactor comment report to match post report dialog --- lib/comment/view/create_comment_page.dart | 1 - .../widgets/comment_action_bottom_sheet.dart | 1 - lib/comment/widgets/comment_card.dart | 53 +++--- .../comment_comment_action_bottom_sheet.dart | 58 +++---- lib/moderator/view/report_page.dart | 4 +- lib/post/pages/post_page.dart | 1 - lib/post/pages/post_page_success.dart | 6 - lib/post/utils/comment_action_helpers.dart | 21 --- lib/post/widgets/comment_card.dart | 13 +- lib/post/widgets/comment_view.dart | 3 - lib/post/widgets/report_comment_dialog.dart | 158 ------------------ .../comment_appearance_settings_page.dart | 1 - lib/shared/comment_card_actions.dart | 44 ++++- lib/shared/comment_content.dart | 3 - lib/shared/comment_reference.dart | 1 - 15 files changed, 99 insertions(+), 269 deletions(-) delete mode 100644 lib/post/widgets/report_comment_dialog.dart diff --git a/lib/comment/view/create_comment_page.dart b/lib/comment/view/create_comment_page.dart index b35f8c6ea..ffeb0269f 100644 --- a/lib/comment/view/create_comment_page.dart +++ b/lib/comment/view/create_comment_page.dart @@ -325,7 +325,6 @@ class _CreateCommentPageState extends State { onVoteAction: (_, __) {}, onSaveAction: (_, __) {}, onReplyEditAction: (_, __) {}, - onReportAction: (_) {}, onDeleteAction: (_, __) {}, isUserLoggedIn: true, isOwnComment: false, diff --git a/lib/comment/widgets/comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_action_bottom_sheet.dart index e64656a18..8c768b3d4 100644 --- a/lib/comment/widgets/comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_action_bottom_sheet.dart @@ -132,7 +132,6 @@ class _CommentActionBottomSheetState extends State { commentView: widget.commentView, onAction: () {}, ), - _ => SizedBox(), }; return SafeArea( diff --git a/lib/comment/widgets/comment_card.dart b/lib/comment/widgets/comment_card.dart index 9602a850f..29ae3b32b 100644 --- a/lib/comment/widgets/comment_card.dart +++ b/lib/comment/widgets/comment_card.dart @@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:lemmy_api_client/v3.dart'; +import 'package:thunder/comment/enums/comment_action.dart'; import 'package:thunder/comment/utils/navigate_comment.dart'; import 'package:thunder/comment/widgets/comment_action_bottom_sheet.dart'; import 'package:thunder/core/auth/bloc/auth_bloc.dart'; @@ -57,9 +58,6 @@ class CommentCard extends StatefulWidget { /// Callback function for when a comment being replied to or edited final Function(CommentView commentView, bool isEdit)? onReplyEditAction; - /// Callback function for when a comment is reported - final Function(int commentId)? onReportAction; - const CommentCard({ super.key, required this.commentView, @@ -75,7 +73,6 @@ class CommentCard extends StatefulWidget { this.onCollapseCommentChange, this.onDeleteAction, this.onReplyEditAction, - this.onReportAction, }); @override @@ -306,20 +303,39 @@ class _CommentCardState extends State with SingleTickerProviderStat showCommentActionBottomModalSheet( context, widget.commentView, - // widget.onSaveAction ?? () {}, - // widget.onDeleteAction ?? () {}, - // widget.onVoteAction ?? () {}, - // (CommentView commentView, bool isEdit) { - // return navigateToCreateCommentPage( - // context, - // commentView: isEdit ? commentView : null, - // parentCommentView: isEdit ? null : commentView, - // onCommentSuccess: (commentView, isEdit) => widget.onReplyEditAction?.call(commentView, isEdit), - // ); - // }, - // widget.onReportAction ?? () {}, - // () => setState(() => viewSource = !viewSource), - // viewSource, + onAction: ({commentAction, required commentView, communityAction, userAction, value}) { + if (commentAction != null) { + switch (commentAction) { + case CommentAction.vote: + widget.onVoteAction?.call(commentView.comment.id, value); + break; + case CommentAction.save: + widget.onSaveAction?.call(commentView.comment.id, value); + break; + case CommentAction.reply: + widget.onReplyEditAction?.call(commentView, false); + break; + case CommentAction.edit: + widget.onReplyEditAction?.call(commentView, true); + break; + case CommentAction.delete: + widget.onDeleteAction?.call(commentView.comment.id, value); + break; + case CommentAction.report: + context.read().add(ReportCommentEvent(commentId: commentView.comment.id, message: value)); + break; + case CommentAction.viewSource: + setState(() => viewSource = !viewSource); + break; + default: + break; + } + } else if (communityAction != null) { + // @todo - implement community actions + } else if (userAction != null) { + // @todo - implement user actions + } + }, ); }, onTap: () { @@ -331,7 +347,6 @@ class _CommentCardState extends State with SingleTickerProviderStat onSaveAction: (int commentId, bool save) => widget.onSaveAction?.call(commentId, save), onVoteAction: (int commentId, int vote) => widget.onVoteAction?.call(commentId, vote), onDeleteAction: (int commentId, bool deleted) => widget.onDeleteAction?.call(commentId, deleted), - onReportAction: (int commentId) => widget.onReportAction?.call(commentId), onReplyEditAction: (CommentView commentView, bool isEdit) { return navigateToCreateCommentPage( context, diff --git a/lib/comment/widgets/comment_comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart index 12d169f06..8c230853f 100644 --- a/lib/comment/widgets/comment_comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart @@ -9,6 +9,7 @@ import 'package:thunder/modlog/utils/navigate_modlog.dart'; import 'package:thunder/post/enums/post_action.dart'; import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/shared/bottom_sheet_action.dart'; +import 'package:thunder/shared/dialogs.dart'; import 'package:thunder/shared/divider.dart'; import 'package:thunder/shared/text/selectable_text_modal.dart'; import 'package:thunder/thunder/thunder_icons.dart'; @@ -93,8 +94,7 @@ class _CommentCommentActionBottomSheetState extends State().add( - // FeedItemActionedEvent( - // postAction: PostAction.report, - // postId: widget.postViewMedia.postView.post.id, - // value: messageController.text, - // ), - // ); - // Navigator.of(dialogContext).pop(); - // }, - // secondaryButtonText: l10n.cancel, - // onSecondaryButtonPressed: (context) => Navigator.of(context).pop(), - // contentWidgetBuilder: (_) => TextFormField( - // decoration: InputDecoration( - // border: const OutlineInputBorder(), - // labelText: l10n.message(0), - // ), - // autofocus: true, - // controller: messageController, - // maxLines: 4, - // ), - // ); - // } + showThunderDialog( + context: widget.context, + title: l10n.reportComment, + primaryButtonText: l10n.report(1), + onPrimaryButtonPressed: (dialogContext, setPrimaryButtonEnabled) { + widget.onAction(CommentAction.report, widget.commentView, messageController.text); + Navigator.of(dialogContext).pop(); + }, + secondaryButtonText: l10n.cancel, + onSecondaryButtonPressed: (context) => Navigator.of(context).pop(), + contentWidgetBuilder: (_) => TextFormField( + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: l10n.message(0), + ), + autofocus: true, + controller: messageController, + maxLines: 4, + ), + ); + } // void showRemovePostReasonDialog() { // Navigator.of(context).pop(); diff --git a/lib/moderator/view/report_page.dart b/lib/moderator/view/report_page.dart index 738d60377..4304f29e9 100644 --- a/lib/moderator/view/report_page.dart +++ b/lib/moderator/view/report_page.dart @@ -316,7 +316,9 @@ class _ReportFeedViewState extends State { itemBuilder: (context, index) { CommentView commentView = CommentView( comment: state.commentReports[index].comment, - creator: state.commentReports[index].creator, + creator: state.commentReports[index].commentCreator, + creatorIsModerator: state.commentReports[index].creatorIsModerator, + creatorIsAdmin: state.commentReports[index].creatorIsAdmin, post: state.commentReports[index].post, community: state.commentReports[index].community, counts: state.commentReports[index].counts, diff --git a/lib/post/pages/post_page.dart b/lib/post/pages/post_page.dart index a01f78f86..d0b1385ff 100644 --- a/lib/post/pages/post_page.dart +++ b/lib/post/pages/post_page.dart @@ -234,7 +234,6 @@ class _PostPageState extends State { onSaveAction: (int commentId, bool saved) => context.read().add(CommentActionEvent(commentId: commentId, action: CommentAction.save, value: saved)), onDeleteAction: (int commentId, bool deleted) => context.read().add(CommentActionEvent(commentId: commentId, action: CommentAction.delete, value: deleted)), onReplyEditAction: (CommentView commentView, bool isEdit) async => context.read().add(CommentItemUpdatedEvent(commentView: commentView)), - onReportAction: (int commentId) => showReportCommentActionBottomSheet(context, commentId: commentId), onCollapseCommentChange: (int commentId, bool collapsed) { if (collapsed) { collapsedComments.add(commentId); diff --git a/lib/post/pages/post_page_success.dart b/lib/post/pages/post_page_success.dart index ce7206015..dbaf2c6bf 100644 --- a/lib/post/pages/post_page_success.dart +++ b/lib/post/pages/post_page_success.dart @@ -89,12 +89,6 @@ class _PostPageSuccessState extends State { onVoteAction: (int commentId, int voteType) => context.read().add(VoteCommentEvent(commentId: commentId, score: voteType)), onSaveAction: (int commentId, bool save) => context.read().add(SaveCommentEvent(commentId: commentId, save: save)), onDeleteAction: (int commentId, bool deleted) => context.read().add(DeleteCommentEvent(deleted: deleted, commentId: commentId)), - onReportAction: (int commentId) { - showReportCommentActionBottomSheet( - context, - commentId: commentId, - ); - }, onReplyEditAction: (CommentView commentView, bool isEdit) async => navigateToCreateCommentPage( context, commentView: isEdit ? commentView : null, diff --git a/lib/post/utils/comment_action_helpers.dart b/lib/post/utils/comment_action_helpers.dart index 1228e68a7..17feaefaa 100644 --- a/lib/post/utils/comment_action_helpers.dart +++ b/lib/post/utils/comment_action_helpers.dart @@ -615,28 +615,7 @@ // } // } -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/post/bloc/post_bloc.dart'; -import 'package:thunder/post/widgets/report_comment_dialog.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:thunder/utils/global_context.dart'; final l10n = AppLocalizations.of(GlobalContext.context)!; - -void showReportCommentActionBottomSheet( - BuildContext context, { - required int commentId, -}) { - showModalBottomSheet( - context: context, - showDragHandle: true, - isScrollControlled: true, - builder: (_) => BlocProvider.value( - value: context.read(), - child: ReportCommentDialog( - commentId: commentId, - ), - ), - ); -} diff --git a/lib/post/widgets/comment_card.dart b/lib/post/widgets/comment_card.dart index 7f6513eeb..521fe36c6 100644 --- a/lib/post/widgets/comment_card.dart +++ b/lib/post/widgets/comment_card.dart @@ -23,7 +23,6 @@ class CommentCard extends StatefulWidget { final Function(int, bool) onCollapseCommentChange; final Function(int, bool) onDeleteAction; final Function(CommentView, bool) onReplyEditAction; - final Function(int) onReportAction; final Set collapsedCommentSet; final int? selectCommentId; @@ -40,7 +39,6 @@ class CommentCard extends StatefulWidget { required this.onSaveAction, required this.onCollapseCommentChange, required this.onReplyEditAction, - required this.onReportAction, this.collapsedCommentSet = const {}, this.selectCommentId, this.selectedCommentPath, @@ -373,7 +371,7 @@ class _CommentCardState extends State with SingleTickerProviderStat widget.onDeleteAction(commentView.comment.id, value); break; case CommentAction.report: - widget.onReportAction(commentView.comment.id); + context.read().add(ReportCommentEvent(commentId: commentView.comment.id, message: value)); break; case CommentAction.viewSource: setState(() => viewSource = !viewSource); @@ -387,13 +385,6 @@ class _CommentCardState extends State with SingleTickerProviderStat // @todo - implement user actions } }, - // widget.onSaveAction, - // widget.onDeleteAction, - // widget.onVoteAction, - // widget.onReplyEditAction, - // widget.onReportAction, - // () => setState(() => viewSource = !viewSource), - // viewSource, ); }, onTap: () { @@ -406,7 +397,6 @@ class _CommentCardState extends State with SingleTickerProviderStat onSaveAction: (int commentId, bool save) => widget.onSaveAction(commentId, save), onVoteAction: (int commentId, int vote) => widget.onVoteAction(commentId, vote), onDeleteAction: (int commentId, bool deleted) => widget.onDeleteAction(commentId, deleted), - onReportAction: (int commentId) => widget.onReportAction(commentId), onReplyEditAction: (CommentView commentView, bool isEdit) => widget.onReplyEditAction(commentView, isEdit), isOwnComment: isOwnComment, isHidden: isHidden, @@ -505,7 +495,6 @@ class _CommentCardState extends State with SingleTickerProviderStat collapsed: widget.collapsedCommentSet.contains(widget.commentViewTree.replies[index].commentView!.comment.id), level: widget.level + 1, onVoteAction: widget.onVoteAction, - onReportAction: widget.onReportAction, onSaveAction: widget.onSaveAction, onCollapseCommentChange: widget.onCollapseCommentChange, onDeleteAction: widget.onDeleteAction, diff --git a/lib/post/widgets/comment_view.dart b/lib/post/widgets/comment_view.dart index 2fc65fd69..2b98f81e9 100644 --- a/lib/post/widgets/comment_view.dart +++ b/lib/post/widgets/comment_view.dart @@ -22,7 +22,6 @@ class CommentSubview extends StatefulWidget { final Function(int, int) onVoteAction; final Function(int, bool) onSaveAction; final Function(int, bool) onDeleteAction; - final Function(int) onReportAction; final Function(CommentView, bool) onReplyEditAction; final PostViewMedia? postViewMedia; @@ -47,7 +46,6 @@ class CommentSubview extends StatefulWidget { required this.onSaveAction, required this.onDeleteAction, required this.onReplyEditAction, - required this.onReportAction, this.postViewMedia, this.selectedCommentId, this.selectedCommentPath, @@ -249,7 +247,6 @@ class _CommentSubviewState extends State with SingleTickerProvid onVoteAction: (int commentId, int voteType) => widget.onVoteAction(commentId, voteType), onCollapseCommentChange: (int commentId, bool collapsed) => onCollapseCommentChange(commentId, collapsed), onDeleteAction: (int commentId, bool deleted) => widget.onDeleteAction(commentId, deleted), - onReportAction: (int commentId) => widget.onReportAction(commentId), onReplyEditAction: (CommentView commentView, bool isEdit) => widget.onReplyEditAction(commentView, isEdit), ), if (index == widget.comments.length + 1) ...[ diff --git a/lib/post/widgets/report_comment_dialog.dart b/lib/post/widgets/report_comment_dialog.dart deleted file mode 100644 index d0eedfa96..000000000 --- a/lib/post/widgets/report_comment_dialog.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'package:thunder/post/bloc/post_bloc.dart'; -import 'package:thunder/shared/snackbar.dart'; - -class ReportCommentDialog extends StatefulWidget { - const ReportCommentDialog({super.key, required this.commentId}); - - final int commentId; - @override - State createState() => _ReportCommentDialogState(); -} - -class _ReportCommentDialogState extends State { - late TextEditingController messageController; - @override - void initState() { - messageController = TextEditingController(); - super.initState(); - } - - @override - void dispose() { - messageController.dispose(); - super.dispose(); - } - - bool hasError = false; - String errorMessage = ''; - @override - Widget build(BuildContext context) { - return Material( - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 18.0, - vertical: 12, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - AutoSizeText( - AppLocalizations.of(context)!.reportComment, - ), - const SizedBox( - height: 12, - ), - TextFormField( - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: AppLocalizations.of(context)!.message(0), - ), - autofocus: true, - controller: messageController, - maxLines: 4, - ), - const SizedBox( - height: 12, - ), - if (hasError) - AutoSizeText( - errorMessage, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.error, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - AppLocalizations.of(context)!.cancel, - )), - const SizedBox( - width: 8, - ), - FilledButton( - onPressed: () { - if (messageController.text.isNotEmpty) { - context.read().add( - ReportCommentEvent( - commentId: widget.commentId, - message: messageController.text, - ), - ); - } - }, - child: BlocConsumer( - bloc: context.read(), - listener: (context, state) { - switch (state.status) { - case PostStatus.loading: - setState(() { - hasError = false; - }); - case PostStatus.refreshing: - setState(() { - hasError = false; - }); - case PostStatus.success: - showSnackbar(AppLocalizations.of(context)!.commentReported); - Navigator.of(context).pop(); - break; - case PostStatus.failure: - setState(() { - hasError = true; - errorMessage = state.errorMessage ?? AppLocalizations.of(context)!.unexpectedError; - }); - - default: - } - }, - builder: (context, state) { - switch (state.status) { - case PostStatus.loading: - return const SizedBox( - width: 15, - height: 15, - child: CircularProgressIndicator( - color: Colors.white, - ), - ); - - case PostStatus.refreshing: - return const SizedBox( - width: 15, - height: 15, - child: CircularProgressIndicator( - color: Colors.white, - )); - default: - return Text( - AppLocalizations.of(context)!.submit, - ); - } - }, - )) - ], - ), - const SizedBox( - height: kToolbarHeight, - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/settings/pages/comment_appearance_settings_page.dart b/lib/settings/pages/comment_appearance_settings_page.dart index 85e15dc32..432860f96 100644 --- a/lib/settings/pages/comment_appearance_settings_page.dart +++ b/lib/settings/pages/comment_appearance_settings_page.dart @@ -304,7 +304,6 @@ class _CommentAppearanceSettingsPageState extends State {}, onCollapseCommentChange: (int commentId, bool collapsed) => {}, onDeleteAction: (int commentId, bool deleted) => {}, - onReportAction: (int commentId) => {}, onReplyEditAction: (CommentView commentView, bool isEdit) => {}, ), ], diff --git a/lib/shared/comment_card_actions.dart b/lib/shared/comment_card_actions.dart index 9fbca6a34..7ff3dbbad 100644 --- a/lib/shared/comment_card_actions.dart +++ b/lib/shared/comment_card_actions.dart @@ -4,8 +4,10 @@ import 'package:flutter/services.dart'; import 'package:lemmy_api_client/v3.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:thunder/comment/enums/comment_action.dart'; import 'package:thunder/comment/widgets/comment_action_bottom_sheet.dart'; import 'package:thunder/core/auth/bloc/auth_bloc.dart'; +import 'package:thunder/post/bloc/post_bloc.dart'; import 'package:thunder/thunder/bloc/thunder_bloc.dart'; class CommentCardActions extends StatelessWidget { @@ -17,7 +19,6 @@ class CommentCardActions extends StatelessWidget { final Function(int, bool) onSaveAction; final Function(int, bool) onDeleteAction; final Function(CommentView, bool) onReplyEditAction; - final Function(int) onReportAction; final void Function() onViewSourceToggled; final bool viewSource; @@ -29,7 +30,6 @@ class CommentCardActions extends StatelessWidget { required this.onSaveAction, required this.onDeleteAction, required this.onReplyEditAction, - required this.onReportAction, required this.onViewSourceToggled, required this.viewSource, }); @@ -59,13 +59,39 @@ class CommentCardActions extends StatelessWidget { showCommentActionBottomModalSheet( context, commentView, - // onSaveAction, - // onDeleteAction, - // onVoteAction, - // onReplyEditAction, - // onReportAction, - // onViewSourceToggled, - // viewSource, + onAction: ({commentAction, required commentView, communityAction, userAction, value}) { + if (commentAction != null) { + switch (commentAction) { + case CommentAction.vote: + onVoteAction(commentView.comment.id, value); + break; + case CommentAction.save: + onSaveAction(commentView.comment.id, value); + break; + case CommentAction.reply: + onReplyEditAction(commentView, false); + break; + case CommentAction.edit: + onReplyEditAction(commentView, true); + break; + case CommentAction.delete: + onDeleteAction(commentView.comment.id, value); + break; + case CommentAction.report: + context.read().add(ReportCommentEvent(commentId: commentView.comment.id, message: value)); + break; + case CommentAction.viewSource: + onViewSourceToggled(); + break; + default: + break; + } + } else if (communityAction != null) { + // TODO - implement community actions + } else if (userAction != null) { + // TODO - implement user actions + } + }, ); HapticFeedback.mediumImpact(); }), diff --git a/lib/shared/comment_content.dart b/lib/shared/comment_content.dart index fcfc26fee..f5e3189df 100644 --- a/lib/shared/comment_content.dart +++ b/lib/shared/comment_content.dart @@ -26,7 +26,6 @@ class CommentContent extends StatefulWidget { final Function(int, int) onVoteAction; final Function(int, bool) onSaveAction; final Function(int, bool) onDeleteAction; - final Function(int) onReportAction; final Function(CommentView, bool) onReplyEditAction; final int? moddingCommentId; @@ -44,7 +43,6 @@ class CommentContent extends StatefulWidget { required this.onSaveAction, required this.onDeleteAction, required this.onReplyEditAction, - required this.onReportAction, required this.isOwnComment, required this.isHidden, this.moddingCommentId, @@ -157,7 +155,6 @@ class _CommentContentState extends State with SingleTickerProvid onSaveAction: widget.onSaveAction, onDeleteAction: widget.onDeleteAction, onReplyEditAction: widget.onReplyEditAction, - onReportAction: widget.onReportAction, onViewSourceToggled: widget.onViewSourceToggled, viewSource: widget.viewSource, ), diff --git a/lib/shared/comment_reference.dart b/lib/shared/comment_reference.dart index 6ba33b5bb..8a34ed9dc 100644 --- a/lib/shared/comment_reference.dart +++ b/lib/shared/comment_reference.dart @@ -305,7 +305,6 @@ class _CommentReferenceState extends State { onVoteAction: (int commentId, int voteType) => widget.onVoteAction?.call(commentId, voteType), onDeleteAction: (int commentId, bool deleted) => widget.onDeleteAction?.call(commentId, deleted), onReplyEditAction: (CommentView commentView, bool isEdit) => widget.onReplyEditAction?.call(commentView, widget.isOwnComment), - onReportAction: (int commentId) => widget.onReportAction?.call(commentId), isOwnComment: widget.isOwnComment, isHidden: false, excludeSemantics: true, From f163b06a7026a352faff8ae988e06ed905aeb9e4 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Sat, 4 Jan 2025 19:43:56 -0800 Subject: [PATCH 7/9] feat: use localization strings for actions, fix certain actions not refreshing properly --- .../widgets/comment_action_bottom_sheet.dart | 9 +++- lib/comment/widgets/comment_card.dart | 3 +- .../comment_comment_action_bottom_sheet.dart | 44 ++++++++++++------- lib/l10n/app_en.arb | 12 +++++ lib/post/widgets/comment_card.dart | 3 +- lib/shared/comment_card_actions.dart | 5 +-- lib/user/bloc/user_bloc.dart | 4 +- lib/user/enums/user_action.dart | 2 + .../widgets/user_action_bottom_sheet.dart | 4 +- 9 files changed, 61 insertions(+), 25 deletions(-) diff --git a/lib/comment/widgets/comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_action_bottom_sheet.dart index 8c768b3d4..00b4b1f94 100644 --- a/lib/comment/widgets/comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_action_bottom_sheet.dart @@ -21,6 +21,7 @@ import 'package:thunder/utils/instance.dart'; void showCommentActionBottomModalSheet( BuildContext context, CommentView commentView, { + bool isShowingSource = false, GeneralCommentAction page = GeneralCommentAction.general, void Function({CommentAction? commentAction, UserAction? userAction, CommunityAction? communityAction, required CommentView commentView, dynamic value})? onAction, }) { @@ -28,12 +29,12 @@ void showCommentActionBottomModalSheet( context: context, showDragHandle: true, isScrollControlled: true, - builder: (_) => CommentActionBottomSheet(context: context, initialPage: page, commentView: commentView, onAction: onAction), + builder: (_) => CommentActionBottomSheet(context: context, initialPage: page, commentView: commentView, onAction: onAction, isShowingSource: isShowingSource), ); } class CommentActionBottomSheet extends StatefulWidget { - const CommentActionBottomSheet({super.key, required this.context, required this.commentView, this.initialPage = GeneralCommentAction.general, required this.onAction}); + const CommentActionBottomSheet({super.key, required this.context, required this.commentView, this.initialPage = GeneralCommentAction.general, required this.onAction, this.isShowingSource = false}); /// The parent context final BuildContext context; @@ -41,6 +42,9 @@ class CommentActionBottomSheet extends StatefulWidget { /// The comment that is being acted on final CommentView commentView; + /// Whether the source of the comment is being shown + final bool isShowingSource; + /// The initial page of the bottom sheet final GeneralCommentAction initialPage; @@ -108,6 +112,7 @@ class _CommentActionBottomSheetState extends State { GeneralCommentAction.comment => CommentCommentActionBottomSheet( context: widget.context, commentView: widget.commentView, + isShowingSource: widget.isShowingSource, onAction: (CommentAction commentAction, CommentView? updatedCommentView, dynamic value) { widget.onAction?.call(commentAction: commentAction, commentView: widget.commentView, value: value); }, diff --git a/lib/comment/widgets/comment_card.dart b/lib/comment/widgets/comment_card.dart index 29ae3b32b..0a3557e48 100644 --- a/lib/comment/widgets/comment_card.dart +++ b/lib/comment/widgets/comment_card.dart @@ -303,6 +303,7 @@ class _CommentCardState extends State with SingleTickerProviderStat showCommentActionBottomModalSheet( context, widget.commentView, + isShowingSource: viewSource, onAction: ({commentAction, required commentView, communityAction, userAction, value}) { if (commentAction != null) { switch (commentAction) { @@ -333,7 +334,7 @@ class _CommentCardState extends State with SingleTickerProviderStat } else if (communityAction != null) { // @todo - implement community actions } else if (userAction != null) { - // @todo - implement user actions + setState(() {}); } }, ); diff --git a/lib/comment/widgets/comment_comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart index 8c230853f..f935cfdbc 100644 --- a/lib/comment/widgets/comment_comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart @@ -19,6 +19,7 @@ import 'package:thunder/thunder/thunder_icons.dart'; enum CommentBottomSheetAction { selectCommentText(icon: Icons.select_all_rounded, permissionType: PermissionType.all, requiresAuthentication: false), viewCommentSource(icon: Icons.code_rounded, permissionType: PermissionType.all, requiresAuthentication: false), + viewCommentMarkdown(icon: Icons.code_rounded, permissionType: PermissionType.all, requiresAuthentication: false), viewModlog(icon: Icons.history_rounded, permissionType: PermissionType.all, requiresAuthentication: true), reportComment(icon: Icons.flag_rounded, permissionType: PermissionType.user, requiresAuthentication: true), editComment(icon: Icons.edit_rounded, permissionType: PermissionType.user, requiresAuthentication: true), @@ -31,13 +32,14 @@ enum CommentBottomSheetAction { String get name => switch (this) { CommentBottomSheetAction.selectCommentText => l10n.selectText, CommentBottomSheetAction.viewCommentSource => l10n.viewCommentSource, + CommentBottomSheetAction.viewCommentMarkdown => l10n.viewOriginal, CommentBottomSheetAction.viewModlog => l10n.viewModlog, CommentBottomSheetAction.reportComment => l10n.reportComment, CommentBottomSheetAction.editComment => l10n.editComment, - CommentBottomSheetAction.deleteComment => "Delete Comment", - CommentBottomSheetAction.restoreComment => "Restore Comment", - CommentBottomSheetAction.removeComment => "Remove Comment", - CommentBottomSheetAction.restoreCommentAsModerator => "Restore Comment", + CommentBottomSheetAction.deleteComment => l10n.deleteComment, + CommentBottomSheetAction.restoreComment => l10n.restoreComment, + CommentBottomSheetAction.removeComment => l10n.removeComment, + CommentBottomSheetAction.restoreCommentAsModerator => l10n.restoreComment, }; /// The icon to use for the action @@ -57,7 +59,7 @@ enum CommentBottomSheetAction { /// Given a [commentView] and a [onAction] callback, this widget will display a list of actions that can be taken on the comment. /// The [onAction] callback will be triggered when an action is performed. class CommentCommentActionBottomSheet extends StatefulWidget { - const CommentCommentActionBottomSheet({super.key, required this.context, required this.commentView, required this.onAction}); + const CommentCommentActionBottomSheet({super.key, required this.context, required this.commentView, this.isShowingSource = false, required this.onAction}); /// The outer context final BuildContext context; @@ -65,6 +67,9 @@ class CommentCommentActionBottomSheet extends StatefulWidget { /// The comment information final CommentView commentView; + /// Whether the source of the comment is being shown + final bool isShowingSource; + /// Called when an action is selected final Function(CommentAction commentAction, CommentView? commentView, dynamic value) onAction; @@ -82,6 +87,7 @@ class _CommentCommentActionBottomSheetState extends State action != CommentBottomSheetAction.viewCommentSource).toList(); + } else { + generalActions = generalActions.where((action) => action != CommentBottomSheetAction.viewCommentMarkdown).toList(); + } + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -233,16 +245,18 @@ class _CommentCommentActionBottomSheetState extends State, - const ThunderDivider(sliver: false, padding: false), - ...userActions - .map( - (postPostAction) => BottomSheetAction( - leading: Icon(postPostAction.icon), - title: postPostAction.name, - onTap: () => performAction(postPostAction), - ), - ) - .toList() as List, + if (userActions.isNotEmpty) ...[ + const ThunderDivider(sliver: false, padding: false), + ...userActions + .map( + (postPostAction) => BottomSheetAction( + leading: Icon(postPostAction.icon), + title: postPostAction.name, + onTap: () => performAction(postPostAction), + ), + ) + .toList() as List + ], if (isModerator && moderatorActions.isNotEmpty) ...[ const ThunderDivider(sliver: false, padding: false), ...moderatorActions diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5cb2bf6c8..f4044ff84 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -661,6 +661,10 @@ "@deleteAccountDescription": { "description": "Description for confirmation action to delete account" }, + "deleteComment": "Delete Comment", + "@deleteComment": { + "description": "Action for deleting a comment" + }, "deleteLocalDatabase": "Delete Local Database", "@deleteLocalDatabase": { "description": "Label for action to delete local database" @@ -1785,6 +1789,10 @@ "@removeAsCommunityModerator": { "description": "Moderator action to remove user as community moderator" }, + "removeComment": "Remove Comment", + "@removeComment": { + "description": "Moderator action to remove a comment" + }, "removeFromFavorites": "Remove from favorites", "@removeFromFavorites": { "description": "Action to remove a community in drawer from favorites" @@ -1877,6 +1885,10 @@ }, "restore": "Restore", "@restore": {}, + "restoreComment": "Restore Comment", + "@restoreComment": { + "description": "Moderator action to restore a comment" + }, "restorePost": "Restore Post", "@restorePost": { "description": "Action to restore a post (moderator action)" diff --git a/lib/post/widgets/comment_card.dart b/lib/post/widgets/comment_card.dart index 521fe36c6..fa8ec314b 100644 --- a/lib/post/widgets/comment_card.dart +++ b/lib/post/widgets/comment_card.dart @@ -352,6 +352,7 @@ class _CommentCardState extends State with SingleTickerProviderStat showCommentActionBottomModalSheet( context, widget.commentViewTree.commentView!, + isShowingSource: viewSource, onAction: ({commentAction, required commentView, communityAction, userAction, value}) { if (commentAction != null) { switch (commentAction) { @@ -382,7 +383,7 @@ class _CommentCardState extends State with SingleTickerProviderStat } else if (communityAction != null) { // @todo - implement community actions } else if (userAction != null) { - // @todo - implement user actions + setState(() {}); } }, ); diff --git a/lib/shared/comment_card_actions.dart b/lib/shared/comment_card_actions.dart index 7ff3dbbad..24c7b1b3c 100644 --- a/lib/shared/comment_card_actions.dart +++ b/lib/shared/comment_card_actions.dart @@ -59,6 +59,7 @@ class CommentCardActions extends StatelessWidget { showCommentActionBottomModalSheet( context, commentView, + isShowingSource: viewSource, onAction: ({commentAction, required commentView, communityAction, userAction, value}) { if (commentAction != null) { switch (commentAction) { @@ -88,9 +89,7 @@ class CommentCardActions extends StatelessWidget { } } else if (communityAction != null) { // TODO - implement community actions - } else if (userAction != null) { - // TODO - implement user actions - } + } else if (userAction != null) {} }, ); HapticFeedback.mediumImpact(); diff --git a/lib/user/bloc/user_bloc.dart b/lib/user/bloc/user_bloc.dart index 8775b2f95..b38fc963a 100644 --- a/lib/user/bloc/user_bloc.dart +++ b/lib/user/bloc/user_bloc.dart @@ -61,7 +61,7 @@ class UserBloc extends Bloc { blockPersonResponse.blocked ? l10n.successfullyBlockedUser(blockPersonResponse.personView.person.name) : l10n.successfullyUnblockedUser(blockPersonResponse.personView.person.name), )); } catch (e) { - return emit(state.copyWith(status: UserStatus.failure)); + return emit(state.copyWith(status: UserStatus.failure, message: e.toString())); } break; case UserAction.banFromCommunity: @@ -110,6 +110,8 @@ class UserBloc extends Bloc { return emit(state.copyWith(status: UserStatus.failure)); } break; + default: + break; } } } diff --git a/lib/user/enums/user_action.dart b/lib/user/enums/user_action.dart index 84e06fa95..435686782 100644 --- a/lib/user/enums/user_action.dart +++ b/lib/user/enums/user_action.dart @@ -1,6 +1,8 @@ import 'package:thunder/post/enums/post_action.dart'; enum UserAction { + setUserLabel(permissionType: PermissionType.all), + /// User level user actions block(permissionType: PermissionType.user), diff --git a/lib/user/widgets/user_action_bottom_sheet.dart b/lib/user/widgets/user_action_bottom_sheet.dart index 5bfeefa2e..8a05e5446 100644 --- a/lib/user/widgets/user_action_bottom_sheet.dart +++ b/lib/user/widgets/user_action_bottom_sheet.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lemmy_api_client/v3.dart'; +import 'package:thunder/account/models/user_label.dart'; import 'package:thunder/core/auth/bloc/auth_bloc.dart'; import 'package:thunder/core/enums/user_type.dart'; import 'package:thunder/feed/utils/utils.dart'; @@ -19,8 +20,6 @@ import 'package:thunder/thunder/thunder_icons.dart'; import 'package:thunder/user/bloc/user_bloc.dart'; import 'package:thunder/user/enums/user_action.dart'; -import '../../account/models/user_label.dart'; - /// Defines the actions that can be taken on a user /// TODO: Implement admin-level actions enum UserBottomSheetAction { @@ -116,6 +115,7 @@ class _UserActionBottomSheetState extends State { break; case UserBottomSheetAction.addUserLabel: await showUserLabelEditorDialog(context, UserLabel.usernameFromParts(widget.user.name, widget.user.actorId)); + widget.onAction(UserAction.setUserLabel, null); Navigator.of(context).pop(); break; case UserBottomSheetAction.banUserFromCommunity: From 885c547d65e9e00a07b9b9e9d3686856b386ef91 Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Thu, 9 Jan 2025 15:30:38 -0800 Subject: [PATCH 8/9] change: remove old comment bottom sheet, added l10n to GlobalContext --- .../comment_comment_action_bottom_sheet.dart | 40 +- .../widgets/instance_action_bottom_sheet.dart | 14 +- lib/post/pages/post_page.dart | 1 - lib/post/pages/post_page_success.dart | 1 - lib/post/utils/comment_action_helpers.dart | 621 ------------------ .../post_post_action_bottom_sheet.dart | 38 +- .../share/share_action_bottom_sheet.dart | 24 +- .../widgets/user_action_bottom_sheet.dart | 24 +- lib/utils/global_context.dart | 3 + .../src/thunder_video_player.dart | 10 +- .../src/thunder_youtube_player.dart | 4 +- 11 files changed, 80 insertions(+), 700 deletions(-) delete mode 100644 lib/post/utils/comment_action_helpers.dart diff --git a/lib/comment/widgets/comment_comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart index f935cfdbc..a6fcd4951 100644 --- a/lib/comment/widgets/comment_comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart @@ -7,12 +7,12 @@ import 'package:thunder/comment/enums/comment_action.dart'; import 'package:thunder/core/auth/bloc/auth_bloc.dart'; import 'package:thunder/modlog/utils/navigate_modlog.dart'; import 'package:thunder/post/enums/post_action.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/shared/bottom_sheet_action.dart'; import 'package:thunder/shared/dialogs.dart'; import 'package:thunder/shared/divider.dart'; import 'package:thunder/shared/text/selectable_text_modal.dart'; import 'package:thunder/thunder/thunder_icons.dart'; +import 'package:thunder/utils/global_context.dart'; /// Defines the actions that can be taken on a comment /// TODO: Implement admin-level actions @@ -30,16 +30,16 @@ enum CommentBottomSheetAction { ; String get name => switch (this) { - CommentBottomSheetAction.selectCommentText => l10n.selectText, - CommentBottomSheetAction.viewCommentSource => l10n.viewCommentSource, - CommentBottomSheetAction.viewCommentMarkdown => l10n.viewOriginal, - CommentBottomSheetAction.viewModlog => l10n.viewModlog, - CommentBottomSheetAction.reportComment => l10n.reportComment, - CommentBottomSheetAction.editComment => l10n.editComment, - CommentBottomSheetAction.deleteComment => l10n.deleteComment, - CommentBottomSheetAction.restoreComment => l10n.restoreComment, - CommentBottomSheetAction.removeComment => l10n.removeComment, - CommentBottomSheetAction.restoreCommentAsModerator => l10n.restoreComment, + CommentBottomSheetAction.selectCommentText => GlobalContext.l10n.selectText, + CommentBottomSheetAction.viewCommentSource => GlobalContext.l10n.viewCommentSource, + CommentBottomSheetAction.viewCommentMarkdown => GlobalContext.l10n.viewOriginal, + CommentBottomSheetAction.viewModlog => GlobalContext.l10n.viewModlog, + CommentBottomSheetAction.reportComment => GlobalContext.l10n.reportComment, + CommentBottomSheetAction.editComment => GlobalContext.l10n.editComment, + CommentBottomSheetAction.deleteComment => GlobalContext.l10n.deleteComment, + CommentBottomSheetAction.restoreComment => GlobalContext.l10n.restoreComment, + CommentBottomSheetAction.removeComment => GlobalContext.l10n.removeComment, + CommentBottomSheetAction.restoreCommentAsModerator => GlobalContext.l10n.restoreComment, }; /// The icon to use for the action @@ -94,7 +94,7 @@ class _CommentCommentActionBottomSheetState extends State Navigator.of(context).pop(), contentWidgetBuilder: (_) => TextFormField( decoration: InputDecoration( border: const OutlineInputBorder(), - labelText: l10n.message(0), + labelText: GlobalContext.l10n.message(0), ), autofocus: true, controller: messageController, @@ -155,8 +155,8 @@ class _CommentCommentActionBottomSheetState extends State().add( // FeedItemActionedEvent( @@ -170,12 +170,12 @@ class _CommentCommentActionBottomSheetState extends State Navigator.of(context).pop(), // contentWidgetBuilder: (_) => TextFormField( // decoration: InputDecoration( // border: const OutlineInputBorder(), - // labelText: l10n.message(0), + // labelText:GlobalContext.l10n.message(0), // ), // autofocus: true, // controller: messageController, diff --git a/lib/instance/widgets/instance_action_bottom_sheet.dart b/lib/instance/widgets/instance_action_bottom_sheet.dart index fb3253696..01feef2c1 100644 --- a/lib/instance/widgets/instance_action_bottom_sheet.dart +++ b/lib/instance/widgets/instance_action_bottom_sheet.dart @@ -7,8 +7,8 @@ import 'package:thunder/instance/bloc/instance_bloc.dart'; import 'package:thunder/instance/enums/instance_action.dart'; import 'package:thunder/instance/utils/navigate_instance.dart'; import 'package:thunder/post/enums/post_action.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/shared/bottom_sheet_action.dart'; +import 'package:thunder/utils/global_context.dart'; import 'package:thunder/utils/instance.dart'; /// Defines the actions that can be taken on an instance @@ -22,12 +22,12 @@ enum InstanceBottomSheetAction { ; String get name => switch (this) { - InstanceBottomSheetAction.visitCommunityInstance => l10n.visitCommunityInstance, - InstanceBottomSheetAction.blockCommunityInstance => l10n.blockCommunityInstance, - InstanceBottomSheetAction.unblockCommunityInstance => l10n.unblockCommunityInstance, - InstanceBottomSheetAction.visitUserInstance => l10n.visitUserInstance, - InstanceBottomSheetAction.blockUserInstance => l10n.blockUserInstance, - InstanceBottomSheetAction.unblockUserInstance => l10n.unblockUserInstance, + InstanceBottomSheetAction.visitCommunityInstance => GlobalContext.l10n.visitCommunityInstance, + InstanceBottomSheetAction.blockCommunityInstance => GlobalContext.l10n.blockCommunityInstance, + InstanceBottomSheetAction.unblockCommunityInstance => GlobalContext.l10n.unblockCommunityInstance, + InstanceBottomSheetAction.visitUserInstance => GlobalContext.l10n.visitUserInstance, + InstanceBottomSheetAction.blockUserInstance => GlobalContext.l10n.blockUserInstance, + InstanceBottomSheetAction.unblockUserInstance => GlobalContext.l10n.unblockUserInstance, }; /// The icon to use for the action diff --git a/lib/post/pages/post_page.dart b/lib/post/pages/post_page.dart index d0b1385ff..1c9128850 100644 --- a/lib/post/pages/post_page.dart +++ b/lib/post/pages/post_page.dart @@ -13,7 +13,6 @@ import 'package:thunder/comment/widgets/comment_card.dart'; import 'package:thunder/core/auth/bloc/auth_bloc.dart'; import 'package:thunder/core/models/post_view_media.dart'; import 'package:thunder/post/bloc/post_bloc.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/post/widgets/post_page_app_bar.dart'; import 'package:thunder/post/widgets/post_view.dart'; import 'package:thunder/shared/comment_navigator_fab.dart'; diff --git a/lib/post/pages/post_page_success.dart b/lib/post/pages/post_page_success.dart index dbaf2c6bf..08b121ebf 100644 --- a/lib/post/pages/post_page_success.dart +++ b/lib/post/pages/post_page_success.dart @@ -11,7 +11,6 @@ import 'package:thunder/comment/utils/navigate_comment.dart'; import 'package:thunder/core/models/comment_view_tree.dart'; import 'package:thunder/core/models/post_view_media.dart'; import 'package:thunder/post/bloc/post_bloc.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/post/widgets/comment_view.dart'; class PostPageSuccess extends StatefulWidget { diff --git a/lib/post/utils/comment_action_helpers.dart b/lib/post/utils/comment_action_helpers.dart deleted file mode 100644 index 17feaefaa..000000000 --- a/lib/post/utils/comment_action_helpers.dart +++ /dev/null @@ -1,621 +0,0 @@ -// import 'dart:async'; - -// import 'package:back_button_interceptor/back_button_interceptor.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter/services.dart'; -// import 'package:flutter_bloc/flutter_bloc.dart'; -// import 'package:lemmy_api_client/v3.dart'; -// import 'package:share_plus/share_plus.dart'; -// import 'package:thunder/account/models/user_label.dart'; -// import 'package:thunder/comment/utils/comment.dart'; -// import 'package:thunder/community/widgets/post_card_metadata.dart'; -// import 'package:thunder/core/enums/full_name.dart'; -// import 'package:thunder/core/singletons/lemmy_client.dart'; -// import 'package:thunder/feed/utils/utils.dart'; -// import 'package:thunder/feed/view/feed_page.dart'; -// import 'package:thunder/instance/bloc/instance_bloc.dart'; -// import 'package:thunder/instance/enums/instance_action.dart'; -// import 'package:thunder/modlog/utils/navigate_modlog.dart'; -// import 'package:thunder/post/bloc/post_bloc.dart'; -// import 'package:thunder/post/utils/user_label_utils.dart'; -// import 'package:thunder/post/widgets/report_comment_dialog.dart'; -// import 'package:thunder/shared/multi_picker_item.dart'; -// import 'package:thunder/shared/picker_item.dart'; -// import 'package:thunder/shared/snackbar.dart'; -// import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -// import 'package:thunder/shared/text/selectable_text_modal.dart'; -// import 'package:thunder/thunder/bloc/thunder_bloc.dart'; -// import 'package:thunder/user/bloc/user_bloc.dart'; -// import 'package:thunder/user/enums/user_action.dart'; -// import 'package:thunder/utils/global_context.dart'; -// import 'package:thunder/utils/instance.dart'; -// import 'package:thunder/instance/utils/navigate_instance.dart'; - -// import '../../core/auth/bloc/auth_bloc.dart'; - -// enum CommentCardAction { -// save, -// share, -// shareLink, -// shareLinkLocal, -// delete, -// upvote, -// downvote, -// reply, -// edit, -// textActions, -// selectText, -// copyText, -// viewSource, -// viewModlog, -// report, -// userActions, -// visitProfile, -// blockUser, -// userLabel, -// instanceActions, -// visitInstance, -// blockInstance, -// } - -// class ExtendedCommentCardActions { -// const ExtendedCommentCardActions({ -// required this.commentCardAction, -// required this.icon, -// this.getTrailingIcon, -// required this.label, -// this.getColor, -// this.getForegroundColor, -// this.getOverrideIcon, -// this.getOverrideLabel, -// this.getSubtitleLabel, -// this.shouldShow, -// this.shouldEnable, -// }); - -// final CommentCardAction commentCardAction; -// final IconData icon; -// final IconData Function()? getTrailingIcon; -// final String label; -// final Color Function(BuildContext context)? getColor; -// final Color? Function(BuildContext context, CommentView commentView)? getForegroundColor; -// final IconData? Function(CommentView commentView)? getOverrideIcon; -// final String Function(BuildContext context, CommentView commentView, bool viewSource)? getOverrideLabel; -// final String Function(BuildContext context, CommentView commentView)? getSubtitleLabel; -// final bool Function(BuildContext context, CommentView commentView)? shouldShow; -// final bool Function(bool isUserLoggedIn)? shouldEnable; -// } - -// final List commentCardDefaultActionItems = [ -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.userActions, -// icon: Icons.person_rounded, -// label: l10n.user, -// getSubtitleLabel: (context, commentView) => generateUserFullName( -// context, -// commentView.creator.name, -// commentView.creator.displayName, -// fetchInstanceNameFromUrl(commentView.creator.actorId), -// ), -// getTrailingIcon: () => Icons.chevron_right_rounded, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.visitProfile, -// icon: Icons.person_search_rounded, -// label: AppLocalizations.of(GlobalContext.context)!.visitUserProfile, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.blockUser, -// icon: Icons.block, -// label: AppLocalizations.of(GlobalContext.context)!.blockUser, -// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.userLabel, -// icon: Icons.label_rounded, -// label: AppLocalizations.of(GlobalContext.context)!.addUserLabel, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.instanceActions, -// icon: Icons.language_rounded, -// label: l10n.instance(1), -// getSubtitleLabel: (context, postView) => fetchInstanceNameFromUrl(postView.creator.actorId) ?? '', -// getTrailingIcon: () => Icons.chevron_right_rounded, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.visitInstance, -// icon: Icons.language, -// label: AppLocalizations.of(GlobalContext.context)!.visitInstance, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.blockInstance, -// icon: Icons.block_rounded, -// label: AppLocalizations.of(GlobalContext.context)!.blockInstance, -// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.textActions, -// icon: Icons.comment_rounded, -// label: l10n.textActions, -// getTrailingIcon: () => Icons.chevron_right_rounded, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.selectText, -// icon: Icons.select_all_rounded, -// label: l10n.selectText, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.copyText, -// icon: Icons.copy_rounded, -// label: AppLocalizations.of(GlobalContext.context)!.copyComment, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.viewSource, -// icon: Icons.edit_document, -// label: l10n.viewCommentSource, -// getOverrideLabel: (context, commentView, viewSource) => viewSource ? l10n.viewOriginal : l10n.viewCommentSource, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.viewModlog, -// icon: Icons.shield_rounded, -// label: AppLocalizations.of(GlobalContext.context)!.viewModlog, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.report, -// icon: Icons.report_outlined, -// label: AppLocalizations.of(GlobalContext.context)!.reportComment, -// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.shareLink, -// icon: Icons.share_rounded, -// label: l10n.shareComment, -// getSubtitleLabel: (context, commentView) => commentView.comment.apId, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.shareLinkLocal, -// icon: Icons.share_rounded, -// label: l10n.shareCommentLocal, -// getSubtitleLabel: (context, commentView) => LemmyClient.instance.generateCommentUrl(commentView.comment.id), -// ), -// ]; - -// final List commentCardDefaultMultiActionItems = [ -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.upvote, -// label: AppLocalizations.of(GlobalContext.context)!.upvote, -// icon: Icons.arrow_upward_rounded, -// getColor: (context) => context.read().state.upvoteColor.color, -// getForegroundColor: (context, commentView) => commentView.myVote == 1 ? context.read().state.upvoteColor.color : null, -// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.downvote, -// label: AppLocalizations.of(GlobalContext.context)!.downvote, -// icon: Icons.arrow_downward_rounded, -// getColor: (context) => context.read().state.downvoteColor.color, -// getForegroundColor: (context, commentView) => commentView.myVote == -1 ? context.read().state.downvoteColor.color : null, -// shouldShow: (context, commentView) => context.read().state.downvotesEnabled, -// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.save, -// label: AppLocalizations.of(GlobalContext.context)!.save, -// icon: Icons.star_border_rounded, -// getColor: (context) => context.read().state.saveColor.color, -// getForegroundColor: (context, commentView) => commentView.saved ? context.read().state.saveColor.color : null, -// getOverrideIcon: (commentView) => commentView.saved ? Icons.star_rounded : null, -// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.reply, -// label: AppLocalizations.of(GlobalContext.context)!.reply(0), -// icon: Icons.reply_rounded, -// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.edit, -// label: AppLocalizations.of(GlobalContext.context)!.edit, -// icon: Icons.edit, -// shouldShow: (context, commentView) => commentView.creator.id == context.read().state.account?.userId, -// shouldEnable: (isUserLoggedIn) => isUserLoggedIn, -// ), -// ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.share, -// icon: Icons.share_rounded, -// label: l10n.share, -// ), -// ]; - -// enum CommentActionBottomSheetPage { -// general, -// user, -// instance, -// share, -// text, -// } - -// void showCommentActionBottomModalSheet( -// BuildContext context, -// CommentView commentView, -// Function onSaveAction, -// Function onDeleteAction, -// Function onVoteAction, -// Function onReplyEditAction, -// Function onReportAction, -// Function onViewSourceToggled, -// bool viewSource, -// ) { -// final bool isOwnComment = commentView.creator.id == context.read().state.account?.userId; -// bool isDeleted = commentView.comment.deleted; - -// // Generate the list of default actions for the general page -// final List defaultCommentCardActions = commentCardDefaultActionItems -// .where((extendedAction) => [ -// CommentCardAction.userActions, -// CommentCardAction.instanceActions, -// CommentCardAction.textActions, -// CommentCardAction.report, -// CommentCardAction.delete, -// ].contains(extendedAction.commentCardAction)) -// .toList(); - -// // Add the ability to delete one's own comment -// if (isOwnComment) { -// defaultCommentCardActions.add(ExtendedCommentCardActions( -// commentCardAction: CommentCardAction.delete, -// icon: isDeleted ? Icons.restore_from_trash_rounded : Icons.delete_rounded, -// label: isDeleted ? AppLocalizations.of(GlobalContext.context)!.restore : AppLocalizations.of(GlobalContext.context)!.delete, -// )); -// } - -// // Hide the ability to block instance if not supported -- todo change this to instance list -// if (defaultCommentCardActions.any((c) => c.commentCardAction == CommentCardAction.blockInstance) && !LemmyClient.instance.supportsFeature(LemmyFeature.blockInstance)) { -// defaultCommentCardActions.removeWhere((c) => c.commentCardAction == CommentCardAction.blockInstance); -// } - -// // Generate list of user actions -// final List userActions = commentCardDefaultActionItems -// .where((extendedAction) => [ -// CommentCardAction.visitProfile, -// CommentCardAction.blockUser, -// CommentCardAction.userLabel, -// ].contains(extendedAction.commentCardAction)) -// .toList(); - -// // Generate list of instance actions -// final List instanceActions = commentCardDefaultActionItems -// .where((extendedAction) => [ -// CommentCardAction.visitInstance, -// CommentCardAction.blockInstance, -// ].contains(extendedAction.commentCardAction)) -// .toList(); - -// // Generate the list of share actions -// final List shareActions = commentCardDefaultActionItems -// .where((extendedAction) => [ -// CommentCardAction.shareLink, -// if (commentView.comment.apId != LemmyClient.instance.generateCommentUrl(commentView.comment.id)) CommentCardAction.shareLinkLocal, -// ].contains(extendedAction.commentCardAction)) -// .toList(); - -// // Generate list of text actions -// final List textActions = commentCardDefaultActionItems -// .where((extendedAction) => [ -// CommentCardAction.selectText, -// CommentCardAction.copyText, -// CommentCardAction.viewSource, -// if (commentView.comment.removed && LemmyClient.instance.supportsFeature(LemmyFeature.commentModLog)) CommentCardAction.viewModlog, -// ].contains(extendedAction.commentCardAction)) -// .toList(); - -// showModalBottomSheet( -// showDragHandle: true, -// isScrollControlled: true, -// context: context, -// builder: (builderContext) => CommentActionPicker( -// outerContext: context, -// commentView: commentView, -// titles: { -// CommentActionBottomSheetPage.general: l10n.actions, -// CommentActionBottomSheetPage.user: l10n.userActions, -// CommentActionBottomSheetPage.instance: l10n.instanceActions, -// CommentActionBottomSheetPage.share: l10n.share, -// CommentActionBottomSheetPage.text: l10n.textActions, -// }, -// multiCommentCardActions: {CommentActionBottomSheetPage.general: commentCardDefaultMultiActionItems}, -// commentCardActions: { -// CommentActionBottomSheetPage.general: defaultCommentCardActions, -// CommentActionBottomSheetPage.user: userActions, -// CommentActionBottomSheetPage.instance: instanceActions, -// CommentActionBottomSheetPage.share: shareActions, -// CommentActionBottomSheetPage.text: textActions, -// }, -// onSaveAction: onSaveAction, -// onDeleteAction: onDeleteAction, -// onVoteAction: onVoteAction, -// onReplyEditAction: onReplyEditAction, -// onReportAction: onReportAction, -// onViewSourceToggled: onViewSourceToggled, -// viewSource: viewSource, -// ), -// ); -// } - -// class CommentActionPicker extends StatefulWidget { -// /// The comment -// final CommentView commentView; - -// /// This is the set of titles to show for each page -// final Map titles; - -// /// This is the list of quick actions that are shown horizontally across the top of the sheet -// final Map> multiCommentCardActions; - -// /// This is the set of full actions to display vertically in a list -// final Map> commentCardActions; - -// /// The context from whoever invoked this sheet (useful for blocs that would otherwise be missing) -// final BuildContext outerContext; - -// // Callback functions -// final Function onSaveAction; -// final Function onDeleteAction; -// final Function onVoteAction; -// final Function onReplyEditAction; -// final Function onReportAction; -// final Function onViewSourceToggled; -// final bool viewSource; - -// const CommentActionPicker({ -// super.key, -// required this.outerContext, -// required this.commentView, -// required this.titles, -// required this.multiCommentCardActions, -// required this.commentCardActions, -// required this.onSaveAction, -// required this.onDeleteAction, -// required this.onVoteAction, -// required this.onReplyEditAction, -// required this.onReportAction, -// required this.onViewSourceToggled, -// required this.viewSource, -// }); - -// @override -// State createState() => _CommentActionPickerState(); -// } - -// class _CommentActionPickerState extends State { -// /// The current page -// CommentActionBottomSheetPage page = CommentActionBottomSheetPage.general; - -// @override -// void initState() { -// super.initState(); - -// BackButtonInterceptor.add(_handleBack); -// } - -// @override -// void dispose() { -// BackButtonInterceptor.remove(_handleBack); - -// super.dispose(); -// } - -// @override -// Widget build(BuildContext context) { -// final AppLocalizations l10n = AppLocalizations.of(context)!; -// final ThemeData theme = Theme.of(context); -// final bool isUserLoggedIn = context.read().state.isLoggedIn; - -// return SingleChildScrollView( -// child: AnimatedSize( -// duration: const Duration(milliseconds: 100), -// curve: Curves.easeInOut, -// child: SingleChildScrollView( -// child: Column( -// mainAxisAlignment: MainAxisAlignment.start, -// mainAxisSize: MainAxisSize.max, -// children: [ -// Semantics( -// label: '${widget.titles[page] ?? l10n.actions}, ${page == CommentActionBottomSheetPage.general ? '' : l10n.backButton}', -// child: Padding( -// padding: const EdgeInsets.only(left: 10, right: 10), -// child: Material( -// borderRadius: BorderRadius.circular(50), -// color: Colors.transparent, -// child: InkWell( -// borderRadius: BorderRadius.circular(50), -// onTap: page == CommentActionBottomSheetPage.general ? null : () => setState(() => page = CommentActionBottomSheetPage.general), -// child: Padding( -// padding: const EdgeInsets.fromLTRB(12.0, 10, 16.0, 10.0), -// child: Align( -// alignment: Alignment.centerLeft, -// child: Row( -// children: [ -// if (page != CommentActionBottomSheetPage.general) ...[ -// const Icon(Icons.chevron_left, size: 30), -// const SizedBox(width: 12), -// ], -// Semantics( -// excludeSemantics: true, -// child: Text( -// widget.titles[page] ?? l10n.actions, -// style: theme.textTheme.titleLarge, -// ), -// ), -// ], -// ), -// ), -// ), -// ), -// ), -// ), -// ), -// // Post metadata chips -// Row( -// children: [ -// const SizedBox(width: 20), -// LanguagePostCardMetaData(languageId: widget.commentView.comment.languageId), -// ], -// ), -// if (widget.multiCommentCardActions[page]?.isNotEmpty == true) -// MultiPickerItem( -// pickerItems: [ -// ...widget.multiCommentCardActions[page]!.where((a) => a.shouldShow?.call(context, widget.commentView) ?? true).map( -// (a) { -// return PickerItemData( -// label: a.label, -// icon: a.getOverrideIcon?.call(widget.commentView) ?? a.icon, -// backgroundColor: a.getColor?.call(context), -// foregroundColor: a.getForegroundColor?.call(context, widget.commentView), -// onSelected: (a.shouldEnable?.call(isUserLoggedIn) ?? true) ? () => onSelected(a.commentCardAction) : null, -// ); -// }, -// ), -// ], -// ), -// if (widget.commentCardActions[page]?.isNotEmpty == true) -// ListView.builder( -// shrinkWrap: true, -// physics: const NeverScrollableScrollPhysics(), -// itemCount: widget.commentCardActions[page]!.length, -// itemBuilder: (BuildContext itemBuilderContext, int index) { -// return PickerItem( -// label: widget.commentCardActions[page]![index].getOverrideLabel?.call(context, widget.commentView, widget.viewSource) ?? widget.commentCardActions[page]![index].label, -// subtitle: widget.commentCardActions[page]![index].getSubtitleLabel?.call(context, widget.commentView), -// icon: widget.commentCardActions[page]![index].getOverrideIcon?.call(widget.commentView) ?? widget.commentCardActions[page]![index].icon, -// trailingIcon: widget.commentCardActions[page]![index].getTrailingIcon?.call(), -// onSelected: -// (widget.commentCardActions[page]![index].shouldEnable?.call(isUserLoggedIn) ?? true) ? () => onSelected(widget.commentCardActions[page]![index].commentCardAction) : null, -// ); -// }, -// ), -// const SizedBox(height: 16.0), -// ], -// ), -// ), -// ), -// ); -// } - -// void onSelected(CommentCardAction commentCardAction) async { -// bool pop = true; -// Function action; - -// switch (commentCardAction) { -// case CommentCardAction.save: -// action = () => widget.onSaveAction(widget.commentView.comment.id, !(widget.commentView.saved)); -// break; -// case CommentCardAction.share: -// pop = false; -// action = () => setState(() => page = CommentActionBottomSheetPage.share); -// break; -// case CommentCardAction.shareLink: -// action = () => Share.share(widget.commentView.comment.apId); -// break; -// case CommentCardAction.shareLinkLocal: -// action = () => Share.share(LemmyClient.instance.generateCommentUrl(widget.commentView.comment.id)); -// break; -// case CommentCardAction.delete: -// action = () => widget.onDeleteAction(widget.commentView.comment.id, !(widget.commentView.comment.deleted)); -// case CommentCardAction.upvote: -// action = () => widget.onVoteAction(widget.commentView.comment.id, widget.commentView.myVote == 1 ? 0 : 1); -// break; -// case CommentCardAction.downvote: -// action = () => widget.onVoteAction(widget.commentView.comment.id, widget.commentView.myVote == -1 ? 0 : -1); -// break; -// case CommentCardAction.reply: -// action = () => widget.onReplyEditAction(widget.commentView, false); -// break; -// case CommentCardAction.edit: -// action = () => widget.onReplyEditAction(widget.commentView, true); -// break; -// case CommentCardAction.textActions: -// action = () => setState(() => page = CommentActionBottomSheetPage.text); -// pop = false; -// break; -// case CommentCardAction.selectText: -// action = () => showSelectableTextModal( -// context, -// text: widget.commentView.comment.content, -// ); -// break; -// case CommentCardAction.copyText: -// action = () => Clipboard.setData(ClipboardData(text: cleanCommentContent(widget.commentView.comment))).then((_) { -// showSnackbar(AppLocalizations.of(widget.outerContext)!.copiedToClipboard); -// }); -// break; -// case CommentCardAction.viewSource: -// action = widget.onViewSourceToggled; -// break; -// case CommentCardAction.viewModlog: -// action = () async { -// await navigateToModlogPage( -// context, -// subtitle: Text(l10n.removedComment), -// modlogActionType: ModlogActionType.modRemoveComment, -// commentId: widget.commentView.comment.id, -// ); -// }; -// break; - -// case CommentCardAction.report: -// action = () => widget.onReportAction(widget.commentView.comment.id); -// break; -// case CommentCardAction.userActions: -// action = () => setState(() => page = CommentActionBottomSheetPage.user); -// pop = false; -// break; -// case CommentCardAction.visitProfile: -// action = () => navigateToFeedPage(widget.outerContext, feedType: FeedType.user, userId: widget.commentView.creator.id); -// break; -// case CommentCardAction.blockUser: -// action = () => widget.outerContext.read().add(UserActionEvent(userAction: UserAction.block, userId: widget.commentView.creator.id, value: true)); -// break; -// case CommentCardAction.userLabel: -// action = () async { -// await showUserLabelEditorDialog(context, UserLabel.usernameFromParts(widget.commentView.creator.name, widget.commentView.creator.actorId)); -// }; -// break; -// case CommentCardAction.instanceActions: -// action = () => setState(() => page = CommentActionBottomSheetPage.instance); -// pop = false; - -// case CommentCardAction.visitInstance: -// action = () => navigateToInstancePage(widget.outerContext, instanceHost: fetchInstanceNameFromUrl(widget.commentView.creator.actorId)!, instanceId: widget.commentView.community.instanceId); -// break; -// case CommentCardAction.blockInstance: -// action = () => widget.outerContext.read().add(InstanceActionEvent( -// instanceAction: InstanceAction.block, -// instanceId: widget.commentView.creator.instanceId, -// domain: fetchInstanceNameFromUrl(widget.commentView.creator.actorId), -// value: true, -// )); -// break; -// } - -// if (pop) { -// Navigator.of(context).pop(); -// } - -// action(); -// } - -// FutureOr _handleBack(bool stopDefaultButtonEvent, RouteInfo routeInfo) { -// if (page != CommentActionBottomSheetPage.general) { -// setState(() => page = CommentActionBottomSheetPage.general); -// return true; -// } - -// return false; -// } -// } - -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:thunder/utils/global_context.dart'; - -final l10n = AppLocalizations.of(GlobalContext.context)!; diff --git a/lib/post/widgets/post_post_action_bottom_sheet.dart b/lib/post/widgets/post_post_action_bottom_sheet.dart index fee5b9750..519e0b936 100644 --- a/lib/post/widgets/post_post_action_bottom_sheet.dart +++ b/lib/post/widgets/post_post_action_bottom_sheet.dart @@ -11,12 +11,12 @@ import 'package:thunder/core/models/post_view_media.dart'; import 'package:thunder/feed/bloc/feed_bloc.dart'; import 'package:thunder/post/cubit/create_post_cubit.dart'; import 'package:thunder/post/enums/post_action.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/shared/bottom_sheet_action.dart'; import 'package:thunder/shared/dialogs.dart'; import 'package:thunder/shared/divider.dart'; import 'package:thunder/thunder/bloc/thunder_bloc.dart'; import 'package:thunder/thunder/thunder_icons.dart'; +import 'package:thunder/utils/global_context.dart'; /// Defines the actions that can be taken on a user /// TODO: Implement admin-level actions @@ -36,16 +36,16 @@ enum PostPostAction { ; String get name => switch (this) { - PostPostAction.reportPost => l10n.reportPost, - PostPostAction.editPost => l10n.editPost, - PostPostAction.deletePost => l10n.deletePost, - PostPostAction.restorePost => l10n.restorePost, - PostPostAction.lockPost => l10n.lockPost, - PostPostAction.unlockPost => l10n.unlockPost, - PostPostAction.removePost => l10n.removePost, - PostPostAction.restorePostAsModerator => l10n.restorePost, - PostPostAction.pinPostToCommunity => l10n.pinPostToCommunity, - PostPostAction.unpinPostFromCommunity => l10n.unpinPostFromCommunity, + PostPostAction.reportPost => GlobalContext.l10n.reportPost, + PostPostAction.editPost => GlobalContext.l10n.editPost, + PostPostAction.deletePost => GlobalContext.l10n.deletePost, + PostPostAction.restorePost => GlobalContext.l10n.restorePost, + PostPostAction.lockPost => GlobalContext.l10n.lockPost, + PostPostAction.unlockPost => GlobalContext.l10n.unlockPost, + PostPostAction.removePost => GlobalContext.l10n.removePost, + PostPostAction.restorePostAsModerator => GlobalContext.l10n.restorePost, + PostPostAction.pinPostToCommunity => GlobalContext.l10n.pinPostToCommunity, + PostPostAction.unpinPostFromCommunity => GlobalContext.l10n.unpinPostFromCommunity, // PostPostAction.pinPostToInstance => "Pin Post To Instance", // PostPostAction.unpinPostFromInstance => "Unpin Post From Instance", }; @@ -185,8 +185,8 @@ class _PostPostActionBottomSheetState extends State { showThunderDialog( context: widget.context, - title: l10n.reportPost, - primaryButtonText: l10n.report(1), + title: GlobalContext.l10n.reportPost, + primaryButtonText: GlobalContext.l10n.report(1), onPrimaryButtonPressed: (dialogContext, setPrimaryButtonEnabled) { widget.context.read().add( FeedItemActionedEvent( @@ -197,12 +197,12 @@ class _PostPostActionBottomSheetState extends State { ); Navigator.of(dialogContext).pop(); }, - secondaryButtonText: l10n.cancel, + secondaryButtonText: GlobalContext.l10n.cancel, onSecondaryButtonPressed: (context) => Navigator.of(context).pop(), contentWidgetBuilder: (_) => TextFormField( decoration: InputDecoration( border: const OutlineInputBorder(), - labelText: l10n.message(0), + labelText: GlobalContext.l10n.message(0), ), autofocus: true, controller: messageController, @@ -217,8 +217,8 @@ class _PostPostActionBottomSheetState extends State { showThunderDialog( context: widget.context, - title: widget.postViewMedia.postView.post.removed ? l10n.restorePost : l10n.removalReason, - primaryButtonText: widget.postViewMedia.postView.post.removed ? l10n.restore : l10n.remove, + title: widget.postViewMedia.postView.post.removed ? GlobalContext.l10n.restorePost : GlobalContext.l10n.removalReason, + primaryButtonText: widget.postViewMedia.postView.post.removed ? GlobalContext.l10n.restore : GlobalContext.l10n.remove, onPrimaryButtonPressed: (dialogContext, setPrimaryButtonEnabled) { widget.context.read().add( FeedItemActionedEvent( @@ -232,12 +232,12 @@ class _PostPostActionBottomSheetState extends State { ); Navigator.of(dialogContext).pop(); }, - secondaryButtonText: l10n.cancel, + secondaryButtonText: GlobalContext.l10n.cancel, onSecondaryButtonPressed: (context) => Navigator.of(context).pop(), contentWidgetBuilder: (_) => TextFormField( decoration: InputDecoration( border: const OutlineInputBorder(), - labelText: l10n.message(0), + labelText: GlobalContext.l10n.message(0), ), autofocus: true, controller: messageController, diff --git a/lib/shared/share/share_action_bottom_sheet.dart b/lib/shared/share/share_action_bottom_sheet.dart index 6e61819d3..77bc3ba1c 100644 --- a/lib/shared/share/share_action_bottom_sheet.dart +++ b/lib/shared/share/share_action_bottom_sheet.dart @@ -10,10 +10,10 @@ import 'package:thunder/core/enums/media_type.dart'; import 'package:thunder/core/models/post_view_media.dart'; import 'package:thunder/core/singletons/lemmy_client.dart'; import 'package:thunder/post/enums/post_action.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/shared/share/advanced_share_sheet.dart'; import 'package:thunder/shared/bottom_sheet_action.dart'; import 'package:thunder/shared/snackbar.dart'; +import 'package:thunder/utils/global_context.dart'; /// Defines the actions that can be taken on a post when sharing enum ShareBottomSheetAction { @@ -28,14 +28,14 @@ enum ShareBottomSheetAction { ; String get name => switch (this) { - ShareBottomSheetAction.shareComment => l10n.shareComment, - ShareBottomSheetAction.shareCommentLocal => l10n.shareCommentLocal, - ShareBottomSheetAction.sharePost => l10n.sharePost, - ShareBottomSheetAction.sharePostLocal => l10n.sharePostLocal, - ShareBottomSheetAction.shareImage => l10n.shareImage, - ShareBottomSheetAction.shareMedia => l10n.shareMediaLink, - ShareBottomSheetAction.shareLink => l10n.shareLink, - ShareBottomSheetAction.shareAdvanced => l10n.advanced, + ShareBottomSheetAction.shareComment => GlobalContext.l10n.shareComment, + ShareBottomSheetAction.shareCommentLocal => GlobalContext.l10n.shareCommentLocal, + ShareBottomSheetAction.sharePost => GlobalContext.l10n.sharePost, + ShareBottomSheetAction.sharePostLocal => GlobalContext.l10n.sharePostLocal, + ShareBottomSheetAction.shareImage => GlobalContext.l10n.shareImage, + ShareBottomSheetAction.shareMedia => GlobalContext.l10n.shareMediaLink, + ShareBottomSheetAction.shareLink => GlobalContext.l10n.shareLink, + ShareBottomSheetAction.shareAdvanced => GlobalContext.l10n.advanced, }; /// The icon to use for the action @@ -82,13 +82,13 @@ class _ShareActionBottomSheetState extends State { File? mediaFile = media?.file; if (media == null) { - showSnackbar(l10n.downloadingMedia); + showSnackbar(GlobalContext.l10n.downloadingMedia); mediaFile = await DefaultCacheManager().getSingleFile(url); } await Share.shareXFiles([XFile(mediaFile!.path)]); } catch (e) { - showSnackbar(l10n.errorDownloadingMedia(e)); + showSnackbar(GlobalContext.l10n.errorDownloadingMedia(e)); } } @@ -144,7 +144,7 @@ class _ShareActionBottomSheetState extends State { case ShareBottomSheetAction.shareLink: return postViewMedia!.media.first.originalUrl; case ShareBottomSheetAction.shareAdvanced: - return l10n.useAdvancedShareSheet; + return GlobalContext.l10n.useAdvancedShareSheet; } } diff --git a/lib/user/widgets/user_action_bottom_sheet.dart b/lib/user/widgets/user_action_bottom_sheet.dart index 8a05e5446..33c9fbdb0 100644 --- a/lib/user/widgets/user_action_bottom_sheet.dart +++ b/lib/user/widgets/user_action_bottom_sheet.dart @@ -9,7 +9,6 @@ import 'package:thunder/core/enums/user_type.dart'; import 'package:thunder/feed/utils/utils.dart'; import 'package:thunder/feed/view/feed_page.dart'; import 'package:thunder/post/enums/post_action.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/post/utils/user_label_utils.dart'; import 'package:thunder/shared/avatars/user_avatar.dart'; import 'package:thunder/shared/bottom_sheet_action.dart'; @@ -19,6 +18,7 @@ import 'package:thunder/shared/divider.dart'; import 'package:thunder/thunder/thunder_icons.dart'; import 'package:thunder/user/bloc/user_bloc.dart'; import 'package:thunder/user/enums/user_action.dart'; +import 'package:thunder/utils/global_context.dart'; /// Defines the actions that can be taken on a user /// TODO: Implement admin-level actions @@ -39,14 +39,14 @@ enum UserBottomSheetAction { ; String get name => switch (this) { - UserBottomSheetAction.viewProfile => l10n.visitUserProfile, - UserBottomSheetAction.blockUser => l10n.blockUser, - UserBottomSheetAction.unblockUser => l10n.unblockUser, - UserBottomSheetAction.addUserLabel => l10n.addUserLabel, - UserBottomSheetAction.banUserFromCommunity => l10n.banFromCommunity, - UserBottomSheetAction.unbanUserFromCommunity => l10n.unbanFromCommunity, - UserBottomSheetAction.addUserAsCommunityModerator => l10n.addAsCommunityModerator, - UserBottomSheetAction.removeUserAsCommunityModerator => l10n.removeAsCommunityModerator, + UserBottomSheetAction.viewProfile => GlobalContext.l10n.visitUserProfile, + UserBottomSheetAction.blockUser => GlobalContext.l10n.blockUser, + UserBottomSheetAction.unblockUser => GlobalContext.l10n.unblockUser, + UserBottomSheetAction.addUserLabel => GlobalContext.l10n.addUserLabel, + UserBottomSheetAction.banUserFromCommunity => GlobalContext.l10n.banFromCommunity, + UserBottomSheetAction.unbanUserFromCommunity => GlobalContext.l10n.unbanFromCommunity, + UserBottomSheetAction.addUserAsCommunityModerator => GlobalContext.l10n.addAsCommunityModerator, + UserBottomSheetAction.removeUserAsCommunityModerator => GlobalContext.l10n.removeAsCommunityModerator, // UserPostAction.banUser => "Ban From Instance", // UserPostAction.unbanUser => "Unban User From Instance", // UserPostAction.purgeUser => "Purge User", @@ -164,7 +164,7 @@ class _UserActionBottomSheetState extends State { showThunderDialog( context: widget.context, - title: l10n.banFromCommunity, + title: GlobalContext.l10n.banFromCommunity, primaryButtonText: "Ban", onPrimaryButtonPressed: (dialogContext, setPrimaryButtonEnabled) { widget.context.read().add( @@ -182,7 +182,7 @@ class _UserActionBottomSheetState extends State { setState(() => _userAction = UserAction.banFromCommunity); Navigator.of(dialogContext).pop(); }, - secondaryButtonText: l10n.cancel, + secondaryButtonText: GlobalContext.l10n.cancel, onSecondaryButtonPressed: (context) => Navigator.of(context).pop(), contentWidgetBuilder: (_) => StatefulBuilder( builder: (context, setState) { @@ -200,7 +200,7 @@ class _UserActionBottomSheetState extends State { TextFormField( decoration: InputDecoration( border: const OutlineInputBorder(), - labelText: l10n.message(0), + labelText: GlobalContext.l10n.message(0), ), autofocus: true, controller: messageController, diff --git a/lib/utils/global_context.dart b/lib/utils/global_context.dart index 71f429aef..01b79ce8d 100644 --- a/lib/utils/global_context.dart +++ b/lib/utils/global_context.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + class GlobalContext { static GlobalKey scaffoldMessengerKey = GlobalKey(); static BuildContext get context => scaffoldMessengerKey.currentContext!; + static AppLocalizations get l10n => AppLocalizations.of(context)!; } diff --git a/lib/utils/video_player/src/thunder_video_player.dart b/lib/utils/video_player/src/thunder_video_player.dart index c2f0251c9..acf636fd8 100644 --- a/lib/utils/video_player/src/thunder_video_player.dart +++ b/lib/utils/video_player/src/thunder_video_player.dart @@ -4,13 +4,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:thunder/core/enums/video_playback_speed.dart'; -import 'package:thunder/shared/thunder_popup_menu_item.dart'; import 'package:video_player/video_player.dart'; +import 'package:thunder/core/enums/video_playback_speed.dart'; +import 'package:thunder/shared/thunder_popup_menu_item.dart'; +import 'package:thunder/utils/global_context.dart'; import 'package:thunder/core/enums/internet_connection_type.dart'; import 'package:thunder/core/enums/video_auto_play.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/shared/snackbar.dart'; import 'package:thunder/thunder/bloc/thunder_bloc.dart'; import 'package:thunder/thunder/cubits/network_checker_cubit/network_checker_cubit.dart'; @@ -100,7 +100,7 @@ class _ThunderVideoPlayerState extends State { if (_videoPlayerController.value.hasError) { showSnackbar( - l10n.failedToLoadVideo, + GlobalContext.l10n.failedToLoadVideo, trailingIcon: Icons.chevron_right_rounded, trailingAction: () { handleLink(context, url: widget.videoUrl, forceOpenInBrowser: true); @@ -157,7 +157,7 @@ class _ThunderVideoPlayerState extends State { onPressed: () => handleLink(context, url: widget.videoUrl, forceOpenInBrowser: true), icon: Icon( Icons.open_in_browser_rounded, - semanticLabel: l10n.openInBrowser, + semanticLabel: GlobalContext.l10n.openInBrowser, color: Colors.white.withOpacity(0.90), ), ), diff --git a/lib/utils/video_player/src/thunder_youtube_player.dart b/lib/utils/video_player/src/thunder_youtube_player.dart index 6aaad1ad0..7080b5fd4 100644 --- a/lib/utils/video_player/src/thunder_youtube_player.dart +++ b/lib/utils/video_player/src/thunder_youtube_player.dart @@ -7,9 +7,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:youtube_player_flutter/youtube_player_flutter.dart' as ypf; import 'package:youtube_player_iframe/youtube_player_iframe.dart'; +import 'package:thunder/utils/global_context.dart'; import 'package:thunder/core/enums/internet_connection_type.dart'; import 'package:thunder/core/enums/video_auto_play.dart'; -import 'package:thunder/post/utils/comment_action_helpers.dart'; import 'package:thunder/thunder/thunder.dart'; import 'package:thunder/utils/links.dart'; @@ -121,7 +121,7 @@ class _ThunderYoutubePlayerState extends State with Single onPressed: () => handleLink(context, url: widget.videoUrl, forceOpenInBrowser: true), icon: Icon( Icons.open_in_browser_rounded, - semanticLabel: l10n.openInBrowser, + semanticLabel: GlobalContext.l10n.openInBrowser, color: Colors.white.withOpacity(0.90), ), ), From 07722ef68f85879641b970ddab0204d822e292be Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Wed, 15 Jan 2025 17:26:21 -0800 Subject: [PATCH 9/9] change: only show view modlog when comment has been removed --- lib/comment/widgets/comment_comment_action_bottom_sheet.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/comment/widgets/comment_comment_action_bottom_sheet.dart b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart index a6fcd4951..19d1584d1 100644 --- a/lib/comment/widgets/comment_comment_action_bottom_sheet.dart +++ b/lib/comment/widgets/comment_comment_action_bottom_sheet.dart @@ -223,6 +223,7 @@ class _CommentCommentActionBottomSheetState extends State action != CommentBottomSheetAction.removeComment).toList(); } else { + generalActions = generalActions.where((action) => action != CommentBottomSheetAction.viewModlog).toList(); moderatorActions = moderatorActions.where((action) => action != CommentBottomSheetAction.restoreCommentAsModerator).toList(); } }