diff --git a/lib/model/emoji.dart b/lib/model/emoji.dart index b0ec5f7324..670c574c12 100644 --- a/lib/model/emoji.dart +++ b/lib/model/emoji.dart @@ -137,15 +137,12 @@ mixin EmojiStore { /// Generally the only code that should need this class is [PerAccountStore] /// itself. Other code accesses this functionality through [PerAccountStore], /// or through the mixin [EmojiStore] which describes its interface. -class EmojiStoreImpl with EmojiStore { +class EmojiStoreImpl extends PerAccountStoreBase with EmojiStore { EmojiStoreImpl({ - required this.realmUrl, + required super.core, required this.allRealmEmoji, }) : _serverEmojiData = null; // TODO(#974) maybe start from a hard-coded baseline - /// The same as [PerAccountStore.realmUrl]. - final Uri realmUrl; - /// The realm's custom emoji, indexed by their [RealmEmojiItem.emojiCode], /// including deactivated emoji not available for new uses. /// @@ -195,19 +192,19 @@ class EmojiStoreImpl with EmojiStore { required String? stillUrl, required String emojiName, }) { - final source = Uri.tryParse(sourceUrl); - if (source == null) return TextEmojiDisplay(emojiName: emojiName); + final resolvedUrl = this.tryResolveUrl(sourceUrl); + if (resolvedUrl == null) return TextEmojiDisplay(emojiName: emojiName); - Uri? still; + Uri? resolvedStillUrl; if (stillUrl != null) { - still = Uri.tryParse(stillUrl); - if (still == null) return TextEmojiDisplay(emojiName: emojiName); + resolvedStillUrl = this.tryResolveUrl(stillUrl); + if (resolvedStillUrl == null) return TextEmojiDisplay(emojiName: emojiName); } return ImageEmojiDisplay( emojiName: emojiName, - resolvedUrl: realmUrl.resolveUri(source), - resolvedStillUrl: still == null ? null : realmUrl.resolveUri(still), + resolvedUrl: resolvedUrl, + resolvedStillUrl: resolvedStillUrl, ); } diff --git a/lib/model/recent_dm_conversations.dart b/lib/model/recent_dm_conversations.dart index b38610be15..8428ecdf63 100644 --- a/lib/model/recent_dm_conversations.dart +++ b/lib/model/recent_dm_conversations.dart @@ -7,18 +7,19 @@ import '../api/model/initial_snapshot.dart'; import '../api/model/model.dart'; import '../api/model/events.dart'; import 'narrow.dart'; +import 'store.dart'; /// A view-model for the recent-DM-conversations UI. /// /// This maintains the list of recent DM conversations, /// plus additional data in order to efficiently maintain the list. -class RecentDmConversationsView extends ChangeNotifier { +class RecentDmConversationsView extends PerAccountStoreBase with ChangeNotifier { factory RecentDmConversationsView({ + required CorePerAccountStore core, required List initial, - required int selfUserId, }) { final entries = initial.map((conversation) => MapEntry( - DmNarrow.ofRecentDmConversation(conversation, selfUserId: selfUserId), + DmNarrow.ofRecentDmConversation(conversation, selfUserId: core.selfUserId), conversation.maxMessageId, )).toList()..sort((a, b) => -a.value.compareTo(b.value)); @@ -33,18 +34,18 @@ class RecentDmConversationsView extends ChangeNotifier { } return RecentDmConversationsView._( + core: core, map: Map.fromEntries(entries), sorted: QueueList.from(entries.map((e) => e.key)), latestMessagesByRecipient: latestMessagesByRecipient, - selfUserId: selfUserId, ); } RecentDmConversationsView._({ + required super.core, required this.map, required this.sorted, required this.latestMessagesByRecipient, - required this.selfUserId, }); /// The latest message ID in each conversation. @@ -62,8 +63,6 @@ class RecentDmConversationsView extends ChangeNotifier { /// it might have been sent by anyone in its conversation.) final Map latestMessagesByRecipient; - final int selfUserId; - /// Insert the key at the proper place in [sorted]. /// /// Optimized, taking O(1) time, for the case where that place is the start, diff --git a/lib/model/store.dart b/lib/model/store.dart index 4a4a3e209a..bd6e359045 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -328,10 +328,31 @@ abstract class GlobalStore extends ChangeNotifier { class AccountNotFoundException implements Exception {} /// A bundle of items that are useful to [PerAccountStore] and its substores. +/// +/// Each instance of this class is constructed as part of constructing a +/// [PerAccountStore] instance, +/// and is shared by that [PerAccountStore] and its substores. +/// Calling [PerAccountStore.dispose] also disposes the [CorePerAccountStore] +/// (for example, it calls [ApiConnection.dispose] on [connection]). class CorePerAccountStore { - CorePerAccountStore({required this.connection}); + CorePerAccountStore._({ + required GlobalStore globalStore, + required this.connection, + required this.accountId, + required this.selfUserId, + }) : _globalStore = globalStore, + assert(connection.realmUrl == globalStore.getAccount(accountId)!.realmUrl); + final GlobalStore _globalStore; final ApiConnection connection; // TODO(#135): update zulipFeatureLevel with events + final int accountId; + + // This isn't strictly needed as a field; it could be a getter + // that uses `_globalStore.getAccount(accountId)`. + // But we denormalize it here to save a hash-table lookup every time + // the self-user ID is needed, which can be often. + // It never changes on the account; see [GlobalStore.updateAccount]. + final int selfUserId; } /// A base class for [PerAccountStore] and its substores, @@ -342,7 +363,54 @@ abstract class PerAccountStoreBase { final CorePerAccountStore _core; + //////////////////////////////// + // Where data comes from in the first place. + + GlobalStore get _globalStore => _core._globalStore; + ApiConnection get connection => _core.connection; + + //////////////////////////////// + // Data attached to the realm or the server. + + /// Always equal to `account.realmUrl` and `connection.realmUrl`. + Uri get realmUrl => connection.realmUrl; + + /// Resolve [reference] as a URL relative to [realmUrl]. + /// + /// This returns null if [reference] fails to parse as a URL. + Uri? tryResolveUrl(String reference) => _tryResolveUrl(realmUrl, reference); + + //////////////////////////////// + // Data attached to the self-account on the realm. + + int get accountId => _core.accountId; + + /// The [Account] this store belongs to. + /// + /// Will throw if the account has been removed from the global store, + /// which is possible only if [PerAccountStore.dispose] has been called + /// on this store. + Account get account => _globalStore.getAccount(accountId)!; + + /// The user ID of the "self-user", + /// i.e. the account the person using this app is logged into. + /// + /// This always equals the [Account.userId] on [account]. + /// + /// For the corresponding [User] object, see [UserStore.selfUser]. + int get selfUserId => _core.selfUserId; +} + +const _tryResolveUrl = tryResolveUrl; + +/// Like [Uri.resolve], but on failure return null instead of throwing. +Uri? tryResolveUrl(Uri baseUrl, String reference) { + try { + return baseUrl.resolve(reference); + } on FormatException { + return null; + } } /// Store for the user's data for a given Zulip account. @@ -385,14 +453,16 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor throw Exception("bad initial snapshot: missing queueId"); } - final realmUrl = account.realmUrl; - final core = CorePerAccountStore(connection: connection); + final core = CorePerAccountStore._( + globalStore: globalStore, + connection: connection, + accountId: accountId, + selfUserId: account.userId, + ); final channels = ChannelStoreImpl(initialSnapshot: initialSnapshot); return PerAccountStore._( - globalStore: globalStore, core: core, queueId: queueId, - realmUrl: realmUrl, realmWildcardMentionPolicy: initialSnapshot.realmWildcardMentionPolicy, realmMandatoryTopics: initialSnapshot.realmMandatoryTopics, realmWaitingPeriodThreshold: initialSnapshot.realmWaitingPeriodThreshold, @@ -402,41 +472,35 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor customProfileFields: _sortCustomProfileFields(initialSnapshot.customProfileFields), emailAddressVisibility: initialSnapshot.emailAddressVisibility, emoji: EmojiStoreImpl( - realmUrl: realmUrl, allRealmEmoji: initialSnapshot.realmEmoji), - accountId: accountId, + core: core, allRealmEmoji: initialSnapshot.realmEmoji), userSettings: initialSnapshot.userSettings, typingNotifier: TypingNotifier( - connection: connection, + core: core, typingStoppedWaitPeriod: Duration( milliseconds: initialSnapshot.serverTypingStoppedWaitPeriodMilliseconds), typingStartedWaitPeriod: Duration( milliseconds: initialSnapshot.serverTypingStartedWaitPeriodMilliseconds), ), - users: UserStoreImpl( - selfUserId: account.userId, - initialSnapshot: initialSnapshot), - typingStatus: TypingStatus( - selfUserId: account.userId, + users: UserStoreImpl(core: core, initialSnapshot: initialSnapshot), + typingStatus: TypingStatus(core: core, typingStartedExpiryPeriod: Duration(milliseconds: initialSnapshot.serverTypingStartedExpiryPeriodMilliseconds), ), channels: channels, messages: MessageStoreImpl(core: core), unreads: Unreads( initial: initialSnapshot.unreadMsgs, - selfUserId: account.userId, + core: core, channelStore: channels, ), - recentDmConversationsView: RecentDmConversationsView( - initial: initialSnapshot.recentPrivateConversations, selfUserId: account.userId), + recentDmConversationsView: RecentDmConversationsView(core: core, + initial: initialSnapshot.recentPrivateConversations), recentSenders: RecentSenders(), ); } PerAccountStore._({ - required GlobalStore globalStore, required super.core, required this.queueId, - required this.realmUrl, required this.realmWildcardMentionPolicy, required this.realmMandatoryTopics, required this.realmWaitingPeriodThreshold, @@ -446,7 +510,6 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor required this.customProfileFields, required this.emailAddressVisibility, required EmojiStoreImpl emoji, - required this.accountId, required this.userSettings, required this.typingNotifier, required UserStoreImpl users, @@ -456,11 +519,7 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor required this.unreads, required this.recentDmConversationsView, required this.recentSenders, - }) : assert(realmUrl == globalStore.getAccount(accountId)!.realmUrl), - assert(realmUrl == core.connection.realmUrl), - assert(emoji.realmUrl == realmUrl), - _globalStore = globalStore, - _realmEmptyTopicDisplayName = realmEmptyTopicDisplayName, + }) : _realmEmptyTopicDisplayName = realmEmptyTopicDisplayName, _emoji = emoji, _users = users, _channels = channels, @@ -472,8 +531,6 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor //////////////////////////////// // Where data comes from in the first place. - final GlobalStore _globalStore; - final String queueId; UpdateMachine? get updateMachine => _updateMachine; UpdateMachine? _updateMachine; @@ -495,14 +552,6 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor //////////////////////////////// // Data attached to the realm or the server. - /// Always equal to `account.realmUrl` and `connection.realmUrl`. - final Uri realmUrl; - - /// Resolve [reference] as a URL relative to [realmUrl]. - /// - /// This returns null if [reference] fails to parse as a URL. - Uri? tryResolveUrl(String reference) => _tryResolveUrl(realmUrl, reference); - /// Always equal to `connection.zulipFeatureLevel` /// and `account.zulipFeatureLevel`. int get zulipFeatureLevel => connection.zulipFeatureLevel!; @@ -560,13 +609,6 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor //////////////////////////////// // Data attached to the self-account on the realm. - final int accountId; - - /// The [Account] this store belongs to. - /// - /// Will throw if called after [dispose] has been called. - Account get account => _globalStore.getAccount(accountId)!; - final UserSettings? userSettings; // TODO(server-5) final TypingNotifier typingNotifier; @@ -574,9 +616,6 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor //////////////////////////////// // Users and data about them. - @override - int get selfUserId => _users.selfUserId; - @override User? getUser(int userId) => _users.getUser(userId); @@ -882,17 +921,6 @@ class PerAccountStore extends PerAccountStoreBase with ChangeNotifier, EmojiStor String toString() => '${objectRuntimeType(this, 'PerAccountStore')}#${shortHash(this)}'; } -const _tryResolveUrl = tryResolveUrl; - -/// Like [Uri.resolve], but on failure return null instead of throwing. -Uri? tryResolveUrl(Uri baseUrl, String reference) { - try { - return baseUrl.resolve(reference); - } on FormatException { - return null; - } -} - /// A [GlobalStoreBackend] that uses a live, persistent local database. /// /// Used as part of a [LiveGlobalStore]. diff --git a/lib/model/typing_status.dart b/lib/model/typing_status.dart index 2956564c20..1ddd72c48b 100644 --- a/lib/model/typing_status.dart +++ b/lib/model/typing_status.dart @@ -2,22 +2,21 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import '../api/core.dart'; import '../api/model/events.dart'; import '../api/route/typing.dart'; import 'binding.dart'; import 'narrow.dart'; +import 'store.dart'; /// The model for tracking the typing status organized by narrows. /// /// Listeners are notified when a typist is added or removed from any narrow. -class TypingStatus extends ChangeNotifier { +class TypingStatus extends PerAccountStoreBase with ChangeNotifier { TypingStatus({ - required this.selfUserId, + required super.core, required this.typingStartedExpiryPeriod, }); - final int selfUserId; final Duration typingStartedExpiryPeriod; Iterable get debugActiveNarrows => _timerMapsByNarrow.keys; @@ -93,14 +92,13 @@ class TypingStatus extends ChangeNotifier { /// See also: /// * https://github.com/zulip/zulip/blob/52a9846cdf4abfbe937a94559690d508e95f4065/web/shared/src/typing_status.ts /// * https://zulip.readthedocs.io/en/latest/subsystems/typing-indicators.html -class TypingNotifier { +class TypingNotifier extends PerAccountStoreBase { TypingNotifier({ - required this.connection, + required super.core, required this.typingStoppedWaitPeriod, required this.typingStartedWaitPeriod, }); - final ApiConnection connection; final Duration typingStoppedWaitPeriod; final Duration typingStartedWaitPeriod; diff --git a/lib/model/unreads.dart b/lib/model/unreads.dart index 34b4492013..254b615452 100644 --- a/lib/model/unreads.dart +++ b/lib/model/unreads.dart @@ -10,6 +10,7 @@ import '../log.dart'; import 'algorithms.dart'; import 'narrow.dart'; import 'channel.dart'; +import 'store.dart'; /// The view-model for unread messages. /// @@ -34,10 +35,10 @@ import 'channel.dart'; // sync to those unreads, because the user has shown an interest in them. // TODO When loading a message list with stream messages, check all the stream // messages and refresh [mentions] (see [mentions] dartdoc). -class Unreads extends ChangeNotifier { +class Unreads extends PerAccountStoreBase with ChangeNotifier { factory Unreads({ required UnreadMessagesSnapshot initial, - required int selfUserId, + required CorePerAccountStore core, required ChannelStore channelStore, }) { final streams = >>{}; @@ -52,32 +53,33 @@ class Unreads extends ChangeNotifier { for (final unreadDmSnapshot in initial.dms) { final otherUserId = unreadDmSnapshot.otherUserId; - final narrow = DmNarrow.withUser(otherUserId, selfUserId: selfUserId); + final narrow = DmNarrow.withUser(otherUserId, selfUserId: core.selfUserId); dms[narrow] = QueueList.from(unreadDmSnapshot.unreadMessageIds); } for (final unreadHuddleSnapshot in initial.huddles) { - final narrow = DmNarrow.ofUnreadHuddleSnapshot(unreadHuddleSnapshot, selfUserId: selfUserId); + final narrow = DmNarrow.ofUnreadHuddleSnapshot(unreadHuddleSnapshot, + selfUserId: core.selfUserId); dms[narrow] = QueueList.from(unreadHuddleSnapshot.unreadMessageIds); } return Unreads._( + core: core, channelStore: channelStore, streams: streams, dms: dms, mentions: mentions, oldUnreadsMissing: initial.oldUnreadsMissing, - selfUserId: selfUserId, ); } Unreads._({ + required super.core, required this.channelStore, required this.streams, required this.dms, required this.mentions, required this.oldUnreadsMissing, - required this.selfUserId, }); final ChannelStore channelStore; @@ -125,8 +127,6 @@ class Unreads extends ChangeNotifier { /// Is set to false when the user clears out all unreads. bool oldUnreadsMissing; - final int selfUserId; - // TODO(#370): maintain this count incrementally, rather than recomputing from scratch int countInCombinedFeedNarrow() { int c = 0; diff --git a/lib/model/user.dart b/lib/model/user.dart index 1577e21048..05ab2747df 100644 --- a/lib/model/user.dart +++ b/lib/model/user.dart @@ -2,17 +2,10 @@ import '../api/model/events.dart'; import '../api/model/initial_snapshot.dart'; import '../api/model/model.dart'; import 'localizations.dart'; +import 'store.dart'; /// The portion of [PerAccountStore] describing the users in the realm. -mixin UserStore { - /// The user ID of the "self-user", - /// i.e. the account the person using this app is logged into. - /// - /// This always equals the [Account.userId] on [PerAccountStore.account]. - /// - /// For the corresponding [User] object, see [selfUser]. - int get selfUserId; - +mixin UserStore on PerAccountStoreBase { /// The user with the given ID, if that user is known. /// /// There may be other users that are perfectly real but are @@ -80,9 +73,9 @@ mixin UserStore { /// Generally the only code that should need this class is [PerAccountStore] /// itself. Other code accesses this functionality through [PerAccountStore], /// or through the mixin [UserStore] which describes its interface. -class UserStoreImpl with UserStore { +class UserStoreImpl extends PerAccountStoreBase with UserStore { UserStoreImpl({ - required this.selfUserId, + required super.core, required InitialSnapshot initialSnapshot, }) : _users = Map.fromEntries( initialSnapshot.realmUsers @@ -90,9 +83,6 @@ class UserStoreImpl with UserStore { .followedBy(initialSnapshot.crossRealmBots) .map((user) => MapEntry(user.userId, user))); - @override - final int selfUserId; - final Map _users; @override diff --git a/test/model/recent_dm_conversations_test.dart b/test/model/recent_dm_conversations_test.dart index da487304e6..8905460e66 100644 --- a/test/model/recent_dm_conversations_test.dart +++ b/test/model/recent_dm_conversations_test.dart @@ -6,6 +6,7 @@ import 'package:zulip/model/recent_dm_conversations.dart'; import '../example_data.dart' as eg; import 'recent_dm_conversations_checks.dart'; +import 'store_checks.dart'; void main() { group('RecentDmConversationsView', () { @@ -18,18 +19,19 @@ void main() { } test('construct from initial data', () { - check(RecentDmConversationsView(selfUserId: eg.selfUser.userId, - initial: [])) + check(eg.store(initialSnapshot: eg.initialSnapshot( + recentPrivateConversations: [], + ))).recentDmConversationsView ..map.isEmpty() ..sorted.isEmpty() ..latestMessagesByRecipient.isEmpty(); - check(RecentDmConversationsView(selfUserId: eg.selfUser.userId, - initial: [ + check(eg.store(initialSnapshot: eg.initialSnapshot( + recentPrivateConversations: [ RecentDmConversation(userIds: [], maxMessageId: 200), RecentDmConversation(userIds: [1], maxMessageId: 100), RecentDmConversation(userIds: [2, 1], maxMessageId: 300), // userIds out of order - ])) + ]))).recentDmConversationsView ..map.deepEquals({ key([1, 2]): 300, key([]): 200, @@ -41,11 +43,11 @@ void main() { group('message event (new message)', () { RecentDmConversationsView setupView() { - return RecentDmConversationsView(selfUserId: eg.selfUser.userId, - initial: [ + return eg.store(initialSnapshot: eg.initialSnapshot( + recentPrivateConversations: [ RecentDmConversation(userIds: [1], maxMessageId: 200), RecentDmConversation(userIds: [1, 2], maxMessageId: 100), - ]); + ])).recentDmConversationsView; } test('(check base state)', () { diff --git a/test/model/typing_status_test.dart b/test/model/typing_status_test.dart index 4061301366..01d817680a 100644 --- a/test/model/typing_status_test.dart +++ b/test/model/typing_status_test.dart @@ -77,9 +77,11 @@ void main() { int? selfUserId, Map> typistsByNarrow = const {}, }) { - model = TypingStatus( - selfUserId: selfUserId ?? eg.selfUser.userId, - typingStartedExpiryPeriod: const Duration(milliseconds: 15000)); + final store = eg.store( + account: eg.selfAccount.copyWith(id: selfUserId), + initialSnapshot: eg.initialSnapshot( + serverTypingStartedExpiryPeriodMilliseconds: 15000)); + model = store.typingStatus; check(model.debugActiveNarrows).isEmpty(); notifiedCount = 0; model.addListener(() => notifiedCount += 1); diff --git a/test/model/unreads_test.dart b/test/model/unreads_test.dart index e8f7a0f850..42e799624f 100644 --- a/test/model/unreads_test.dart +++ b/test/model/unreads_test.dart @@ -16,7 +16,7 @@ void main() { // These variables are the common state operated on by each test. // Each test case calls [prepare] to initialize them. late Unreads model; - late PerAccountStore channelStore; // TODO reduce this to ChannelStore + late PerAccountStore store; late int notifiedCount; void checkNotified({required int count}) { @@ -37,10 +37,9 @@ void main() { oldUnreadsMissing: false, ), }) { - channelStore = eg.store(); + store = eg.store(initialSnapshot: eg.initialSnapshot(unreadMsgs: initial)); notifiedCount = 0; - model = Unreads(initial: initial, - selfUserId: eg.selfUser.userId, channelStore: channelStore) + model = store.unreads ..addListener(() { notifiedCount++; }); @@ -157,11 +156,11 @@ void main() { final stream2 = eg.stream(); final stream3 = eg.stream(); prepare(); - await channelStore.addStreams([stream1, stream2, stream3]); - await channelStore.addSubscription(eg.subscription(stream1)); - await channelStore.addSubscription(eg.subscription(stream2)); - await channelStore.addSubscription(eg.subscription(stream3, isMuted: true)); - await channelStore.addUserTopic(stream1, 'a', UserTopicVisibilityPolicy.muted); + await store.addStreams([stream1, stream2, stream3]); + await store.addSubscription(eg.subscription(stream1)); + await store.addSubscription(eg.subscription(stream2)); + await store.addSubscription(eg.subscription(stream3, isMuted: true)); + await store.addUserTopic(stream1, 'a', UserTopicVisibilityPolicy.muted); fillWithMessages([ eg.streamMessage(stream: stream1, topic: 'a', flags: []), eg.streamMessage(stream: stream1, topic: 'b', flags: []), @@ -177,10 +176,10 @@ void main() { test('countInChannel/Narrow', () async { final stream = eg.stream(); prepare(); - await channelStore.addStream(stream); - await channelStore.addSubscription(eg.subscription(stream)); - await channelStore.addUserTopic(stream, 'a', UserTopicVisibilityPolicy.unmuted); - await channelStore.addUserTopic(stream, 'c', UserTopicVisibilityPolicy.muted); + await store.addStream(stream); + await store.addSubscription(eg.subscription(stream)); + await store.addUserTopic(stream, 'a', UserTopicVisibilityPolicy.unmuted); + await store.addUserTopic(stream, 'c', UserTopicVisibilityPolicy.muted); fillWithMessages([ eg.streamMessage(stream: stream, topic: 'a', flags: []), eg.streamMessage(stream: stream, topic: 'a', flags: []), @@ -192,7 +191,7 @@ void main() { check(model.countInChannel (stream.streamId)).equals(5); check(model.countInChannelNarrow(stream.streamId)).equals(5); - await channelStore.handleEvent(SubscriptionUpdateEvent(id: 1, + await store.handleEvent(SubscriptionUpdateEvent(id: 1, streamId: stream.streamId, property: SubscriptionProperty.isMuted, value: true)); check(model.countInChannel (stream.streamId)).equals(2); @@ -219,7 +218,7 @@ void main() { test('countInMentionsNarrow', () async { final stream = eg.stream(); prepare(); - await channelStore.addStream(stream); + await store.addStream(stream); fillWithMessages([ eg.streamMessage(stream: stream, flags: []), eg.streamMessage(stream: stream, flags: [MessageFlag.mentioned]), @@ -231,7 +230,7 @@ void main() { test('countInStarredMessagesNarrow', () async { final stream = eg.stream(); prepare(); - await channelStore.addStream(stream); + await store.addStream(stream); fillWithMessages([ eg.streamMessage(stream: stream, flags: []), eg.streamMessage(stream: stream, flags: [MessageFlag.starred]), @@ -480,8 +479,8 @@ void main() { Future prepareStore() async { prepare(); - await channelStore.addStream(origChannel); - await channelStore.addSubscription(eg.subscription(origChannel)); + await store.addStream(origChannel); + await store.addSubscription(eg.subscription(origChannel)); readMessages = List.generate(10, (_) => eg.streamMessage(stream: origChannel, topic: origTopic, flags: [MessageFlag.read])); @@ -504,8 +503,8 @@ void main() { test('moved messages = unread messages', () async { await prepareStore(); final newChannel = eg.stream(); - await channelStore.addStream(newChannel); - await channelStore.addSubscription(eg.subscription(newChannel)); + await store.addStream(newChannel); + await store.addSubscription(eg.subscription(newChannel)); fillWithMessages(unreadMessages); final originalMessageIds = model.streams[origChannel.streamId]![TopicName(origTopic)]!; @@ -586,8 +585,8 @@ void main() { test('moving to unsubscribed channels drops the unreads', () async { await prepareStore(); final unsubscribedChannel = eg.stream(); - await channelStore.addStream(unsubscribedChannel); - assert(!channelStore.subscriptions.containsKey( + await store.addStream(unsubscribedChannel); + assert(!store.subscriptions.containsKey( unsubscribedChannel.streamId)); fillWithMessages(unreadMessages); @@ -617,7 +616,7 @@ void main() { fillWithMessages(unreadMessages); final unknownChannel = eg.stream(); - assert(!channelStore.streams.containsKey(unknownChannel.streamId)); + assert(!store.streams.containsKey(unknownChannel.streamId)); final unknownUnreadMessage = eg.streamMessage( stream: unknownChannel, topic: origTopic);