Skip to content

subscription_list: Show a dot for unreads if channel is muted #714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions lib/widgets/subscription_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,11 @@ class _SubscriptionList extends StatelessWidget {
itemBuilder: (BuildContext context, int index) {
final subscription = subscriptions[index];
final unreadCount = unreadsModel!.countInChannel(subscription.streamId);
// TODO(#712): if stream muted, show a dot for unreads
return SubscriptionItem(subscription: subscription, unreadCount: unreadCount);
final showMutedUnreadBadge = unreadCount == 0
&& unreadsModel!.countInChannelNarrow(subscription.streamId) > 0;
return SubscriptionItem(subscription: subscription,
unreadCount: unreadCount,
showMutedUnreadBadge: showMutedUnreadBadge);
});
}
}
Expand All @@ -201,10 +204,12 @@ class SubscriptionItem extends StatelessWidget {
super.key,
required this.subscription,
required this.unreadCount,
required this.showMutedUnreadBadge,
});

final Subscription subscription;
final int unreadCount;
final bool showMutedUnreadBadge;

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -246,11 +251,11 @@ class SubscriptionItem extends StatelessWidget {
// TODO(design) check if this is the right variable
color: designVariables.labelMenuButton,
).merge(weightVariableTextStyle(context,
wght: hasUnreads ? 600 : null)),
wght: hasUnreads && !subscription.isMuted ? 600 : null)),
maxLines: 1,
overflow: TextOverflow.ellipsis,
subscription.name)))),
if (unreadCount > 0) ...[
if (hasUnreads) ...[
const SizedBox(width: 12),
// TODO(#747) show @-mention indicator when it applies
Opacity(
Expand All @@ -259,6 +264,10 @@ class SubscriptionItem extends StatelessWidget {
count: unreadCount,
backgroundColor: swatch,
bold: true)),
] else if (showMutedUnreadBadge) ...[
const SizedBox(width: 12),
// TODO(#747) show @-mention indicator when it applies
const MutedUnreadBadge(),
],
const SizedBox(width: 16),
])));
Expand Down
15 changes: 15 additions & 0 deletions lib/widgets/unread_count_badge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,18 @@ class UnreadCountBadge extends StatelessWidget {
count.toString())));
}
}

class MutedUnreadBadge extends StatelessWidget {
const MutedUnreadBadge({super.key});

@override
Widget build(BuildContext context) {
return Container(
width: 8,
height: 8,
margin: const EdgeInsetsDirectional.only(end: 3),
decoration: BoxDecoration(
color: const HSLColor.fromAHSL(0.5, 0, 0, 0.8).toColor(),
shape: BoxShape.circle));
}
}
82 changes: 82 additions & 0 deletions test/widgets/subscription_list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:zulip/api/model/model.dart';
import 'package:zulip/widgets/icons.dart';
import 'package:zulip/widgets/channel_colors.dart';
import 'package:zulip/widgets/subscription_list.dart';
import 'package:zulip/widgets/text.dart';
import 'package:zulip/widgets/unread_count_badge.dart';

import '../flutter_checks.dart';
Expand Down Expand Up @@ -153,6 +154,7 @@ void main() {
eg.subscription(stream),
], unreadMsgs: unreadMsgs);
check(find.byType(UnreadCountBadge).evaluate()).length.equals(1);
check(find.byType(MutedUnreadBadge).evaluate().length).equals(0);
});

testWidgets('unread badge counts unmuted only', (tester) async {
Expand All @@ -173,6 +175,7 @@ void main() {
check(tester.widget<Text>(find.descendant(
of: find.byType(UnreadCountBadge), matching: find.byType(Text))))
.data.equals('1');
check(find.byType(MutedUnreadBadge).evaluate().length).equals(0);
});

testWidgets('unread badge does not show with no unreads', (tester) async {
Expand All @@ -182,6 +185,46 @@ void main() {
eg.subscription(stream),
], unreadMsgs: unreadMsgs);
check(find.byType(UnreadCountBadge).evaluate()).length.equals(0);
check(find.byType(MutedUnreadBadge).evaluate().length).equals(0);
});

testWidgets('muted unread badge shows when unreads are visible in channel but not inbox', (tester) async {
final stream = eg.stream();
final unreadMsgs = eg.unreadMsgs(channels: [
UnreadChannelSnapshot(streamId: stream.streamId, topic: 'b', unreadMessageIds: [3]),
]);
await setupStreamListPage(tester,
subscriptions: [eg.subscription(stream, isMuted: true)],
userTopics: [eg.userTopicItem(stream, 'b', UserTopicVisibilityPolicy.none)],
unreadMsgs: unreadMsgs);

check(find.byType(MutedUnreadBadge).evaluate().length).equals(1);
});

