Skip to content

Commit 84186c6

Browse files
committed
Merge remote-tracking branch 'pr/1561'
2 parents 0c85980 + 5438a65 commit 84186c6

19 files changed

+478
-94
lines changed

lib/model/autocomplete.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'compose.dart';
1212
import 'emoji.dart';
1313
import 'narrow.dart';
1414
import 'store.dart';
15+
import 'user.dart';
1516

1617
extension ComposeContentAutocomplete on ComposeContentController {
1718
AutocompleteIntent<ComposeAutocompleteQuery>? autocompleteIntent() {
@@ -648,7 +649,7 @@ class MentionAutocompleteView extends AutocompleteView<MentionAutocompleteQuery,
648649
}
649650

650651
MentionAutocompleteResult? _testUser(MentionAutocompleteQuery query, User user) {
651-
if (query.testUser(user, store.autocompleteViewManager.autocompleteDataCache)) {
652+
if (query.testUser(user, store.autocompleteViewManager.autocompleteDataCache, store)) {
652653
return UserMentionAutocompleteResult(userId: user.userId);
653654
}
654655
return null;
@@ -753,9 +754,10 @@ class MentionAutocompleteQuery extends ComposeAutocompleteQuery {
753754
|| wildcardOption.localizedCanonicalString(localizations).contains(_lowercase);
754755
}
755756

756-
bool testUser(User user, AutocompleteDataCache cache) {
757+
bool testUser(User user, AutocompleteDataCache cache, UserStore store) {
757758
// TODO(#236) test email too, not just name
758759
if (!user.isActive) return false;
760+
if (store.isUserMuted(user.userId)) return false;
759761

760762
return _testName(user, cache);
761763
}

lib/model/channel.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ mixin ChannelStore {
6969

7070
/// Whether the given event will change the result of [isTopicVisibleInStream]
7171
/// for its stream and topic, compared to the current state.
72-
VisibilityEffect willChangeIfTopicVisibleInStream(UserTopicEvent event) {
72+
UserTopicVisibilityEffect willChangeIfTopicVisibleInStream(UserTopicEvent event) {
7373
final streamId = event.streamId;
7474
final topic = event.topicName;
75-
return VisibilityEffect._fromBeforeAfter(
75+
return UserTopicVisibilityEffect._fromBeforeAfter(
7676
_isTopicVisibleInStream(topicVisibilityPolicy(streamId, topic)),
7777
_isTopicVisibleInStream(event.visibilityPolicy));
7878
}
@@ -106,10 +106,10 @@ mixin ChannelStore {
106106

107107
/// Whether the given event will change the result of [isTopicVisible]
108108
/// for its stream and topic, compared to the current state.
109-
VisibilityEffect willChangeIfTopicVisible(UserTopicEvent event) {
109+
UserTopicVisibilityEffect willChangeIfTopicVisible(UserTopicEvent event) {
110110
final streamId = event.streamId;
111111
final topic = event.topicName;
112-
return VisibilityEffect._fromBeforeAfter(
112+
return UserTopicVisibilityEffect._fromBeforeAfter(
113113
_isTopicVisible(streamId, topicVisibilityPolicy(streamId, topic)),
114114
_isTopicVisible(streamId, event.visibilityPolicy));
115115
}
@@ -137,7 +137,7 @@ mixin ChannelStore {
137137
/// Whether and how a given [UserTopicEvent] will affect the results
138138
/// that [ChannelStore.isTopicVisible] or [ChannelStore.isTopicVisibleInStream]
139139
/// would give for some messages.
140-
enum VisibilityEffect {
140+
enum UserTopicVisibilityEffect {
141141
/// The event will have no effect on the visibility results.
142142
none,
143143

@@ -147,11 +147,11 @@ enum VisibilityEffect {
147147
/// The event will change some visibility results from false to true.
148148
unmuted;
149149

150-
factory VisibilityEffect._fromBeforeAfter(bool before, bool after) {
150+
factory UserTopicVisibilityEffect._fromBeforeAfter(bool before, bool after) {
151151
return switch ((before, after)) {
152-
(false, true) => VisibilityEffect.unmuted,
153-
(true, false) => VisibilityEffect.muted,
154-
_ => VisibilityEffect.none,
152+
(false, true) => UserTopicVisibilityEffect.unmuted,
153+
(true, false) => UserTopicVisibilityEffect.muted,
154+
_ => UserTopicVisibilityEffect.none,
155155
};
156156
}
157157
}

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: 74 additions & 17 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
export '../api/route/messages.dart' show Anchor, AnchorCode, NumericAnchor;
1819

@@ -622,10 +623,12 @@ class MessageListView with ChangeNotifier, _MessageSequence {
622623
bool _messageVisible(MessageBase message) {
623624
switch (narrow) {
624625
case CombinedFeedNarrow():
625-
return switch (message.conversation) {
626+
final conversation = message.conversation;
627+
return switch (conversation) {
626628
StreamConversation(:final streamId, :final topic) =>
627629
store.isTopicVisible(streamId, topic),
628-
DmConversation() => true,
630+
DmConversation() => !store.shouldMuteDmConversation(
631+
DmNarrow.ofConversation(conversation, selfUserId: store.selfUserId)),
629632
};
630633

631634
case ChannelNarrow(:final streamId):
@@ -636,45 +639,71 @@ class MessageListView with ChangeNotifier, _MessageSequence {
636639

637640
case TopicNarrow():
638641
case DmNarrow():
642+
return true;
643+
639644
case MentionsNarrow():
640645
case StarredMessagesNarrow():
646+
if (message.conversation case DmConversation(:final allRecipientIds)) {
647+
return !store.shouldMuteDmConversation(DmNarrow(
648+
allRecipientIds: allRecipientIds, selfUserId: store.selfUserId));
649+
}
641650
return true;
642651
}
643652
}
644653

654+
/// Whether [_messageVisible] is true for all possible messages.
655+
///
656+
/// This is useful for an optimization.
657+
bool get _allMessagesVisible {
658+
switch (narrow) {
659+
case CombinedFeedNarrow():
660+
case ChannelNarrow():
661+
return false;
662+
663+
case TopicNarrow():
664+
case DmNarrow():
665+
return true;
666+
667+
case MentionsNarrow():
668+
case StarredMessagesNarrow():
669+
return false;
670+
}
671+
}
672+
645673
/// Whether this event could affect the result that [_messageVisible]
646674
/// would ever have returned for any possible message in this message list.
647-
VisibilityEffect _canAffectVisibility(UserTopicEvent event) {
675+
UserTopicVisibilityEffect _userTopicEventCanAffectVisibility(UserTopicEvent event) {
648676
switch (narrow) {
649677
case CombinedFeedNarrow():
650678
return store.willChangeIfTopicVisible(event);
651679

652680
case ChannelNarrow(:final streamId):
653-
if (event.streamId != streamId) return VisibilityEffect.none;
681+
if (event.streamId != streamId) return UserTopicVisibilityEffect.none;
654682
return store.willChangeIfTopicVisibleInStream(event);
655683

656684
case TopicNarrow():
657685
case DmNarrow():
658686
case MentionsNarrow():
659687
case StarredMessagesNarrow():
660-
return VisibilityEffect.none;
688+
return UserTopicVisibilityEffect.none;
661689
}
662690
}
663691

664-
/// Whether [_messageVisible] is true for all possible messages.
665-
///
666-
/// This is useful for an optimization.
667-
bool get _allMessagesVisible {
668-
switch (narrow) {
692+
/// Whether this event could affect the result that [_messageVisible]
693+
/// would ever have returned for any possible message in this message list.
694+
MutedUsersVisibilityEffect _mutedUsersEventCanAffectVisibility(MutedUsersEvent event) {
695+
switch(narrow) {
669696
case CombinedFeedNarrow():
670-
case ChannelNarrow():
671-
return false;
697+
return store.mightChangeShouldMuteDmConversation(event);
672698

699+
case ChannelNarrow():
673700
case TopicNarrow():
674701
case DmNarrow():
702+
return MutedUsersVisibilityEffect.none;
703+
675704
case MentionsNarrow():
676705
case StarredMessagesNarrow():
677-
return true;
706+
return store.mightChangeShouldMuteDmConversation(event);
678707
}
679708
}
680709

@@ -950,11 +979,11 @@ class MessageListView with ChangeNotifier, _MessageSequence {
950979
}
951980

952981
void handleUserTopicEvent(UserTopicEvent event) {
953-
switch (_canAffectVisibility(event)) {
954-
case VisibilityEffect.none:
982+
switch (_userTopicEventCanAffectVisibility(event)) {
983+
case UserTopicVisibilityEffect.none:
955984
return;
956985

957-
case VisibilityEffect.muted:
986+
case UserTopicVisibilityEffect.muted:
958987
bool removed = _removeMessagesWhere((message) =>
959988
message is StreamMessage
960989
&& message.streamId == event.streamId
@@ -969,7 +998,35 @@ class MessageListView with ChangeNotifier, _MessageSequence {
969998
notifyListeners();
970999
}
9711000

972-
case VisibilityEffect.unmuted:
1001+
case UserTopicVisibilityEffect.unmuted:
1002+
// TODO get the newly-unmuted messages from the message store
1003+
// For now, we simplify the task by just refetching this message list
1004+
// from scratch.
1005+
if (fetched) {
1006+
_reset();
1007+
notifyListeners();
1008+
fetchInitial();
1009+
}
1010+
}
1011+
}
1012+
1013+
void handleMutedUsersEvent(MutedUsersEvent event) {
1014+
switch (_mutedUsersEventCanAffectVisibility(event)) {
1015+
case MutedUsersVisibilityEffect.none:
1016+
return;
1017+
1018+
case MutedUsersVisibilityEffect.muted:
1019+
final anyRemoved = _removeMessagesWhere((message) {
1020+
if (message is! DmMessage) return false;
1021+
final narrow = DmNarrow.ofMessage(message, selfUserId: store.selfUserId);
1022+
return store.shouldMuteDmConversation(narrow, event: event);
1023+
});
1024+
if (anyRemoved) {
1025+
notifyListeners();
1026+
}
1027+
1028+
case MutedUsersVisibilityEffect.mixed:
1029+
case MutedUsersVisibilityEffect.unmuted:
9731030
// TODO get the newly-unmuted messages from the message store
9741031
// For now, we simplify the task by just refetching this message list
9751032
// from scratch.

lib/model/narrow.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,21 @@ class DmNarrow extends Narrow implements SendableNarrow {
200200
required int selfUserId,
201201
}) {
202202
return DmNarrow(
203+
// TODO should this really be making a copy of `allRecipientIds`?
203204
allRecipientIds: List.unmodifiable(message.conversation.allRecipientIds),
204205
selfUserId: selfUserId,
205206
);
206207
}
207208

209+
factory DmNarrow.ofConversation(DmConversation conversation, {
210+
required int selfUserId,
211+
}) {
212+
return DmNarrow(
213+
allRecipientIds: conversation.allRecipientIds,
214+
selfUserId: selfUserId,
215+
);
216+
}
217+
208218
/// A [DmNarrow] from an item in [InitialSnapshot.recentPrivateConversations].
209219
factory DmNarrow.ofRecentDmConversation(RecentDmConversation conversation, {required int selfUserId}) {
210220
return DmNarrow.withOtherUsers(conversation.userIds, selfUserId: selfUserId);

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, {MutedUsersEvent? event}) =>
650650
_users.isUserMuted(userId, event: event);
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/typing_status.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ class TypingStatus extends PerAccountStoreBase with ChangeNotifier {
2121

2222
Iterable<SendableNarrow> get debugActiveNarrows => _timerMapsByNarrow.keys;
2323

24-
Iterable<int> typistIdsInNarrow(SendableNarrow narrow) =>
25-
_timerMapsByNarrow[narrow]?.keys ?? [];
24+
Iterable<int>? typistIdsInNarrow(SendableNarrow narrow) =>
25+
_timerMapsByNarrow[narrow]?.keys;
2626

2727
// Using SendableNarrow as the key covers the narrows
2828
// where typing notices are supported (topics and DMs).

lib/model/user.dart

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
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';
6+
import 'narrow.dart';
57
import 'store.dart';
68

79
/// The portion of [PerAccountStore] describing the users in the realm.
@@ -85,6 +87,38 @@ mixin UserStore on PerAccountStoreBase {
8587
/// Looks for [userId] in a private [Set],
8688
/// or in [event.mutedUsers] instead if event is non-null.
8789
bool isUserMuted(int userId, {MutedUsersEvent? event});
90+
91+
/// Whether the self-user has muted everyone in [narrow].
92+
///
93+
/// Returns false for the self-DM.
94+
///
95+
/// Calls [isUserMuted] for each participant, passing along [event].
96+
bool shouldMuteDmConversation(DmNarrow narrow, {MutedUsersEvent? event}) {
97+
if (narrow.otherRecipientIds.isEmpty) return false;
98+
return narrow.otherRecipientIds.every(
99+
(userId) => isUserMuted(userId, event: event));
100+
}
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;
88122
}
89123

90124
/// The implementation of [UserStore] that does the work.
@@ -118,6 +152,29 @@ class UserStoreImpl extends PerAccountStoreBase with UserStore {
118152
return (event?.mutedUsers.map((item) => item.id) ?? _mutedUsers).contains(userId);
119153
}
120154

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+
121178
void handleRealmUserEvent(RealmUserEvent event) {
122179
switch (event) {
123180
case RealmUserAddEvent():

0 commit comments

Comments
 (0)