Skip to content

Commit bc72c11

Browse files
committed
Merge remote-tracking branch 'pr/1561'
2 parents 6d96bc7 + 5438a65 commit bc72c11

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
@@ -351,6 +351,12 @@ class MessageStoreImpl extends PerAccountStoreBase with MessageStore, _OutboxMes
351351
}
352352
}
353353

354+
void handleMutedUsersEvent(MutedUsersEvent event) {
355+
for (final view in _messageListViews) {
356+
view.handleMutedUsersEvent(event);
357+
}
358+
}
359+
354360
void handleMessageEvent(MessageEvent event) {
355361
// If the message is one we already know about (from a fetch),
356362
// 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

@@ -633,10 +634,12 @@ class MessageListView with ChangeNotifier, _MessageSequence {
633634
bool _messageVisible(MessageBase message) {
634635
switch (narrow) {
635636
case CombinedFeedNarrow():
636-
return switch (message.conversation) {
637+
final conversation = message.conversation;
638+
return switch (conversation) {
637639
StreamConversation(:final streamId, :final topic) =>
638640
store.isTopicVisible(streamId, topic),
639-
DmConversation() => true,
641+
DmConversation() => !store.shouldMuteDmConversation(
642+
DmNarrow.ofConversation(conversation, selfUserId: store.selfUserId)),
640643
};
641644

642645
case ChannelNarrow(:final streamId):
@@ -647,45 +650,71 @@ class MessageListView with ChangeNotifier, _MessageSequence {
647650

648651
case TopicNarrow():
649652
case DmNarrow():
653+
return true;
654+
650655
case MentionsNarrow():
651656
case StarredMessagesNarrow():
657+
if (message.conversation case DmConversation(:final allRecipientIds)) {
658+
return !store.shouldMuteDmConversation(DmNarrow(
659+
allRecipientIds: allRecipientIds, selfUserId: store.selfUserId));
660+
}
652661
return true;
653662
}
654663
}
655664

665+
/// Whether [_messageVisible] is true for all possible messages.
666+
///
667+
/// This is useful for an optimization.
668+
bool get _allMessagesVisible {
669+
switch (narrow) {
670+
case CombinedFeedNarrow():
671+
case ChannelNarrow():
672+
return false;
673+
674+
case TopicNarrow():
675+
case DmNarrow():
676+
return true;
677+
678+
case MentionsNarrow():
679+
case StarredMessagesNarrow():
680+
return false;
681+
}
682+
}
683+
656684
/// Whether this event could affect the result that [_messageVisible]
657685
/// would ever have returned for any possible message in this message list.
658-
VisibilityEffect _canAffectVisibility(UserTopicEvent event) {
686+
UserTopicVisibilityEffect _userTopicEventCanAffectVisibility(UserTopicEvent event) {
659687
switch (narrow) {
660688
case CombinedFeedNarrow():
661689
return store.willChangeIfTopicVisible(event);
662690

663691
case ChannelNarrow(:final streamId):
664-
if (event.streamId != streamId) return VisibilityEffect.none;
692+
if (event.streamId != streamId) return UserTopicVisibilityEffect.none;
665693
return store.willChangeIfTopicVisibleInStream(event);
666694

667695
case TopicNarrow():
668696
case DmNarrow():
669697
case MentionsNarrow():
670698
case StarredMessagesNarrow():
671-
return VisibilityEffect.none;
699+
return UserTopicVisibilityEffect.none;
672700
}
673701
}
674702

675-
/// Whether [_messageVisible] is true for all possible messages.
676-
///
677-
/// This is useful for an optimization.
678-
bool get _allMessagesVisible {
679-
switch (narrow) {
703+
/// Whether this event could affect the result that [_messageVisible]
704+
/// would ever have returned for any possible message in this message list.
705+
MutedUsersVisibilityEffect _mutedUsersEventCanAffectVisibility(MutedUsersEvent event) {
706+
switch(narrow) {
680707
case CombinedFeedNarrow():
681-
case ChannelNarrow():
682-
return false;
708+
return store.mightChangeShouldMuteDmConversation(event);
683709

710+
case ChannelNarrow():
684711
case TopicNarrow():
685712
case DmNarrow():
713+
return MutedUsersVisibilityEffect.none;
714+
686715
case MentionsNarrow():
687716
case StarredMessagesNarrow():
688-
return true;
717+
return store.mightChangeShouldMuteDmConversation(event);
689718
}
690719
}
691720

@@ -961,11 +990,11 @@ class MessageListView with ChangeNotifier, _MessageSequence {
961990
}
962991

963992
void handleUserTopicEvent(UserTopicEvent event) {
964-
switch (_canAffectVisibility(event)) {
965-
case VisibilityEffect.none:
993+
switch (_userTopicEventCanAffectVisibility(event)) {
994+
case UserTopicVisibilityEffect.none:
966995
return;
967996

968-
case VisibilityEffect.muted:
997+
case UserTopicVisibilityEffect.muted:
969998
bool removed = _removeMessagesWhere((message) =>
970999
message is StreamMessage
9711000
&& message.streamId == event.streamId
@@ -980,7 +1009,35 @@ class MessageListView with ChangeNotifier, _MessageSequence {
9801009
notifyListeners();
9811010
}
9821011

983-
case VisibilityEffect.unmuted:
1012+
case UserTopicVisibilityEffect.unmuted:
1013+
// TODO get the newly-unmuted messages from the message store
1014+
// For now, we simplify the task by just refetching this message list
1015+
// from scratch.
1016+
if (fetched) {
1017+
_reset();
1018+
notifyListeners();
1019+
fetchInitial();
1020+
}
1021+
}
1022+
}
1023+
1024+
void handleMutedUsersEvent(MutedUsersEvent event) {
1025+
switch (_mutedUsersEventCanAffectVisibility(event)) {
1026+
case MutedUsersVisibilityEffect.none:
1027+
return;
1028+
1029+
case MutedUsersVisibilityEffect.muted:
1030+
final anyRemoved = _removeMessagesWhere((message) {
1031+
if (message is! DmMessage) return false;
1032+
final narrow = DmNarrow.ofMessage(message, selfUserId: store.selfUserId);
1033+
return store.shouldMuteDmConversation(narrow, event: event);
1034+
});
1035+
if (anyRemoved) {
1036+
notifyListeners();
1037+
}
1038+
1039+
case MutedUsersVisibilityEffect.mixed:
1040+
case MutedUsersVisibilityEffect.unmuted:
9841041
// TODO get the newly-unmuted messages from the message store
9851042
// For now, we simplify the task by just refetching this message list
9861043
// 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;
@@ -961,6 +965,8 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor
961965

962966
case MutedUsersEvent():
963967
assert(debugLog("server event: muted_users"));
968+
_messages.handleMutedUsersEvent(event);
969+
// Update _users last, so other handlers can compare to the old value.
964970
_users.handleMutedUsersEvent(event);
965971
notifyListeners();
966972

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)