Skip to content

Commit 81dbfbf

Browse files
committed
subscription_list: Show a dot for unreads if channel is muted
Fixes: zulip#712
1 parent 24d9bc1 commit 81dbfbf

File tree

5 files changed

+115
-3
lines changed

5 files changed

+115
-3
lines changed

lib/model/unreads.dart

+13
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,19 @@ class Unreads extends ChangeNotifier {
206206
}
207207
}
208208

209+
/// Checks if stream contains strictly muted unreads,
210+
/// using [StreamStore.isTopicVisible].
211+
bool hasMutedInStream(int streamId) {
212+
final topics = streams[streamId];
213+
if (topics == null) return false;
214+
for (final entry in topics.entries) {
215+
if (!streamStore.isTopicVisible(streamId, entry.key)) {
216+
if (entry.value.isNotEmpty) return true;
217+
}
218+
}
219+
return false;
220+
}
221+
209222
void handleMessageEvent(MessageEvent event) {
210223
final message = event.message;
211224
if (message.flags.contains(MessageFlag.read)) {

lib/widgets/subscription_list.dart

+13-3
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,12 @@ class _SubscriptionList extends StatelessWidget {
188188
itemBuilder: (BuildContext context, int index) {
189189
final subscription = subscriptions[index];
190190
final unreadCount = unreadsModel!.countInStream(subscription.streamId);
191-
// TODO(#712): if stream muted, show a dot for unreads
192-
return SubscriptionItem(subscription: subscription, unreadCount: unreadCount);
191+
final hasUnmutedUnreads = unreadCount > 0;
192+
// There is no need to check for muted unreads if there unmuted ones
193+
final hasMutedUnreads = !hasUnmutedUnreads && unreadsModel!.hasMutedInStream(subscription.streamId);
194+
return SubscriptionItem(subscription: subscription,
195+
unreadCount: unreadCount,
196+
hasMutedUnreads: hasMutedUnreads);
193197
});
194198
}
195199
}
@@ -200,10 +204,13 @@ class SubscriptionItem extends StatelessWidget {
200204
super.key,
201205
required this.subscription,
202206
required this.unreadCount,
203-
});
207+
required this.hasMutedUnreads,
208+
}) : hasUnmutedUnreads = unreadCount > 0;
204209

205210
final Subscription subscription;
206211
final int unreadCount;
212+
final bool hasMutedUnreads;
213+
final bool hasUnmutedUnreads;
207214

208215
@override
209216
Widget build(BuildContext context) {
@@ -256,6 +263,9 @@ class SubscriptionItem extends StatelessWidget {
256263
count: unreadCount,
257264
backgroundColor: swatch,
258265
bold: true)),
266+
] else if (hasMutedUnreads) ...[
267+
const SizedBox(width: 12),
268+
const MutedUnreadBadge(),
259269
],
260270
const SizedBox(width: 16),
261271
])));

lib/widgets/unread_count_badge.dart

+20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
import 'package:flutter/material.dart';
3+
import 'package:flutter_color_models/flutter_color_models.dart';
34

45
import 'stream_colors.dart';
56
import 'text.dart';
@@ -65,3 +66,22 @@ class UnreadCountBadge extends StatelessWidget {
6566
count.toString())));
6667
}
6768
}
69+
70+
class MutedUnreadBadge extends StatelessWidget {
71+
const MutedUnreadBadge({
72+
super.key,
73+
});
74+
75+
@override
76+
Widget build(BuildContext context) {
77+
return Opacity(
78+
opacity: 0.5,
79+
child: Container(
80+
width: 8,
81+
height: 8,
82+
margin: const EdgeInsetsDirectional.only(end: 3),
83+
decoration: const BoxDecoration(
84+
color: HslColor(0, 0, 80),
85+
shape: BoxShape.circle)));
86+
}
87+
}

test/model/unreads_test.dart

+25
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,31 @@ void main() {
217217
});
218218
});
219219

