Skip to content

Commit a19aa62

Browse files
committed
subscription_list: Show a dot for unreads if channel is muted
Fixes: #712
1 parent b3456df commit a19aa62

File tree

5 files changed

+108
-5
lines changed

5 files changed

+108
-5
lines changed

lib/model/unreads.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,15 @@ class Unreads extends ChangeNotifier {
206206
}
207207
}
208208

209+
/// Checks if stream contains strictly muted unreads,
210+
/// using [ChannelStore.isTopicVisible].
211+
bool hasMutedInStream(int streamId) {
212+
final stream = streams[streamId];
213+
if (stream == null) return false;
214+
return stream.entries.any((entry) =>
215+
!channelStore.isTopicVisible(streamId, entry.key) && entry.value.isNotEmpty);
216+
}
217+
209218
void handleMessageEvent(MessageEvent event) {
210219
final message = event.message;
211220
if (message.flags.contains(MessageFlag.read)) {

lib/widgets/subscription_list.dart

Lines changed: 16 additions & 5 deletions
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+
final hasAllUnreadsMuted = !hasUnmutedUnreads && unreadsModel!.hasMutedInStream(subscription.streamId);
193+
return SubscriptionItem(subscription: subscription,
194+
unreadCount: unreadCount,
195+
hasUnmutedUnreads: hasUnmutedUnreads,
196+
hasAllUnreadsMuted: hasAllUnreadsMuted);
193197
});
194198
}
195199
}
@@ -200,15 +204,18 @@ class SubscriptionItem extends StatelessWidget {
200204
super.key,
201205
required this.subscription,
202206
required this.unreadCount,
207+
required this.hasAllUnreadsMuted,
208+
required this.hasUnmutedUnreads,
203209
});
204210

205211
final Subscription subscription;
206212
final int unreadCount;
213+
final bool hasAllUnreadsMuted;
214+
final bool hasUnmutedUnreads;
207215

208216
@override
209217
Widget build(BuildContext context) {
210218
final swatch = colorSwatchFor(context, subscription);
211-
final hasUnreads = (unreadCount > 0);
212219
final opacity = subscription.isMuted ? 0.55 : 1.0;
213220
return Material(
214221
// TODO(#95) need dark-theme color
@@ -243,11 +250,11 @@ class SubscriptionItem extends StatelessWidget {
243250
// TODO(#95) need dark-theme color
244251
color: Color(0xFF262626),
245252
).merge(weightVariableTextStyle(context,
246-
wght: hasUnreads && !subscription.isMuted ? 600 : null)),
253+
wght: hasUnmutedUnreads && !subscription.isMuted ? 600 : null)),
247254
maxLines: 1,
248255
overflow: TextOverflow.ellipsis,
249256
subscription.name)))),
250-
if (unreadCount > 0) ...[
257+
if (hasUnmutedUnreads) ...[
251258
const SizedBox(width: 12),
252259
// TODO(#747) show @-mention indicator when it applies
253260
Opacity(
@@ -256,6 +263,10 @@ class SubscriptionItem extends StatelessWidget {
256263
count: unreadCount,
257264
backgroundColor: swatch,
258265
bold: true)),
266+
] else if (hasAllUnreadsMuted && subscription.isMuted) ...[
267+
const SizedBox(width: 12),
268+
// TODO(#747) show @-mention indicator when it applies
269+
const MutedUnreadBadge(),
259270
],
260271
const SizedBox(width: 16),
261272
])));

lib/widgets/unread_count_badge.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,18 @@ class UnreadCountBadge extends StatelessWidget {
6565
count.toString())));
6666
}
6767
}
68+
69+
class MutedUnreadBadge extends StatelessWidget {
70+
const MutedUnreadBadge({super.key});
71+
72+
@override
73+
Widget build(BuildContext context) {
74+
return Container(
75+
width: 8,
76+
height: 8,
77+
margin: const EdgeInsetsDirectional.only(end: 3),
78+
decoration: BoxDecoration(
79+
color: const HSLColor.fromAHSL(0.5, 0, 0, 0.8).toColor(),
80+
shape: BoxShape.circle));
81+
}
82+
}

test/model/unreads_test.dart

Lines changed: 25 additions & 0 deletions
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 channelStore.addStream(stream);
225+
await channelStore.addSubscription(eg.subscription(stream));
226+
await channelStore.addUserTopic(stream, 'a', UserTopicVisibilityPolicy.unmuted);
227+
await channelStore.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 channelStore.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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,49 @@ void main() {
187187
check(find.byType(UnreadCountBadge).evaluate()).length.equals(0);
188188
});
189189

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

0 commit comments

Comments
 (0)