Skip to content

Commit cd90a72

Browse files
committed
channel: Add willChangeIfTopicVisible/InStream methods
1 parent 24400bf commit cd90a72

File tree

2 files changed

+143
-2
lines changed

2 files changed

+143
-2
lines changed

lib/model/channel.dart

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,21 @@ mixin ChannelStore {
2525
/// For UI contexts that are not specific to a particular stream, see
2626
/// [isTopicVisible].
2727
bool isTopicVisibleInStream(int streamId, String topic) {
28-
switch (topicVisibilityPolicy(streamId, topic)) {
28+
return _isTopicVisibleInStream(topicVisibilityPolicy(streamId, topic));
29+
}
30+
31+
/// Whether the given event will change the result of [isTopicVisibleInStream]
32+
/// for its stream and topic, compared to the current state.
33+
VisibilityEffect willChangeIfTopicVisibleInStream(UserTopicEvent event) {
34+
final streamId = event.streamId;
35+
final topic = event.topicName;
36+
return VisibilityEffect._fromBeforeAfter(
37+
_isTopicVisibleInStream(topicVisibilityPolicy(streamId, topic)),
38+
_isTopicVisibleInStream(event.visibilityPolicy));
39+
}
40+
41+
static bool _isTopicVisibleInStream(UserTopicVisibilityPolicy policy) {
42+
switch (policy) {
2943
case UserTopicVisibilityPolicy.none:
3044
return true;
3145
case UserTopicVisibilityPolicy.muted:
@@ -48,7 +62,21 @@ mixin ChannelStore {
4862
/// For UI contexts that are specific to a particular stream, see
4963
/// [isTopicVisibleInStream].
5064
bool isTopicVisible(int streamId, String topic) {
51-
switch (topicVisibilityPolicy(streamId, topic)) {
65+
return _isTopicVisible(streamId, topicVisibilityPolicy(streamId, topic));
66+
}
67+
68+
/// Whether the given event will change the result of [isTopicVisible]
69+
/// for its stream and topic, compared to the current state.
70+
VisibilityEffect willChangeIfTopicVisible(UserTopicEvent event) {
71+
final streamId = event.streamId;
72+
final topic = event.topicName;
73+
return VisibilityEffect._fromBeforeAfter(
74+
_isTopicVisible(streamId, topicVisibilityPolicy(streamId, topic)),
75+
_isTopicVisible(streamId, event.visibilityPolicy));
76+
}
77+
78+
bool _isTopicVisible(int streamId, UserTopicVisibilityPolicy policy) {
79+
switch (policy) {
5280
case UserTopicVisibilityPolicy.none:
5381
switch (subscriptions[streamId]?.isMuted) {
5482
case false: return true;
@@ -67,6 +95,28 @@ mixin ChannelStore {
6795
}
6896
}
6997

98+
/// Whether and how a given [UserTopicEvent] will affect the results
99+
/// that [ChannelStore.isTopicVisible] or [ChannelStore.isTopicVisibleInStream]
100+
/// would give for some messages.
101+
enum VisibilityEffect {
102+
/// The event will have no effect on the visibility results.
103+
none,
104+
105+
/// The event will change some visibility results from true to false.
106+
muted,
107+
108+
/// The event will change some visibility results from false to true.
109+
unmuted;
110+
111+
factory VisibilityEffect._fromBeforeAfter(bool before, bool after) {
112+
return switch ((before, after)) {
113+
(false, true) => VisibilityEffect.unmuted,
114+
(true, false) => VisibilityEffect.muted,
115+
_ => VisibilityEffect.none,
116+
};
117+
}
118+
}
119+
70120
/// The implementation of [ChannelStore] that does the work.
71121
///
72122
/// Generally the only code that should need this class is [PerAccountStore]

test/model/channel_test.dart

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,97 @@ void main() {
189189
});
190190
});
191191

192+
group('willChangeIfTopicVisible/InStream', () {
193+
UserTopicEvent mkEvent(UserTopicVisibilityPolicy policy) =>
194+
eg.userTopicEvent(stream1.streamId, 'topic', policy);
195+
196+
void checkChanges(PerAccountStore store,
197+
UserTopicVisibilityPolicy newPolicy,
198+
VisibilityEffect expectedInStream, VisibilityEffect expectedOverall) {
199+
final event = mkEvent(newPolicy);
200+
check(store.willChangeIfTopicVisibleInStream(event)).equals(expectedInStream);
201+
check(store.willChangeIfTopicVisible (event)).equals(expectedOverall);
202+
}
203+
204+
test('stream not muted, policy none -> followed, no change', () async {
205+
final store = eg.store();
206+
await store.addStream(stream1);
207+
await store.addSubscription(eg.subscription(stream1));
208+
checkChanges(store, UserTopicVisibilityPolicy.followed,
209+
VisibilityEffect.none, VisibilityEffect.none);
210+
});
211+
212+
test('stream not muted, policy none -> muted, means muted', () async {
213+
final store = eg.store();
214+
await store.addStream(stream1);
215+
await store.addSubscription(eg.subscription(stream1));
216+
checkChanges(store, UserTopicVisibilityPolicy.muted,
217+
VisibilityEffect.muted, VisibilityEffect.muted);
218+
});
219+
220+
test('stream muted, policy none -> followed, means none/unmuted', () async {
221+
final store = eg.store();
222+
await store.addStream(stream1);
223+
await store.addSubscription(eg.subscription(stream1, isMuted: true));
224+
checkChanges(store, UserTopicVisibilityPolicy.followed,
225+
VisibilityEffect.none, VisibilityEffect.unmuted);
226+
});
227+
228+
test('stream muted, policy none -> muted, means muted/none', () async {
229+
final store = eg.store();
230+
await store.addStream(stream1);
231+
await store.addSubscription(eg.subscription(stream1, isMuted: true));
232+
checkChanges(store, UserTopicVisibilityPolicy.muted,
233+
VisibilityEffect.muted, VisibilityEffect.none);
234+
});
235+
236+
final policies = [
237+
UserTopicVisibilityPolicy.muted,
238+
UserTopicVisibilityPolicy.none,
239+
UserTopicVisibilityPolicy.unmuted,
240+
];
241+
for (final streamMuted in [null, false, true]) {
242+
for (final oldPolicy in policies) {
243+
for (final newPolicy in policies) {
244+
final streamDesc = switch (streamMuted) {
245+
false => "stream not muted",
246+
true => "stream muted",
247+
null => "stream unsubscribed",
248+
};
249+
test('$streamDesc, topic ${oldPolicy.name} -> ${newPolicy.name}', () async {
250+
final store = eg.store();
251+
await store.addStream(stream1);
252+
if (streamMuted != null) {
253+
await store.addSubscription(
254+
eg.subscription(stream1, isMuted: streamMuted));
255+
}
256+
store.handleEvent(mkEvent(oldPolicy));
257+
final oldVisibleInStream = store.isTopicVisibleInStream(stream1.streamId, 'topic');
258+
final oldVisible = store.isTopicVisible(stream1.streamId, 'topic');
259+
260+
final event = mkEvent(newPolicy);
261+
final willChangeInStream = store.willChangeIfTopicVisibleInStream(event);
262+
final willChange = store.willChangeIfTopicVisible(event);
263+
264+
store.handleEvent(event);
265+
final newVisibleInStream = store.isTopicVisibleInStream(stream1.streamId, 'topic');
266+
final newVisible = store.isTopicVisible(stream1.streamId, 'topic');
267+
268+
VisibilityEffect fromOldNew(bool oldVisible, bool newVisible) {
269+
if (newVisible == oldVisible) return VisibilityEffect.none;
270+
if (newVisible) return VisibilityEffect.unmuted;
271+
return VisibilityEffect.muted;
272+
}
273+
check(willChangeInStream)
274+
.equals(fromOldNew(oldVisibleInStream, newVisibleInStream));
275+
check(willChange)
276+
.equals(fromOldNew(oldVisible, newVisible));
277+
});
278+
}
279+
}
280+
}
281+
});
282+
192283
void compareTopicVisibility(PerAccountStore store, List<UserTopicItem> expected) {
193284
final expectedStore = eg.store(initialSnapshot: eg.initialSnapshot(
194285
userTopics: expected,

0 commit comments

Comments
 (0)