diff --git a/lib/inbox/bloc/inbox_bloc.dart b/lib/inbox/bloc/inbox_bloc.dart index f5cd9f364..a1a2cb7ba 100644 --- a/lib/inbox/bloc/inbox_bloc.dart +++ b/lib/inbox/bloc/inbox_bloc.dart @@ -81,7 +81,7 @@ class InboxBloc extends Bloc { auth: account!.jwt!, unreadOnly: !event.showAll, limit: limit, - sort: CommentSortType.new_, + sort: event.commentSortType, page: 1, ), ); @@ -91,7 +91,7 @@ class InboxBloc extends Bloc { GetPersonMentions( auth: account!.jwt!, unreadOnly: !event.showAll, - sort: CommentSortType.new_, + sort: event.commentSortType, limit: limit, page: 1, ), @@ -113,7 +113,7 @@ class InboxBloc extends Bloc { auth: account!.jwt!, unreadOnly: !event.showAll, limit: limit, - sort: CommentSortType.new_, + sort: event.commentSortType, page: 1, ), ); @@ -121,7 +121,7 @@ class InboxBloc extends Bloc { GetPersonMentions( auth: account.jwt!, unreadOnly: !event.showAll, - sort: CommentSortType.new_, + sort: event.commentSortType, limit: limit, page: 1, ), @@ -176,7 +176,7 @@ class InboxBloc extends Bloc { auth: account!.jwt!, unreadOnly: state.showUnreadOnly, limit: limit, - sort: CommentSortType.new_, + sort: event.commentSortType, page: state.inboxReplyPage, ), ); @@ -188,7 +188,7 @@ class InboxBloc extends Bloc { GetPersonMentions( auth: account!.jwt!, unreadOnly: state.showUnreadOnly, - sort: CommentSortType.new_, + sort: event.commentSortType, limit: limit, page: state.inboxMentionPage, ), @@ -443,6 +443,23 @@ class InboxBloc extends Bloc { if (account?.jwt == null) return emit(state.copyWith(status: InboxStatus.success)); await lemmy.run(MarkAllAsRead(auth: account!.jwt!)); + + // Update all the replies, mentions, and messages to be read locally + List updatedReplies = state.replies.map((commentReplyView) => commentReplyView.copyWith(commentReply: commentReplyView.commentReply.copyWith(read: true))).toList(); + List updatedMentions = state.mentions.map((personMentionView) => personMentionView.copyWith(personMention: personMentionView.personMention.copyWith(read: true))).toList(); + List updatedPrivateMessages = + state.privateMessages.map((privateMessageView) => privateMessageView.copyWith(privateMessage: privateMessageView.privateMessage.copyWith(read: true))).toList(); + + return emit(state.copyWith( + status: InboxStatus.success, + replies: updatedReplies, + mentions: updatedMentions, + privateMessages: updatedPrivateMessages, + totalUnreadCount: 0, + repliesUnreadCount: 0, + mentionsUnreadCount: 0, + messagesUnreadCount: 0, + )); } catch (e) { emit(state.copyWith(status: InboxStatus.failure, errorMessage: e.toString())); } diff --git a/lib/inbox/bloc/inbox_event.dart b/lib/inbox/bloc/inbox_event.dart index 6225e572c..936e9be4d 100644 --- a/lib/inbox/bloc/inbox_event.dart +++ b/lib/inbox/bloc/inbox_event.dart @@ -17,7 +17,10 @@ class GetInboxEvent extends InboxEvent { /// If true, it will reset the inbox and re-fetch everything depending on [inboxType] final bool reset; - const GetInboxEvent({this.inboxType, this.showAll = false, this.reset = false}); + /// The comment sort type to use for replies/mentions + final CommentSortType commentSortType; + + const GetInboxEvent({this.inboxType, this.showAll = false, this.reset = false, this.commentSortType = CommentSortType.new_}); } class InboxItemActionEvent extends InboxEvent { diff --git a/lib/inbox/pages/inbox_page.dart b/lib/inbox/pages/inbox_page.dart index 08a767930..aacc93e00 100644 --- a/lib/inbox/pages/inbox_page.dart +++ b/lib/inbox/pages/inbox_page.dart @@ -1,16 +1,21 @@ 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/core/auth/bloc/auth_bloc.dart'; +import 'package:thunder/core/singletons/lemmy_client.dart'; import 'package:thunder/inbox/bloc/inbox_bloc.dart'; import 'package:thunder/inbox/enums/inbox_type.dart'; import 'package:thunder/inbox/widgets/inbox_mentions_view.dart'; import 'package:thunder/inbox/widgets/inbox_private_messages_view.dart'; import 'package:thunder/inbox/widgets/inbox_replies_view.dart'; +import 'package:thunder/shared/comment_sort_picker.dart'; import 'package:thunder/shared/dialogs.dart'; import 'package:thunder/shared/snackbar.dart'; +import 'package:thunder/shared/thunder_popup_menu_item.dart'; /// A widget that displays the user's inbox replies, mentions, and private messages. class InboxPage extends StatefulWidget { @@ -30,6 +35,9 @@ class _InboxPageState extends State with SingleTickerProviderStateMix /// Whether to show all inbox mentions, replies, and private messages or not bool showAll = false; + /// The current inbox sort type. This only applies to replies and mentions, since messages does not have a sort type + CommentSortType commentSortType = CommentSortType.new_; + /// The current account id. If this changes, and the current view is active, reload the view int? accountId; @@ -64,6 +72,25 @@ class _InboxPageState extends State with SingleTickerProviderStateMix super.dispose(); } + /// Displays the sort options bottom sheet for comments, since replies and mentions are technically comments + void showSortBottomSheet() { + final AppLocalizations l10n = AppLocalizations.of(context)!; + + showModalBottomSheet( + showDragHandle: true, + context: context, + builder: (builderContext) => CommentSortPicker( + title: l10n.sortOptions, + onSelect: (selected) async { + setState(() => commentSortType = selected.payload); + context.read().add(GetInboxEvent(inboxType: inboxType, reset: true, showAll: showAll, commentSortType: selected.payload)); + }, + previouslySelected: commentSortType, + minimumVersion: LemmyClient.instance.version, + ), + ); + } + @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; @@ -104,38 +131,44 @@ class _InboxPageState extends State with SingleTickerProviderStateMix forceElevated: innerBoxIsScrolled, title: Text(l10n.inbox), actions: [ - IconButton( - icon: Icon(Icons.checklist, semanticLabel: l10n.readAll), - onPressed: () async { - await showThunderDialog( - context: context, - title: l10n.confirmMarkAllAsReadTitle, - contentText: l10n.confirmMarkAllAsReadBody, - onSecondaryButtonPressed: (dialogContext) => Navigator.of(dialogContext).pop(), - secondaryButtonText: l10n.cancel, - onPrimaryButtonPressed: (dialogContext, _) { - Navigator.of(dialogContext).pop(); - context.read().add(MarkAllAsReadEvent()); - }, - primaryButtonText: l10n.markAllAsRead, - ); - }, - ), IconButton( icon: Icon(Icons.refresh_rounded, semanticLabel: l10n.refresh), onPressed: () => context.read().add(GetInboxEvent(inboxType: inboxType, reset: true, showAll: showAll)), ), - FilterChip( - shape: const StadiumBorder(), - visualDensity: VisualDensity.compact, - label: Text(l10n.showAll), - selected: showAll, - onSelected: (bool selected) { - setState(() => showAll = !showAll); - context.read().add(GetInboxEvent(inboxType: inboxType, reset: true, showAll: selected)); - }, + IconButton(onPressed: () => showSortBottomSheet(), icon: Icon(Icons.sort, semanticLabel: l10n.sortBy)), + PopupMenuButton( + onOpened: () => HapticFeedback.mediumImpact(), + itemBuilder: (context) => [ + ThunderPopupMenuItem( + onTap: () async { + HapticFeedback.mediumImpact(); + await showThunderDialog( + context: context, + title: l10n.confirmMarkAllAsReadTitle, + contentText: l10n.confirmMarkAllAsReadBody, + onSecondaryButtonPressed: (dialogContext) => Navigator.of(dialogContext).pop(), + secondaryButtonText: l10n.cancel, + onPrimaryButtonPressed: (dialogContext, _) { + Navigator.of(dialogContext).pop(); + context.read().add(MarkAllAsReadEvent()); + }, + primaryButtonText: l10n.markAllAsRead, + ); + }, + icon: Icons.checklist, + title: l10n.markAllAsRead, + ), + ThunderPopupMenuItem( + onTap: () async { + HapticFeedback.mediumImpact(); + context.read().add(GetInboxEvent(inboxType: inboxType, reset: true, showAll: !showAll)); + setState(() => showAll = !showAll); + }, + icon: showAll ? Icons.mark_as_unread : Icons.all_inbox_rounded, + title: showAll ? l10n.showUnreadOnly : l10n.showAll, + ), + ], ), - const SizedBox(width: 16.0), ], bottom: TabBar( controller: tabController, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1bb8a274d..dc93290e7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -429,7 +429,7 @@ "@confirmMarkAllAsReadBody": { "description": "The body of the confirm mark all as read dialog" }, - "confirmMarkAllAsReadTitle": "Mark All As Read?", + "confirmMarkAllAsReadTitle": "Mark all as read?", "@confirmMarkAllAsReadTitle": { "description": "The title of the confirm mark all as read dialog" }, @@ -1177,7 +1177,7 @@ }, "manageAccounts": "Manage Accounts", "@manageAccounts": {}, - "markAllAsRead": "Mark All As Read", + "markAllAsRead": "Mark all as read", "@markAllAsRead": { "description": "The mark all as read action" }, @@ -1983,7 +1983,7 @@ "@shareUserLinkLocal": { "description": "Menu item for sharing a local user link" }, - "showAll": "Show All", + "showAll": "Show all", "@showAll": {}, "showBotAccounts": "Show Bot Accounts", "@showBotAccounts": { @@ -2087,6 +2087,10 @@ "@showThumbnailPreviewOnRight": { "description": "Toggle to show thumbnails on the right side." }, + "showUnreadOnly": "Show unread only", + "@showUnreadOnly": { + "description": "Show unread replies/mentions/messages only" + }, "showUpdateChangelogs": "Show Update Changelogs", "@showUpdateChangelogs": { "description": "Setting for showing changelogs after updates"