Skip to content

Commit 0023245

Browse files
chrisbobbesm-sayedi
andcommitted
msglist: In combined/mentions/starred, exclude DMs if all recipients muted
In the future, this should apply to the ReactionsNarrow from the Web app too, once we have it. Co-authored-by: Sayed Mahmood Sayedi <[email protected]>
1 parent 26d9537 commit 0023245

File tree

6 files changed

+350
-52
lines changed

6 files changed

+350
-52
lines changed

lib/model/message.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,12 @@ class MessageStoreImpl extends PerAccountStoreBase with MessageStore, _OutboxMes
287287
}
288288
}
289289

290+
void handleMutedUsersEvent(MutedUsersEvent event) {
291+
for (final view in _messageListViews) {
292+
view.handleMutedUsersEvent(event);
293+
}
294+
}
295+
290296
void handleMessageEvent(MessageEvent event) {
291297
// If the message is one we already know about (from a fetch),
292298
// clobber it with the one from the event system.

lib/model/message_list.dart

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'content.dart';
1313
import 'message.dart';
1414
import 'narrow.dart';
1515
import 'store.dart';
16+
import 'user.dart';
1617

1718
/// The number of messages to fetch in each request.
1819
const kMessageListFetchBatchSize = 100; // TODO tune
@@ -461,7 +462,9 @@ class MessageListView with ChangeNotifier, _MessageSequence {
461462
return switch (message.conversation) {
462463
StreamConversation(:final streamId, :final topic) =>
463464
store.isTopicVisible(streamId, topic),
464-
DmConversation() => true,
465+
DmConversation(:final allRecipientIds) =>
466+
!store.shouldMuteDmConversation(DmNarrow(
467+
allRecipientIds: allRecipientIds, selfUserId: store.selfUserId)),
465468
};
466469

467470
case ChannelNarrow(:final streamId):
@@ -472,8 +475,14 @@ class MessageListView with ChangeNotifier, _MessageSequence {
472475

473476
case TopicNarrow():
474477
case DmNarrow():
478+
return true;
479+
475480
case MentionsNarrow():
476481
case StarredMessagesNarrow():
482+
if (message.conversation case DmConversation(:final allRecipientIds)) {
483+
return !store.shouldMuteDmConversation(DmNarrow(
484+
allRecipientIds: allRecipientIds, selfUserId: store.selfUserId));
485+
}
477486
return true;
478487
}
479488
}
@@ -485,12 +494,12 @@ class MessageListView with ChangeNotifier, _MessageSequence {
485494
switch (narrow) {
486495
case CombinedFeedNarrow():
487496
case ChannelNarrow():
497+
case MentionsNarrow():
498+
case StarredMessagesNarrow():
488499
return false;
489500

490501
case TopicNarrow():
491502
case DmNarrow():
492-
case MentionsNarrow():
493-
case StarredMessagesNarrow():
494503
return true;
495504
}
496505
}
@@ -514,6 +523,22 @@ class MessageListView with ChangeNotifier, _MessageSequence {
514523
}
515524
}
516525

526+
/// Whether this event could affect the result that [_messageVisible]
527+
/// would ever have returned for any possible message in this message list.
528+
MutedUsersVisibilityEffect _mutedUsersEventCanAffectVisibility(MutedUsersEvent event) {
529+
switch(narrow) {
530+
case CombinedFeedNarrow():
531+
case MentionsNarrow():
532+
case StarredMessagesNarrow():
533+
return store.mightChangeShouldMuteDmConversation(event);
534+
535+
case ChannelNarrow():
536+
case TopicNarrow():
537+
case DmNarrow():
538+
return MutedUsersVisibilityEffect.none;
539+
}
540+
}
541+
517542
/// Fetch messages, starting from scratch.
518543
Future<void> fetchInitial() async {
519544
// TODO(#80): fetch from anchor firstUnread, instead of newest
@@ -685,6 +710,36 @@ class MessageListView with ChangeNotifier, _MessageSequence {
685710
}
686711
}
687712

