Skip to content

Commit ad232ad

Browse files
committed
recent-dms: Exclude DM conversations with muted users
Conversations are excluded where all other recipients are muted.
1 parent 10a83e9 commit ad232ad

File tree

7 files changed

+85
-4
lines changed

7 files changed

+85
-4
lines changed

lib/model/recent_dm_conversations.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import '../api/model/model.dart';
88
import '../api/model/events.dart';
99
import 'narrow.dart';
1010
import 'store.dart';
11+
import 'user.dart';
1112

1213
/// A view-model for the recent-DM-conversations UI.
1314
///
@@ -17,6 +18,7 @@ class RecentDmConversationsView extends PerAccountStoreBase with ChangeNotifier
1718
factory RecentDmConversationsView({
1819
required CorePerAccountStore core,
1920
required List<RecentDmConversation> initial,
21+
required UserStore userStore,
2022
}) {
2123
final entries = initial.map((conversation) => MapEntry(
2224
DmNarrow.ofRecentDmConversation(conversation, selfUserId: core.selfUserId),
@@ -35,6 +37,7 @@ class RecentDmConversationsView extends PerAccountStoreBase with ChangeNotifier
3537

3638
return RecentDmConversationsView._(
3739
core: core,
40+
userStore: userStore,
3841
map: Map.fromEntries(entries),
3942
sorted: QueueList.from(entries.map((e) => e.key)),
4043
latestMessagesByRecipient: latestMessagesByRecipient,
@@ -43,11 +46,14 @@ class RecentDmConversationsView extends PerAccountStoreBase with ChangeNotifier
4346

4447
RecentDmConversationsView._({
4548
required super.core,
49+
required this.userStore,
4650
required this.map,
4751
required this.sorted,
4852
required this.latestMessagesByRecipient,
4953
});
5054

55+
final UserStore userStore;
56+
5157
/// The latest message ID in each conversation.
5258
final Map<DmNarrow, int> map;
5359

@@ -63,6 +69,11 @@ class RecentDmConversationsView extends PerAccountStoreBase with ChangeNotifier
6369
/// it might have been sent by anyone in its conversation.)
6470
final Map<int, int> latestMessagesByRecipient;
6571

72+
/// Same as [sorted] but excluding conversations where all the recipients are
73+
/// muted.
74+
QueueList<DmNarrow> get sortedFiltered => QueueList.from(
75+
sorted.whereNot((narrow) => userStore.ignoreConversation(narrow)));
76+
6677
/// Insert the key at the proper place in [sorted].
6778
///
6879
/// Optimized, taking O(1) time, for the case where that place is the start,

lib/model/store.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor
471471
accountId: accountId,
472472
selfUserId: account.userId,
473473
);
474+
final users = UserStoreImpl(core: core, initialSnapshot: initialSnapshot);
474475
final channels = ChannelStoreImpl(initialSnapshot: initialSnapshot);
475476
return PerAccountStore._(
476477
core: core,
@@ -496,7 +497,7 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor
496497
typingStartedWaitPeriod: Duration(
497498
milliseconds: initialSnapshot.serverTypingStartedWaitPeriodMilliseconds),
498499
),
499-
users: UserStoreImpl(core: core, initialSnapshot: initialSnapshot),
500+
users: users,
500501
typingStatus: TypingStatus(core: core,
501502
typingStartedExpiryPeriod: Duration(milliseconds: initialSnapshot.serverTypingStartedExpiryPeriodMilliseconds),
502503
),
@@ -508,8 +509,11 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor
508509
core: core,
509510
channelStore: channels,
510511
),
511-
recentDmConversationsView: RecentDmConversationsView(core: core,
512-
initial: initialSnapshot.recentPrivateConversations),
512+
recentDmConversationsView: RecentDmConversationsView(
513+
core: core,
514+
initial: initialSnapshot.recentPrivateConversations,
515+
userStore: users,
516+
),
513517
recentSenders: RecentSenders(),
514518
);
515519
}

lib/model/user.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import '../api/model/events.dart';
44
import '../api/model/initial_snapshot.dart';
55
import '../api/model/model.dart';
66
import 'localizations.dart';
7+
import 'narrow.dart';
78
import 'store.dart';
89

910
/// The portion of [PerAccountStore] describing the users in the realm.
@@ -78,6 +79,18 @@ mixin UserStore on PerAccountStoreBase {
7879
/// By default, looks for the user id in [UserStore.mutedUsers] unless
7980
/// [mutedUsers] is non-null, in which case looks in the latter.
8081
bool isUserMuted(int id, {Set<int>? mutedUsers});
82+
83+
/// Ignores conversation where all of the users corresponding to
84+
/// [DmNarrow.otherRecipientIds] are muted by [selfUser].
85+
///
86+
/// Returns false for self-1:1 conversation.
87+
///
88+
/// By default, looks for the recipients in [UserStore.mutedUsers] unless
89+
/// [mutedUsers] is non-null, in which case looks in the latter.
90+
bool ignoreConversation(DmNarrow narrow, {Set<int>? mutedUsers}) {
91+
if (narrow.otherRecipientIds.isEmpty) return false;
92+
return !narrow.otherRecipientIds.any((id) => !isUserMuted(id, mutedUsers: mutedUsers));
93+
}
8194
}
8295

