diff --git a/lib/model/unreads.dart b/lib/model/unreads.dart index f0f8c5ec09..cb3476145f 100644 --- a/lib/model/unreads.dart +++ b/lib/model/unreads.dart @@ -217,6 +217,28 @@ class Unreads extends ChangeNotifier { } } + Set get channelsWithUnreadMentions { + final channels = {}; + for (var messageId in mentions) { + final streamId = _reverseStreamsLookup[messageId]?.streamId; + if (streamId != null) { + channels.add(streamId); + } + } + return channels; + } + + Set get channelsWithUnmutedMentions { + final channels = {}; + for (var messageId in mentions) { + final info = _reverseStreamsLookup[messageId]!; + if (channelStore.isTopicVisible(info.streamId, info.topic)) { + channels.add(info.streamId); + } + } + return channels; + } + void handleMessageEvent(MessageEvent event) { final message = event.message; if (message.flags.contains(MessageFlag.read)) { diff --git a/lib/widgets/subscription_list.dart b/lib/widgets/subscription_list.dart index 86a9caf4e4..b8ffaf6c86 100644 --- a/lib/widgets/subscription_list.dart +++ b/lib/widgets/subscription_list.dart @@ -185,6 +185,8 @@ class _SubscriptionList extends StatelessWidget { @override Widget build(BuildContext context) { + final channelsWithMentions = unreadsModel!.channelsWithUnreadMentions; + final channelsWithUnmutedMentions = unreadsModel!.channelsWithUnmutedMentions; return SliverList.builder( itemCount: subscriptions.length, itemBuilder: (BuildContext context, int index) { @@ -192,9 +194,15 @@ class _SubscriptionList extends StatelessWidget { final unreadCount = unreadsModel!.countInChannel(subscription.streamId); final showMutedUnreadBadge = unreadCount == 0 && unreadsModel!.countInChannelNarrow(subscription.streamId) > 0; + final hasMentions = channelsWithMentions.contains(subscription.streamId); + final hasOnlyMutedMentions = !subscription.isMuted + && channelsWithMentions.contains(subscription.streamId) + && !channelsWithUnmutedMentions.contains(subscription.streamId); return SubscriptionItem(subscription: subscription, unreadCount: unreadCount, - showMutedUnreadBadge: showMutedUnreadBadge); + showMutedUnreadBadge: showMutedUnreadBadge, + hasMentions: hasMentions, + hasOnlyMutedMentions: hasOnlyMutedMentions); }); } } @@ -206,11 +214,15 @@ class SubscriptionItem extends StatelessWidget { required this.subscription, required this.unreadCount, required this.showMutedUnreadBadge, + required this.hasMentions, + required this.hasOnlyMutedMentions, }); final Subscription subscription; final int unreadCount; final bool showMutedUnreadBadge; + final bool hasMentions; + final bool hasOnlyMutedMentions; @override Widget build(BuildContext context) { @@ -258,7 +270,7 @@ class SubscriptionItem extends StatelessWidget { subscription.name)))), if (hasUnreads) ...[ const SizedBox(width: 12), - // TODO(#747) show @-mention indicator when it applies + AtMentionMarker(muted: !subscription.isMuted && hasOnlyMutedMentions), Opacity( opacity: opacity, child: UnreadCountBadge( @@ -267,7 +279,7 @@ class SubscriptionItem extends StatelessWidget { bold: true)), ] else if (showMutedUnreadBadge) ...[ const SizedBox(width: 12), - // TODO(#747) show @-mention indicator when it applies + if (hasMentions) const AtMentionMarker(muted: true), const MutedUnreadBadge(), ], const SizedBox(width: 16), diff --git a/test/model/unreads_test.dart b/test/model/unreads_test.dart index 1f66b2ad25..64b4e626a0 100644 --- a/test/model/unreads_test.dart +++ b/test/model/unreads_test.dart @@ -298,6 +298,48 @@ void main() { }); } }); + + test('channelsWithUnreadMentions', () { + final stream1 = eg.stream(); + final stream2 = eg.stream(); + + prepare(); + fillWithMessages([ + eg.streamMessage(stream: stream1, flags: [MessageFlag.mentioned]), + eg.streamMessage(stream: stream1, flags: []), + eg.streamMessage(stream: stream2, flags: []), + ]); + + check(model.channelsWithUnreadMentions.single).equals(stream1.streamId); + }); + + test('channelsWithUnmutedMentions', () async { + final stream1 = eg.stream(); + final stream2 = eg.stream(); + final stream3 = eg.stream(); + final streams = [stream1, stream2, stream3]; + + prepare(); + + await channelStore.addStreams(streams); + await channelStore.addSubscriptions([ + eg.subscription(stream1), + eg.subscription(stream2), + eg.subscription(stream3, isMuted: true)]); + + await channelStore.addUserTopic(stream1, 'a normal', UserTopicVisibilityPolicy.none); + await channelStore.addUserTopic(stream2, 'b muted', UserTopicVisibilityPolicy.muted); + await channelStore.addUserTopic(stream3, 'c normal', UserTopicVisibilityPolicy.none); + + + fillWithMessages([ + eg.streamMessage(stream: stream1, flags: [MessageFlag.mentioned], topic: 'a normal'), + eg.streamMessage(stream: stream2, flags: [MessageFlag.mentioned], topic: 'b muted'), + eg.streamMessage(stream: stream3, flags: [MessageFlag.mentioned], topic: 'c normal'), + ]); + + check(model.channelsWithUnmutedMentions.single).equals(stream1.streamId); + }); }); group('DM messages', () { diff --git a/test/widgets/subscription_list_test.dart b/test/widgets/subscription_list_test.dart index b1c7b89137..25b4362b25 100644 --- a/test/widgets/subscription_list_test.dart +++ b/test/widgets/subscription_list_test.dart @@ -315,4 +315,58 @@ void main() { checkStreamNameWght(mutedStreamWithUnmutedUnreads.name, 400); checkStreamNameWght(mutedStreamWithNoUnmutedUnreads.name, 400); }); + + group('@-mention marker', () { + Iterable getAtMentionMarkers(WidgetTester tester) { + return tester.widgetList(find.byType(AtMentionMarker)); + } + + testWidgets('is shown when subscription has unread mentions', (tester) async { + final streamWithMentions = eg.stream(); + final streamWithNoMentions = eg.stream(); + + await setupStreamListPage(tester, + subscriptions: [ + eg.subscription(streamWithMentions), + eg.subscription(streamWithNoMentions), + ], + userTopics: [ + eg.userTopicItem(streamWithMentions, 'a', UserTopicVisibilityPolicy.none), + eg.userTopicItem(streamWithNoMentions, 'b', UserTopicVisibilityPolicy.muted), + ], + unreadMsgs: eg.unreadMsgs( + mentions: [1], + channels: [ + UnreadChannelSnapshot(streamId: streamWithMentions.streamId, topic: 'a', unreadMessageIds: [1]), + UnreadChannelSnapshot(streamId: streamWithNoMentions.streamId, topic: 'b', unreadMessageIds: [2]), + ]), + ); + + check(getAtMentionMarkers(tester)).single; + }); + + testWidgets('is muted when subscription has only muted mentions', (tester) async { + final streamWithMentions = eg.stream(); + final streamWithOnlyMutedMentions = eg.stream(); + + await setupStreamListPage(tester, + subscriptions: [ + eg.subscription(streamWithMentions), + eg.subscription(streamWithOnlyMutedMentions, isMuted: true), + ], + userTopics: [ + eg.userTopicItem(streamWithMentions, 'a', UserTopicVisibilityPolicy.none), + eg.userTopicItem(streamWithOnlyMutedMentions, 'b', UserTopicVisibilityPolicy.none), + ], + unreadMsgs: eg.unreadMsgs( + mentions: [1, 2], + channels: [ + UnreadChannelSnapshot(streamId: streamWithMentions.streamId, topic: 'a', unreadMessageIds: [1]), + UnreadChannelSnapshot(streamId: streamWithOnlyMutedMentions.streamId, topic: 'b', unreadMessageIds: [2]), + ]), + ); + + check(getAtMentionMarkers(tester).map((e) => e.muted)).deepEquals([false, true]); + }); + }); }