713+
void handleMutedUsersEvent(MutedUsersEvent event) {
714+
final newMutedUsers = {...event.mutedUsers.map((e) => e.id)};
715+
716+
switch (_mutedUsersEventCanAffectVisibility(event)) {
717+
case MutedUsersVisibilityEffect.none:
718+
return;
719+
720+
case MutedUsersVisibilityEffect.muted:
721+
final anyRemoved = _removeMessagesWhere((message) {
722+
if (message is! DmMessage) return false;
723+
final narrow = DmNarrow.ofMessage(message, selfUserId: store.selfUserId);
724+
return store.shouldMuteDmConversation(narrow, mutedUsers: newMutedUsers);
725+
});
726+
if (anyRemoved) {
727+
notifyListeners();
728+
}
729+
730+
case MutedUsersVisibilityEffect.mixed:
731+
case MutedUsersVisibilityEffect.unmuted:
732+
// TODO get the newly-unmuted messages from the message store
733+
// For now, we simplify the task by just refetching this message list
734+
// from scratch.
735+
if (fetched) {
736+
_reset();
737+
notifyListeners();
738+
fetchInitial();
739+
}
740+
}
741+
}
742+
688743
void handleDeleteMessageEvent(DeleteMessageEvent event) {
689744
if (_removeMessagesById(event.messageIds)) {
690745
notifyListeners();

lib/model/store.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,10 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor
649649
bool isUserMuted(int userId, {Set<int>? mutedUsers}) =>
650650
_users.isUserMuted(userId, mutedUsers: mutedUsers);
651651

652+
@override
653+
MutedUsersVisibilityEffect mightChangeShouldMuteDmConversation(MutedUsersEvent event) =>
654+
_users.mightChangeShouldMuteDmConversation(event);
655+
652656
final UserStoreImpl _users;
653657

654658
final TypingStatus typingStatus;
@@ -958,6 +962,8 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor
958962

959963
case MutedUsersEvent():
960964
assert(debugLog("server event: muted_users"));
965+
_messages.handleMutedUsersEvent(event);
966+
// Update _users last, so other handlers can compare to the old value.
961967
_users.handleMutedUsersEvent(event);
962968
notifyListeners();
963969

lib/model/user.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import '../api/model/events.dart';
22
import '../api/model/initial_snapshot.dart';
33
import '../api/model/model.dart';
4+
import 'algorithms.dart';
45
import 'localizations.dart';
56
import 'narrow.dart';
67
import 'store.dart';
@@ -97,6 +98,27 @@ mixin UserStore on PerAccountStoreBase {
9798
return narrow.otherRecipientIds.every(
9899
(userId) => isUserMuted(userId, mutedUsers: mutedUsers));
99100
}
101+
102+
/// Whether the given event might change the result of [shouldMuteDmConversation]
103+
/// for its list of muted users, compared to the current state.
104+
MutedUsersVisibilityEffect mightChangeShouldMuteDmConversation(MutedUsersEvent event);
105+
}
106+
107+
/// Whether and how a given [MutedUsersEvent] may affect the results
108+
/// that [UserStore.shouldMuteDmConversation] would give for some messages.
109+
enum MutedUsersVisibilityEffect {
110+
/// The event will have no effect on the visibility results.
111+
none,
112+
113+
/// The event may change some visibility results from true to false.
114+
muted,
115+
116+
/// The event may change some visibility results from false to true.
117+
unmuted,
118+
119+
/// The event may change some visibility results from false to true,
120+
/// and some from true to false.
121+
mixed;
100122
}
101123

102124
/// The implementation of [UserStore] that does the work.
@@ -130,6 +152,29 @@ class UserStoreImpl extends PerAccountStoreBase with UserStore {
130152
return (mutedUsers ?? _mutedUsers).contains(userId);
131153
}
132154

155+
@override
156+
MutedUsersVisibilityEffect mightChangeShouldMuteDmConversation(MutedUsersEvent event) {
157+
final sortedOld = _mutedUsers.toList()..sort();
158+
final sortedNew = event.mutedUsers.map((u) => u.id).toList()..sort();
159+
assert(isSortedWithoutDuplicates(sortedOld));
160+
assert(isSortedWithoutDuplicates(sortedNew));
161+
final union = setUnion(sortedOld, sortedNew);
162+
163+
final willMuteSome = sortedOld.length < union.length;
164+
final willUnmuteSome = sortedNew.length < union.length;
165+
166+
switch ((willUnmuteSome, willMuteSome)) {
167+
case (true, false):
168+
return MutedUsersVisibilityEffect.unmuted;
169+
case (false, true):
170+
return MutedUsersVisibilityEffect.muted;
171+
case (true, true):
172+
return MutedUsersVisibilityEffect.mixed;
173+
case (false, false): // TODO(log)?
174+
return MutedUsersVisibilityEffect.none;
175+
}
176+
}
177+
133178
void handleRealmUserEvent(RealmUserEvent event) {
134179
switch (event) {
135180
case RealmUserAddEvent():

0 commit comments

Comments
 (0)