8396
/// The implementation of [UserStore] that does the work.

lib/widgets/recent_dm_conversations.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class _RecentDmConversationsPageBodyState extends State<RecentDmConversationsPag
4949

5050
@override
5151
Widget build(BuildContext context) {
52-
final sorted = model!.sorted;
52+
final sorted = model!.sortedFiltered;
5353
return SafeArea(
5454
// Don't pad the bottom here; we want the list content to do that.
5555
bottom: false,

test/model/recent_dm_conversations_checks.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:zulip/model/recent_dm_conversations.dart';
66
extension RecentDmConversationsViewChecks on Subject<RecentDmConversationsView> {
77
Subject<Map<DmNarrow, int>> get map => has((v) => v.map, 'map');
88
Subject<QueueList<DmNarrow>> get sorted => has((v) => v.sorted, 'sorted');
9+
Subject<QueueList<DmNarrow>> get sortedFiltered => has((v) => v.sortedFiltered, 'sortedFiltered');
910
Subject<Map<int, int>> get latestMessagesByRecipient => has(
1011
(v) => v.latestMessagesByRecipient, 'latestMessagesByRecipient');
1112
}

test/model/recent_dm_conversations_test.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:checks/checks.dart';
22
import 'package:test/scaffolding.dart';
33
import 'package:zulip/api/model/initial_snapshot.dart';
4+
import 'package:zulip/api/model/model.dart';
45
import 'package:zulip/model/narrow.dart';
56
import 'package:zulip/model/recent_dm_conversations.dart';
67

@@ -24,6 +25,7 @@ void main() {
2425
))).recentDmConversationsView
2526
..map.isEmpty()
2627
..sorted.isEmpty()
28+
..sortedFiltered.isEmpty()
2729
..latestMessagesByRecipient.isEmpty();
2830

2931
check(eg.store(initialSnapshot: eg.initialSnapshot(
@@ -38,7 +40,45 @@ void main() {
3840
key([1]): 100,
3941
})
4042
..sorted.deepEquals([key([1, 2]), key([]), key([1])])
43+
..sortedFiltered.deepEquals([key([1, 2]), key([]), key([1])])
4144
..latestMessagesByRecipient.deepEquals({1: 300, 2: 300});
45+
46+
check(eg.store(initialSnapshot: eg.initialSnapshot(
47+
recentPrivateConversations: [
48+
RecentDmConversation(userIds: [], maxMessageId: 200),
49+
RecentDmConversation(userIds: [1], maxMessageId: 100),
50+
RecentDmConversation(userIds: [2], maxMessageId: 400),
51+
RecentDmConversation(userIds: [2, 1], maxMessageId: 300), // userIds out of order
52+
RecentDmConversation(userIds: [1, 3], maxMessageId: 500),
53+
RecentDmConversation(userIds: [2, 4], maxMessageId: 600),
54+
],
55+
mutedUsers: [
56+
MutedUserItem(id: 1),
57+
MutedUserItem(id: 3),
58+
]))).recentDmConversationsView
59+
..map.deepEquals({
60+
key([2, 4]): 600,
61+
key([1, 3]): 500,
62+
key([2]): 400,
63+
key([1, 2]): 300,
64+
key([]): 200,
65+
key([1]): 100,
66+
})
67+
..sorted.deepEquals([
68+
key([2, 4]),
69+
key([1, 3]),
70+
key([2]),
71+
key([1, 2]),
72+
key([]),
73+
key([1]),
74+
])
75+
..sortedFiltered.deepEquals([
76+
key([2, 4]),
77+
key([2]),
78+
key([1, 2]),
79+
key([]),
80+
])
81+
..latestMessagesByRecipient.deepEquals({1: 500, 2: 600, 3: 500, 4: 600});
4282
});
4383

4484
group('message event (new message)', () {

test/widgets/recent_dm_conversations_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,12 @@ void main() {
211211
group('1:1', () {
212212
group('has right title/avatar', () {
213213
for (final isUserMuted in [false, true]) {
214+
if (isUserMuted) {
215+
// Right now, we don't show DM conversations where all other
216+
// recipients are muted.
217+
continue;
218+
}
219+
214220
testWidgets(isUserMuted ? 'muted user' : 'normal user', (tester) async {
215221
final user = eg.user(userId: 1);
216222
final message = eg.dmMessage(from: eg.selfUser, to: [user]);
@@ -275,6 +281,12 @@ void main() {
275281

276282
group('has right title/avatar', () {
277283
for (final areUsersMuted in [false, true]) {
284+
if (areUsersMuted) {
285+
// Right now, we don't show DM conversations where all other
286+
// recipients are muted.
287+
continue;
288+
}
289+
278290
testWidgets(areUsersMuted ? 'muted users' : 'normal users', (tester) async {
279291
final users = usersList(2);
280292
final user0 = users[0];

0 commit comments

Comments
 (0)