220+
group('muted helpers', () {
221+
test('hasMutedInStream', () async {
222+
final stream = eg.stream();
223+
prepare();
224+
await streamStore.addStream(stream);
225+
await streamStore.addSubscription(eg.subscription(stream));
226+
await streamStore.addUserTopic(stream, 'a', UserTopicVisibilityPolicy.unmuted);
227+
await streamStore.addUserTopic(stream, 'c', UserTopicVisibilityPolicy.unmuted);
228+
fillWithMessages([
229+
eg.streamMessage(stream: stream, topic: 'a', flags: []),
230+
eg.streamMessage(stream: stream, topic: 'a', flags: []),
231+
eg.streamMessage(stream: stream, topic: 'b', flags: []),
232+
eg.streamMessage(stream: stream, topic: 'b', flags: []),
233+
eg.streamMessage(stream: stream, topic: 'b', flags: []),
234+
eg.streamMessage(stream: stream, topic: 'c', flags: []),
235+
]);
236+
check(model.hasMutedInStream(stream.streamId)).equals(false);
237+
238+
await streamStore.handleEvent(SubscriptionUpdateEvent(id: 1,
239+
streamId: stream.streamId,
240+
property: SubscriptionProperty.isMuted, value: true));
241+
check(model.hasMutedInStream(stream.streamId)).equals(true);
242+
});
243+
});
244+
220245
group('handleMessageEvent', () {
221246
for (final (isUnread, isStream, isDirectMentioned, isWildcardMentioned) in [
222247
(true, true, true, true ),

test/widgets/subscription_list_test.dart

+44
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,50 @@ void main() {
186186
check(find.byType(UnreadCountBadge).evaluate()).length.equals(0);
187187
});
188188

189+
testWidgets('muted unread badge shows with muted unreads', (tester) async {
190+
final stream = eg.stream();
191+
final unreadMsgs = eg.unreadMsgs(streams: [
192+
UnreadStreamSnapshot(streamId: stream.streamId, topic: 'a', unreadMessageIds: [1, 2]),
193+
UnreadStreamSnapshot(streamId: stream.streamId, topic: 'b', unreadMessageIds: [3]),
194+
]);
195+
await setupStreamListPage(tester,
196+
subscriptions: [eg.subscription(stream, isMuted: true)],
197+
userTopics: [UserTopicItem(
198+
streamId: stream.streamId,
199+
topicName: 'b',
200+
lastUpdated: 1234567890,
201+
visibilityPolicy: UserTopicVisibilityPolicy.muted,
202+
)],
203+
unreadMsgs: unreadMsgs);
204+
final finder = find.byWidgetPredicate((widget) => widget is MutedUnreadBadge);
205+
check(finder.evaluate().length).equals(1);
206+
});
207+
208+
testWidgets('muted unread badge does not show with any unmuted unreads', (tester) async {
209+
final stream = eg.stream();
210+
final unreadMsgs = eg.unreadMsgs(streams: [
211+
UnreadStreamSnapshot(streamId: stream.streamId, topic: 'a', unreadMessageIds: [1, 2]),
212+
UnreadStreamSnapshot(streamId: stream.streamId, topic: 'b', unreadMessageIds: [3]),
213+
]);
214+
await setupStreamListPage(tester,
215+
subscriptions: [eg.subscription(stream, isMuted: true)],
216+
userTopics: [UserTopicItem(
217+
streamId: stream.streamId,
218+
topicName: 'b',
219+
lastUpdated: 1234567890,
220+
visibilityPolicy: UserTopicVisibilityPolicy.muted,
221+
),
222+
UserTopicItem(
223+
streamId: stream.streamId,
224+
topicName: 'a',
225+
lastUpdated: 9876543210,
226+
visibilityPolicy: UserTopicVisibilityPolicy.unmuted,
227+
)],
228+
unreadMsgs: unreadMsgs);
229+
final finder = find.byWidgetPredicate((widget) => widget is MutedUnreadBadge);
230+
check(finder.evaluate().length).equals(0);
231+
});
232+
189233
testWidgets('color propagates to icon and badge', (tester) async {
190234
final stream = eg.stream();
191235
final unreadMsgs = eg.unreadMsgs(streams: [

0 commit comments

Comments
 (0)