testWidgets('muted unread badge does not show when unreads are visible in both channel & inbox', (tester) async {
final stream = eg.stream();
final unreadMsgs = eg.unreadMsgs(channels: [
UnreadChannelSnapshot(streamId: stream.streamId, topic: 'b', unreadMessageIds: [3]),
]);
await setupStreamListPage(tester,
subscriptions: [eg.subscription(stream, isMuted: false)],
userTopics: [eg.userTopicItem(stream, 'b', UserTopicVisibilityPolicy.none)],
unreadMsgs: unreadMsgs);

check(find.byType(MutedUnreadBadge).evaluate().length).equals(0);
});

testWidgets('muted unread badge does not show when unreads are not visible in channel nor inbox', (tester) async {
final stream = eg.stream();
final unreadMsgs = eg.unreadMsgs(channels: [
UnreadChannelSnapshot(streamId: stream.streamId, topic: 'b', unreadMessageIds: [3]),
]);
await setupStreamListPage(tester,
subscriptions: [eg.subscription(stream, isMuted: true)],
userTopics: [eg.userTopicItem(stream, 'b', UserTopicVisibilityPolicy.muted)],
unreadMsgs: unreadMsgs);

check(find.byType(MutedUnreadBadge).evaluate().length).equals(0);
});

testWidgets('color propagates to icon and badge', (tester) async {
Expand Down Expand Up @@ -233,4 +276,43 @@ void main() {
checkOpacityForStreamAndBadge('Stream 1', 2, 0.55);
checkOpacityForStreamAndBadge('Stream 2', 1, 1.0);
});

testWidgets('stream name of unmuted streams with unmuted unreads is bold', (tester) async {
void checkStreamNameWght(String streamName, double? expectedWght) {
final streamFinder = find.text(streamName);
final wght = wghtFromTextStyle(tester.widget<Text>(streamFinder).style!);
check(wght).equals(expectedWght);
}

final unmutedStreamWithUnmutedUnreads = eg.stream(name: 'Unmuted stream with unmuted unreads');
final unmutedStreamWithNoUnmutedUnreads = eg.stream(name: 'Unmuted stream with no unmuted unreads');
final mutedStreamWithUnmutedUnreads = eg.stream(name: 'Muted stream with unmuted unreads');
final mutedStreamWithNoUnmutedUnreads = eg.stream(name: 'Muted stream with no unmuted unreads');

await setupStreamListPage(tester,
subscriptions: [
eg.subscription(unmutedStreamWithUnmutedUnreads, isMuted: false),
eg.subscription(unmutedStreamWithNoUnmutedUnreads, isMuted: false),
eg.subscription(mutedStreamWithUnmutedUnreads, isMuted: true),
eg.subscription(mutedStreamWithNoUnmutedUnreads, isMuted: true),
],
userTopics: [
eg.userTopicItem(unmutedStreamWithUnmutedUnreads, 'a', UserTopicVisibilityPolicy.unmuted),
eg.userTopicItem(unmutedStreamWithNoUnmutedUnreads, 'b', UserTopicVisibilityPolicy.muted),
eg.userTopicItem(mutedStreamWithUnmutedUnreads, 'c', UserTopicVisibilityPolicy.unmuted),
eg.userTopicItem(mutedStreamWithNoUnmutedUnreads, 'd', UserTopicVisibilityPolicy.muted),
],
unreadMsgs: eg.unreadMsgs(channels: [
UnreadChannelSnapshot(streamId: unmutedStreamWithUnmutedUnreads.streamId, topic: 'a', unreadMessageIds: [1]),
UnreadChannelSnapshot(streamId: unmutedStreamWithNoUnmutedUnreads.streamId, topic: 'b', unreadMessageIds: [2]),
UnreadChannelSnapshot(streamId: mutedStreamWithUnmutedUnreads.streamId, topic: 'c', unreadMessageIds: [3]),
UnreadChannelSnapshot(streamId: mutedStreamWithNoUnmutedUnreads.streamId, topic: 'd', unreadMessageIds: [4]),
]),
);

checkStreamNameWght(unmutedStreamWithUnmutedUnreads.name, 600);
checkStreamNameWght(unmutedStreamWithNoUnmutedUnreads.name, 400);
checkStreamNameWght(mutedStreamWithUnmutedUnreads.name, 400);
checkStreamNameWght(mutedStreamWithNoUnmutedUnreads.name, 400);
});
}