diff --git a/lib/api/model/events.dart b/lib/api/model/events.dart index c3a2eb0126..f35c81880a 100644 --- a/lib/api/model/events.dart +++ b/lib/api/model/events.dart @@ -185,6 +185,7 @@ class UserSettingsUpdateEvent extends Event { switch (UserSettingName.fromRawString(json['property'] as String)) { case UserSettingName.twentyFourHourTime: return TwentyFourHourTimeMode.fromApiValue(value as bool?); + case UserSettingName.starredMessageCounts: case UserSettingName.displayEmojiReactionUsers: return value as bool; case UserSettingName.emojiset: diff --git a/lib/api/model/events.g.dart b/lib/api/model/events.g.dart index fc0c3a95e2..76886dbfc2 100644 --- a/lib/api/model/events.g.dart +++ b/lib/api/model/events.g.dart @@ -65,6 +65,7 @@ Map _$UserSettingsUpdateEventToJson( const _$UserSettingNameEnumMap = { UserSettingName.twentyFourHourTime: 'twenty_four_hour_time', + UserSettingName.starredMessageCounts: 'starred_message_counts', UserSettingName.displayEmojiReactionUsers: 'display_emoji_reaction_users', UserSettingName.emojiset: 'emojiset', UserSettingName.presenceEnabled: 'presence_enabled', diff --git a/lib/api/model/initial_snapshot.dart b/lib/api/model/initial_snapshot.dart index 2f0257eaac..53abb98697 100644 --- a/lib/api/model/initial_snapshot.dart +++ b/lib/api/model/initial_snapshot.dart @@ -60,6 +60,8 @@ class InitialSnapshot { final UnreadMessagesSnapshot unreadMsgs; + final List starredMessages; + final List streams; // In register-queue, the name of this field is the singular "user_status", @@ -178,6 +180,7 @@ class InitialSnapshot { required this.subscriptions, required this.channelFolders, required this.unreadMsgs, + required this.starredMessages, required this.streams, required this.userStatuses, required this.userSettings, @@ -297,6 +300,7 @@ class UserSettings { ) TwentyFourHourTimeMode twentyFourHourTime; + bool starredMessageCounts; bool displayEmojiReactionUsers; @JsonKey(unknownEnumValue: Emojiset.unknown) Emojiset emojiset; @@ -310,6 +314,7 @@ class UserSettings { UserSettings({ required this.twentyFourHourTime, + required this.starredMessageCounts, required this.displayEmojiReactionUsers, required this.emojiset, required this.presenceEnabled, diff --git a/lib/api/model/initial_snapshot.g.dart b/lib/api/model/initial_snapshot.g.dart index dac7c141f7..a0a4cb8a39 100644 --- a/lib/api/model/initial_snapshot.g.dart +++ b/lib/api/model/initial_snapshot.g.dart @@ -71,6 +71,9 @@ InitialSnapshot _$InitialSnapshotFromJson( unreadMsgs: UnreadMessagesSnapshot.fromJson( json['unread_msgs'] as Map, ), + starredMessages: (json['starred_messages'] as List) + .map((e) => (e as num).toInt()) + .toList(), streams: (json['streams'] as List) .map((e) => ZulipStream.fromJson(e as Map)) .toList(), @@ -175,6 +178,7 @@ Map _$InitialSnapshotToJson( 'subscriptions': instance.subscriptions, 'channel_folders': instance.channelFolders, 'unread_msgs': instance.unreadMsgs, + 'starred_messages': instance.starredMessages, 'streams': instance.streams, 'user_status': instance.userStatuses.map((k, e) => MapEntry(k.toString(), e)), 'user_settings': instance.userSettings, @@ -258,6 +262,7 @@ UserSettings _$UserSettingsFromJson(Map json) => UserSettings( twentyFourHourTime: TwentyFourHourTimeMode.fromApiValue( json['twenty_four_hour_time'] as bool?, ), + starredMessageCounts: json['starred_message_counts'] as bool, displayEmojiReactionUsers: json['display_emoji_reaction_users'] as bool, emojiset: $enumDecode( _$EmojisetEnumMap, @@ -269,6 +274,7 @@ UserSettings _$UserSettingsFromJson(Map json) => UserSettings( const _$UserSettingsFieldMap = { 'twentyFourHourTime': 'twenty_four_hour_time', + 'starredMessageCounts': 'starred_message_counts', 'displayEmojiReactionUsers': 'display_emoji_reaction_users', 'emojiset': 'emojiset', 'presenceEnabled': 'presence_enabled', @@ -279,6 +285,7 @@ Map _$UserSettingsToJson(UserSettings instance) => 'twenty_four_hour_time': TwentyFourHourTimeMode.staticToJson( instance.twentyFourHourTime, ), + 'starred_message_counts': instance.starredMessageCounts, 'display_emoji_reaction_users': instance.displayEmojiReactionUsers, 'emojiset': instance.emojiset, 'presence_enabled': instance.presenceEnabled, diff --git a/lib/api/model/model.dart b/lib/api/model/model.dart index c7ae2fb6ac..3e23a0fdd3 100644 --- a/lib/api/model/model.dart +++ b/lib/api/model/model.dart @@ -337,6 +337,7 @@ class UserStatusChange { @JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true) enum UserSettingName { twentyFourHourTime, + starredMessageCounts, displayEmojiReactionUsers, emojiset, presenceEnabled, diff --git a/lib/api/model/model.g.dart b/lib/api/model/model.g.dart index 95c5588392..56ebde9991 100644 --- a/lib/api/model/model.g.dart +++ b/lib/api/model/model.g.dart @@ -527,6 +527,7 @@ Map _$DmMessageToJson(DmMessage instance) => { const _$UserSettingNameEnumMap = { UserSettingName.twentyFourHourTime: 'twenty_four_hour_time', + UserSettingName.starredMessageCounts: 'starred_message_counts', UserSettingName.displayEmojiReactionUsers: 'display_emoji_reaction_users', UserSettingName.emojiset: 'emojiset', UserSettingName.presenceEnabled: 'presence_enabled', diff --git a/lib/api/route/settings.dart b/lib/api/route/settings.dart index 4e98140d76..1c184cc226 100644 --- a/lib/api/route/settings.dart +++ b/lib/api/route/settings.dart @@ -16,6 +16,7 @@ Future updateSettings(ApiConnection connection, { // TODO(server-future) allow localeDefault for servers that support it assert(mode != TwentyFourHourTimeMode.localeDefault); value = mode.toJson(); + case UserSettingName.starredMessageCounts: case UserSettingName.displayEmojiReactionUsers: value = valueRaw as bool; case UserSettingName.emojiset: diff --git a/lib/model/message.dart b/lib/model/message.dart index 16f015416b..d7eb9267f5 100644 --- a/lib/model/message.dart +++ b/lib/model/message.dart @@ -24,6 +24,9 @@ mixin MessageStore on ChannelStore { /// All known messages, indexed by [Message.id]. Map get messages; + /// All starred messages, as message IDs. + Set get starredMessages; + /// [OutboxMessage]s sent by the user, indexed by [OutboxMessage.localMessageId]. Map get outboxMessages; @@ -208,6 +211,8 @@ mixin ProxyMessageStore on MessageStore { @override Map get messages => messageStore.messages; @override + Set get starredMessages => messageStore.starredMessages; + @override Map get outboxMessages => messageStore.outboxMessages; @override void registerMessageList(MessageListView view) => @@ -261,14 +266,19 @@ class _EditMessageRequestStatus { } class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessageStore { - MessageStoreImpl({required super.channels}) - : // There are no messages in InitialSnapshot, so we don't have - // a use case for initializing MessageStore with nonempty [messages]. - messages = {}; + MessageStoreImpl({ + required super.channels, + required List initialStarredMessages, + }) : + messages = {}, + starredMessages = Set.of(initialStarredMessages); @override final Map messages; + @override + final Set starredMessages; + @override final Set _messageListViews = {}; @@ -410,6 +420,17 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage assert(!_disposed); for (int i = 0; i < messages.length; i++) { final message = messages[i]; + + // TODO(#649) update Unreads, if [Unreads.oldUnreadsMissing] + // TODO(#650) update RecentDmConversationsView + + // It's tempting to update [starredMessages] based on fetched messages, + // like Unreads and RecentDmConversationsView. + // But we don't need to: per the API doc, InitialSnapshot.starredMessages + // is the complete list of starred message IDs. So we can maintain it + // using just the event system, avoiding the fetch/event race + // as a cause of inaccuracies. + messages[i] = this.messages.update(message.id, ifAbsent: () => _reconcileUnrecognizedMessage(message), (current) => _reconcileRecognizedMessage(current, message)); @@ -593,6 +614,13 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage void handleMessageEvent(MessageEvent event) { final message = event.message; + if (message.flags.contains(MessageFlag.starred)) { // TODO(log) + // It would be surprising if a newly-sent message could be starred. + // (I notice that the send-message endpoint doesn't offer a way to + // set the starred flag.) + // If it turns out to be possible, we should update [starredMessages]. + } + // If the message is one we already know about (from a fetch), // clobber it with the one from the event system. // See [reconcileMessages] for reasoning. @@ -716,18 +744,25 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage } } - void handleDeleteMessageEvent(DeleteMessageEvent event) { + /// Handle a [DeleteMessageEvent] + /// and return true if the [PerAccountStore] should notify listeners. + bool handleDeleteMessageEvent(DeleteMessageEvent event) { + bool perAccountStoreShouldNotify = false; for (final messageId in event.messageIds) { messages.remove(messageId); + perAccountStoreShouldNotify |= starredMessages.remove(messageId); _maybeStaleChannelMessages.remove(messageId); _editMessageRequests.remove(messageId); } for (final view in _messageListViews) { view.handleDeleteMessageEvent(event); } + return perAccountStoreShouldNotify; } - void handleUpdateMessageFlagsEvent(UpdateMessageFlagsEvent event) { + /// Handle an [UpdateMessageFlagsEvent] + /// and return true if the [PerAccountStore] should notify listeners. + bool handleUpdateMessageFlagsEvent(UpdateMessageFlagsEvent event) { final isAdd = switch (event) { UpdateMessageFlagsAddEvent() => true, UpdateMessageFlagsRemoveEvent() => false, @@ -765,6 +800,15 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage _notifyMessageListViews(event.messages); } } + + if (event.flag == MessageFlag.starred) { + isAdd + ? starredMessages.addAll(event.messages) + : starredMessages.removeAll(event.messages); + return true; + } + + return false; } void handleReactionEvent(ReactionEvent event) { diff --git a/lib/model/message_list.dart b/lib/model/message_list.dart index 947690c426..800a7ec6a1 100644 --- a/lib/model/message_list.dart +++ b/lib/model/message_list.dart @@ -668,7 +668,10 @@ class MessageListView with ChangeNotifier, _MessageSequence { /// This depends in particular on whether the message is muted in /// one way or another. /// - /// See also [_allMessagesVisible]. + /// See also: + /// - [_allMessagesVisible], message-fetch optimization related to this + /// - Message-list UI that mitigates spam/harrassment + /// by obscuring messages from muted senders, with a "reveal" button bool _messageVisible(MessageBase message) { switch (narrow) { case CombinedFeedNarrow(): @@ -694,7 +697,23 @@ class MessageListView with ChangeNotifier, _MessageSequence { // If changing this, consider whether [Unreads.countInMentionsNarrow] // should be changed correspondingly, so the message-list view matches // the unread-count badge. + if (message.conversation case DmConversation(:final allRecipientIds)) { + return !store.shouldMuteDmConversation(DmNarrow( + allRecipientIds: allRecipientIds, selfUserId: store.selfUserId)); + } + return true; + case StarredMessagesNarrow(): + // Include messages even if muted in some way. + // Other users can't spam this view by starring messages; + // the starred state is read/write by the self-user only. + // + // If we want to change this, consider that we need to compute a + // starred-message count without relying on fetched message data: + // https://zulip.com/help/star-a-message#view-your-starred-messages + // ([MessageStore.starredMessages] is just a list of message IDs.) + return true; + case KeywordSearchNarrow(): if (message.conversation case DmConversation(:final allRecipientIds)) { return !store.shouldMuteDmConversation(DmNarrow( @@ -718,7 +737,11 @@ class MessageListView with ChangeNotifier, _MessageSequence { return true; case MentionsNarrow(): + return false; + case StarredMessagesNarrow(): + return true; + case KeywordSearchNarrow(): return false; } diff --git a/lib/model/store.dart b/lib/model/store.dart index b051e75b72..bc646bb815 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -571,7 +571,8 @@ class PerAccountStore extends PerAccountStoreBase with presence: Presence(realm: realm, initial: initialSnapshot.presences), channels: channels, - messages: MessageStoreImpl(channels: channels), + messages: MessageStoreImpl(channels: channels, + initialStarredMessages: initialSnapshot.starredMessages), unreads: Unreads(core: core, channelStore: channels, initial: initialSnapshot.unreadMsgs), recentDmConversationsView: RecentDmConversationsView(core: core, @@ -783,6 +784,8 @@ class PerAccountStore extends PerAccountStoreBase with switch (event.property!) { case UserSettingName.twentyFourHourTime: userSettings.twentyFourHourTime = event.value as TwentyFourHourTimeMode; + case UserSettingName.starredMessageCounts: + userSettings.starredMessageCounts = event.value as bool; case UserSettingName.displayEmojiReactionUsers: userSettings.displayEmojiReactionUsers = event.value as bool; case UserSettingName.emojiset: @@ -889,18 +892,22 @@ class PerAccountStore extends PerAccountStoreBase with case DeleteMessageEvent(): assert(debugLog("server event: delete_message ${event.messageIds}")); + bool shouldNotify = false; // This should be called before [_messages.handleDeleteMessageEvent(event)], // as we need to know about each message for [event.messageIds], // specifically, their `senderId`s. By calling this after the // aforementioned line, we'll lose reference to those messages. recentSenders.handleDeleteMessageEvent(event, messages); - _messages.handleDeleteMessageEvent(event); + shouldNotify |= _messages.handleDeleteMessageEvent(event); unreads.handleDeleteMessageEvent(event); + if (shouldNotify) notifyListeners(); case UpdateMessageFlagsEvent(): assert(debugLog("server event: update_message_flags/${event.op} ${event.flag.toJson()}")); - _messages.handleUpdateMessageFlagsEvent(event); + bool shouldNotify = false; + shouldNotify |= _messages.handleUpdateMessageFlagsEvent(event); unreads.handleUpdateMessageFlagsEvent(event); + if (shouldNotify) notifyListeners(); case SubmessageEvent(): assert(debugLog("server event: submessage ${event.content}")); diff --git a/lib/widgets/unread_count_badge.dart b/lib/widgets/counter_badge.dart similarity index 52% rename from lib/widgets/unread_count_badge.dart rename to lib/widgets/counter_badge.dart index 3208a28fb7..ac0c14cedf 100644 --- a/lib/widgets/unread_count_badge.dart +++ b/lib/widgets/counter_badge.dart @@ -4,31 +4,29 @@ import 'store.dart'; import 'text.dart'; import 'theme.dart'; -/// A widget to display a given number of unreads in a conversation. +/// A widget to display a given number (e.g. of unread messages or of users). /// /// See Figma's "counter-menu" component, which this is based on: /// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=2037-186671&m=dev /// It looks like that component was created for the main menu, /// then adapted for various other contexts, like the Inbox page. -/// See [UnreadCountBadgeStyle]. -/// -/// Currently this widget supports only the component's "kind=unread" variant, -/// not "kind=quantity". -// TODO support the "kind=quantity" variant, update dartdoc -class UnreadCountBadge extends StatelessWidget { - const UnreadCountBadge({ +/// See [CounterBadgeStyle] and [CounterBadgeKind] for the possible variants. +class CounterBadge extends StatelessWidget { + const CounterBadge({ super.key, - this.style = UnreadCountBadgeStyle.other, + this.style = CounterBadgeStyle.other, + required this.kind, required this.count, required this.channelIdForBackground, - }); + }) : assert(!(kind == CounterBadgeKind.quantity && channelIdForBackground != null)); - final UnreadCountBadgeStyle style; + final CounterBadgeStyle style; + final CounterBadgeKind kind; final int count; /// An optional [Subscription.streamId], for a channel-colorized background. /// - /// Useful when this badge represents messages in one specific channel. + /// Useful when this counter represents unreads in one specific channel. /// /// If null, the default neutral background will be used. // TODO remove; the Figma doesn't use this anymore. @@ -48,40 +46,54 @@ class UnreadCountBadge extends StatelessWidget { final swatch = colorSwatchFor(context, subscription); backgroundColor = swatch.unreadCountBadgeBackground; } else { - textColor = designVariables.labelCounterUnread; + textColor = switch (kind) { + CounterBadgeKind.unread => designVariables.labelCounterUnread, + CounterBadgeKind.quantity => designVariables.labelCounterQuantity, + }; backgroundColor = designVariables.bgCounterUnread; } final padding = switch (style) { - UnreadCountBadgeStyle.mainMenu => + CounterBadgeStyle.mainMenu => const EdgeInsets.symmetric(horizontal: 5, vertical: 4), - UnreadCountBadgeStyle.other => + CounterBadgeStyle.other => const EdgeInsets.symmetric(horizontal: 5, vertical: 3), }; - final double wght = switch (style) { - UnreadCountBadgeStyle.mainMenu => 600, - UnreadCountBadgeStyle.other => 500, + final double wght = switch ((style, kind)) { + (CounterBadgeStyle.mainMenu, CounterBadgeKind.unread ) => 600, + (CounterBadgeStyle.mainMenu, CounterBadgeKind.quantity) => 500, + (CounterBadgeStyle.other, CounterBadgeKind.unread ) => 500, + (CounterBadgeStyle.other, CounterBadgeKind.quantity) => 500, }; - return DecoratedBox( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: backgroundColor, - ), - child: Padding( - padding: padding, - child: Text( - style: TextStyle( - fontSize: 16, - height: (16 / 16), - color: textColor, - ).merge(weightVariableTextStyle(context, wght: wght)), - count.toString()))); + Widget result = Padding( + padding: padding, + child: Text( + style: TextStyle( + fontSize: 16, + height: (16 / 16), + color: textColor, + ).merge(weightVariableTextStyle(context, wght: wght)), + count.toString())); + + switch (kind) { + case CounterBadgeKind.unread: + result = DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: backgroundColor, + ), + child: result); + case CounterBadgeKind.quantity: + // no decoration + } + + return result; } } -enum UnreadCountBadgeStyle { +enum CounterBadgeStyle { /// The style to use in the main menu. /// /// Figma: @@ -98,6 +110,22 @@ enum UnreadCountBadgeStyle { other, } +enum CounterBadgeKind { + /// The counter counts unread messages. + /// + /// Figma: + /// Main-menu style: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=2037-185125&m=dev + /// Other style: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=6205-26001&m=dev + unread, + + /// The counter counts something else, like users or starred messages. + /// + /// Figma: + /// Main-menu style: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=2037-186672&m=dev + /// Other style: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=6025-293468&m=dev + quantity, +} + class MutedUnreadBadge extends StatelessWidget { const MutedUnreadBadge({super.key}); diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index 5abfa9e632..f84e4ba8fb 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -24,7 +24,7 @@ import 'store.dart'; import 'subscription_list.dart'; import 'text.dart'; import 'theme.dart'; -import 'unread_count_badge.dart'; +import 'counter_badge.dart'; import 'user.dart'; enum _HomePageTab { @@ -635,8 +635,9 @@ class _InboxButton extends _NavigationBarMenuButton { final store = PerAccountStoreWidget.of(context); final unreadCount = store.unreads.countInCombinedFeedNarrow(); if (unreadCount == 0) return null; - return UnreadCountBadge( - style: UnreadCountBadgeStyle.mainMenu, + return CounterBadge( + kind: CounterBadgeKind.unread, + style: CounterBadgeStyle.mainMenu, count: unreadCount, channelIdForBackground: null, ); @@ -662,8 +663,9 @@ class _MentionsButton extends MenuButton { final store = PerAccountStoreWidget.of(context); final unreadCount = store.unreads.countInMentionsNarrow(); if (unreadCount == 0) return null; - return UnreadCountBadge( - style: UnreadCountBadgeStyle.mainMenu, + return CounterBadge( + kind: CounterBadgeKind.unread, + style: CounterBadgeStyle.mainMenu, count: unreadCount, channelIdForBackground: null, ); @@ -687,6 +689,18 @@ class _StarredMessagesButton extends MenuButton { return zulipLocalizations.starredMessagesPageTitle; } + @override + Widget? buildTrailing(BuildContext context) { + final store = PerAccountStoreWidget.of(context); + if (!store.userSettings.starredMessageCounts) return null; + return CounterBadge( + kind: CounterBadgeKind.quantity, + style: CounterBadgeStyle.mainMenu, + count: store.starredMessages.length, + channelIdForBackground: null, + ); + } + @override void onPressed(BuildContext context) { Navigator.of(context).push(MessageListPage.buildRoute( @@ -743,8 +757,9 @@ class _DirectMessagesButton extends _NavigationBarMenuButton { final store = PerAccountStoreWidget.of(context); final unreadCount = store.unreads.countInDms(); if (unreadCount == 0) return null; - return UnreadCountBadge( - style: UnreadCountBadgeStyle.mainMenu, + return CounterBadge( + kind: CounterBadgeKind.unread, + style: CounterBadgeStyle.mainMenu, count: unreadCount, channelIdForBackground: null, ); diff --git a/lib/widgets/inbox.dart b/lib/widgets/inbox.dart index ce7b200327..8d23819a16 100644 --- a/lib/widgets/inbox.dart +++ b/lib/widgets/inbox.dart @@ -13,7 +13,7 @@ import 'sticky_header.dart'; import 'store.dart'; import 'text.dart'; import 'theme.dart'; -import 'unread_count_badge.dart'; +import 'counter_badge.dart'; class InboxPageBody extends StatefulWidget { const InboxPageBody({super.key}); @@ -309,7 +309,9 @@ abstract class _HeaderItem extends StatelessWidget { const SizedBox(width: 12), if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), Padding(padding: const EdgeInsetsDirectional.only(end: 16), - child: UnreadCountBadge( + child: CounterBadge( + // TODO(design) use CounterKind.quantity, following Figma + kind: CounterBadgeKind.unread, channelIdForBackground: channelId, count: count)), ]))); @@ -431,7 +433,10 @@ class _DmItem extends StatelessWidget { const SizedBox(width: 12), if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), Padding(padding: const EdgeInsetsDirectional.only(end: 16), - child: UnreadCountBadge(channelIdForBackground: null, + child: CounterBadge( + // TODO(design) use CounterKind.quantity, following Figma + kind: CounterBadgeKind.unread, + channelIdForBackground: null, count: count)), ])))); } @@ -565,7 +570,9 @@ class _TopicItem extends StatelessWidget { // TODO(design) copies the "@" marker color; is there a better color? if (visibilityIcon != null) _IconMarker(icon: visibilityIcon), Padding(padding: const EdgeInsetsDirectional.only(end: 16), - child: UnreadCountBadge( + child: CounterBadge( + // TODO(design) use CounterKind.quantity, following Figma + kind: CounterBadgeKind.unread, channelIdForBackground: streamId, count: count)), ])))); diff --git a/lib/widgets/recent_dm_conversations.dart b/lib/widgets/recent_dm_conversations.dart index 36f6a40aab..7db0245f00 100644 --- a/lib/widgets/recent_dm_conversations.dart +++ b/lib/widgets/recent_dm_conversations.dart @@ -11,7 +11,7 @@ import 'page.dart'; import 'store.dart'; import 'text.dart'; import 'theme.dart'; -import 'unread_count_badge.dart'; +import 'counter_badge.dart'; import 'user.dart'; typedef OnDmSelectCallback = void Function(DmNarrow narrow); @@ -234,7 +234,9 @@ class RecentDmConversationsItem extends StatelessWidget { const SizedBox(width: 12), unreadCount > 0 ? Padding(padding: const EdgeInsetsDirectional.only(end: 16), - child: UnreadCountBadge(channelIdForBackground: null, + child: CounterBadge( + kind: CounterBadgeKind.unread, + channelIdForBackground: null, count: unreadCount)) : const SizedBox(), ])))); diff --git a/lib/widgets/subscription_list.dart b/lib/widgets/subscription_list.dart index 03714c734a..5143ab6c6f 100644 --- a/lib/widgets/subscription_list.dart +++ b/lib/widgets/subscription_list.dart @@ -14,7 +14,7 @@ import 'page.dart'; import 'store.dart'; import 'text.dart'; import 'theme.dart'; -import 'unread_count_badge.dart'; +import 'counter_badge.dart'; typedef OnChannelSelectCallback = void Function(ChannelNarrow narrow); @@ -336,7 +336,8 @@ class SubscriptionItem extends StatelessWidget { // TODO(#747) show @-mention indicator when it applies Opacity( opacity: opacity, - child: UnreadCountBadge( + child: CounterBadge( + kind: CounterBadgeKind.unread, count: unreadCount, channelIdForBackground: subscription.streamId)), ] else if (showMutedUnreadBadge) ...[ diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index cb95c78c00..7799890f3d 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -184,6 +184,7 @@ class DesignVariables extends ThemeExtension { foreground: const Color(0xff000000), icon: const Color(0xff6159e1), iconSelected: const Color(0xff222222), + labelCounterQuantity: const Color(0xff222222).withValues(alpha: 0.6), labelCounterUnread: const Color(0xff1a1a1a), labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 0).toColor(), labelMenuButton: const Color(0xff222222), @@ -285,6 +286,7 @@ class DesignVariables extends ThemeExtension { foreground: const Color(0xffffffff), icon: const Color(0xff7977fe), iconSelected: Colors.white.withValues(alpha: 0.8), + labelCounterQuantity: const Color(0xffffffff).withValues(alpha: 0.7), labelCounterUnread: const Color(0xffffffff).withValues(alpha: 0.95), labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 1).toColor(), labelMenuButton: const Color(0xffffffff).withValues(alpha: 0.85), @@ -395,6 +397,7 @@ class DesignVariables extends ThemeExtension { required this.fabShadow, required this.icon, required this.iconSelected, + required this.labelCounterQuantity, required this.labelCounterUnread, required this.labelEdited, required this.labelMenuButton, @@ -496,6 +499,7 @@ class DesignVariables extends ThemeExtension { final Color foreground; final Color icon; final Color iconSelected; + final Color labelCounterQuantity; final Color labelCounterUnread; final Color labelEdited; final Color labelMenuButton; @@ -592,6 +596,7 @@ class DesignVariables extends ThemeExtension { Color? foreground, Color? icon, Color? iconSelected, + Color? labelCounterQuantity, Color? labelCounterUnread, Color? labelEdited, Color? labelMenuButton, @@ -683,6 +688,7 @@ class DesignVariables extends ThemeExtension { fabShadow: fabShadow ?? this.fabShadow, icon: icon ?? this.icon, iconSelected: iconSelected ?? this.iconSelected, + labelCounterQuantity: labelCounterQuantity ?? this.labelCounterQuantity, labelCounterUnread: labelCounterUnread ?? this.labelCounterUnread, labelEdited: labelEdited ?? this.labelEdited, labelMenuButton: labelMenuButton ?? this.labelMenuButton, @@ -781,6 +787,7 @@ class DesignVariables extends ThemeExtension { fabShadow: Color.lerp(fabShadow, other.fabShadow, t)!, icon: Color.lerp(icon, other.icon, t)!, iconSelected: Color.lerp(iconSelected, other.iconSelected, t)!, + labelCounterQuantity: Color.lerp(labelCounterQuantity, other.labelCounterQuantity, t)!, labelCounterUnread: Color.lerp(labelCounterUnread, other.labelCounterUnread, t)!, labelEdited: Color.lerp(labelEdited, other.labelEdited, t)!, labelMenuButton: Color.lerp(labelMenuButton, other.labelMenuButton, t)!, diff --git a/lib/widgets/topic_list.dart b/lib/widgets/topic_list.dart index aad7866f8c..2e6ccc2fdd 100644 --- a/lib/widgets/topic_list.dart +++ b/lib/widgets/topic_list.dart @@ -14,7 +14,7 @@ import 'page.dart'; import 'store.dart'; import 'text.dart'; import 'theme.dart'; -import 'unread_count_badge.dart'; +import 'counter_badge.dart'; class TopicListPage extends StatelessWidget { const TopicListPage({super.key, required this.streamId}); @@ -307,7 +307,8 @@ class _TopicItem extends StatelessWidget { if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), if (visibilityIcon != null) _IconMarker(icon: visibilityIcon), if (unreadCount > 0) - UnreadCountBadge( + CounterBadge( + kind: CounterBadgeKind.unread, count: unreadCount, channelIdForBackground: null), ])), diff --git a/test/api/route/settings_test.dart b/test/api/route/settings_test.dart index 8b31caa646..bc2a46a985 100644 --- a/test/api/route/settings_test.dart +++ b/test/api/route/settings_test.dart @@ -19,6 +19,9 @@ void main() { case UserSettingName.twentyFourHourTime: newSettings[name] = TwentyFourHourTimeMode.twelveHour; expectedBodyFields['twenty_four_hour_time'] = 'false'; + case UserSettingName.starredMessageCounts: + newSettings[name] = false; + expectedBodyFields['starred_message_counts'] = 'false'; case UserSettingName.displayEmojiReactionUsers: newSettings[name] = false; expectedBodyFields['display_emoji_reaction_users'] = 'false'; diff --git a/test/example_data.dart b/test/example_data.dart index c9beaef72f..8b029b5fa7 100644 --- a/test/example_data.dart +++ b/test/example_data.dart @@ -1317,6 +1317,7 @@ UserSettings userSettings({ }) { return UserSettings( twentyFourHourTime: twentyFourHourTime ?? TwentyFourHourTimeMode.twelveHour, + starredMessageCounts: true, displayEmojiReactionUsers: displayEmojiReactionUsers ?? true, emojiset: emojiset ?? Emojiset.google, presenceEnabled: presenceEnabled ?? true, @@ -1348,6 +1349,7 @@ InitialSnapshot initialSnapshot({ List? subscriptions, List? channelFolders, UnreadMessagesSnapshot? unreadMsgs, + List? starredMessages, List? streams, Map? userStatuses, UserSettings? userSettings, @@ -1407,6 +1409,7 @@ InitialSnapshot initialSnapshot({ subscriptions: subscriptions ?? [], // TODO add subscriptions to default channelFolders: channelFolders ?? [], unreadMsgs: unreadMsgs ?? _unreadMsgs(), + starredMessages: starredMessages ?? [], streams: streams ?? [], // TODO add streams to default userStatuses: userStatuses ?? {}, userSettings: userSettings ?? _userSettings(), diff --git a/test/model/message_list_test.dart b/test/model/message_list_test.dart index 7a75647d9d..049ccac7a3 100644 --- a/test/model/message_list_test.dart +++ b/test/model/message_list_test.dart @@ -81,13 +81,14 @@ void main() { Future prepare({ Narrow narrow = const CombinedFeedNarrow(), Anchor anchor = AnchorCode.newest, + List? starredMessages, ZulipStream? stream, List? users, List? mutedUserIds, }) async { stream ??= eg.stream(streamId: eg.defaultStreamMessageStreamId); subscription = eg.subscription(stream); - store = eg.store(); + store = eg.store(initialSnapshot: eg.initialSnapshot(starredMessages: starredMessages)); await store.addStream(stream); await store.addSubscription(subscription); await store.addUsers([...?users, eg.selfUser]); @@ -1238,7 +1239,10 @@ void main() { }); test('StarredMessagesNarrow', () async { - await prepare(narrow: StarredMessagesNarrow(), users: users); + await prepare( + narrow: StarredMessagesNarrow(), + starredMessages: [1, 2, 3], + users: users); await prepareMessages(foundOldest: true, messages: [ eg.dmMessage(id: 1, from: eg.selfUser, to: [user1], flags: [MessageFlag.starred]), @@ -2202,12 +2206,12 @@ void main() { await checkApplied( mkEvent: (message) => UpdateMessageFlagsAddEvent( id: 1, - flag: MessageFlag.starred, + flag: MessageFlag.hasAlertWord, messages: [message.id], all: false, ), doCheckMessageAfterFetch: - (messageSubject) => messageSubject.flags.contains(MessageFlag.starred), + (messageSubject) => messageSubject.flags.contains(MessageFlag.hasAlertWord), ); }); }); @@ -2447,7 +2451,10 @@ void main() { test('in StarredMessagesNarrow', () async { final stream = eg.stream(streamId: 1, name: 'muted stream'); const mutedTopic = 'muted'; - await prepare(narrow: const StarredMessagesNarrow()); + await prepare( + narrow: const StarredMessagesNarrow(), + starredMessages: [101, 102, 201, 202, 301, 302], + ); await store.addStream(stream); await store.setUserTopic(stream, mutedTopic, UserTopicVisibilityPolicy.muted); await store.addSubscription(eg.subscription(stream, isMuted: true)); @@ -2939,6 +2946,7 @@ void main() { await prepare( narrow: narrow, + starredMessages: [message1.id, message2.id], stream: channel, ); connection.prepare(json: newestResult( @@ -3253,12 +3261,12 @@ void checkInvariants(MessageListView model) { switch (model.narrow) { case CombinedFeedNarrow(): case MentionsNarrow(): - case StarredMessagesNarrow(): case KeywordSearchNarrow(): check(model.store.shouldMuteDmConversation(narrow)).isFalse(); case ChannelNarrow(): case TopicNarrow(): case DmNarrow(): + case StarredMessagesNarrow(): } } } diff --git a/test/model/message_test.dart b/test/model/message_test.dart index 2865d598e5..697c97bd2b 100644 --- a/test/model/message_test.dart +++ b/test/model/message_test.dart @@ -38,7 +38,17 @@ void main() { // Each test case calls [prepare] to initialize them. late Subscription? subscription; late PerAccountStore store; + + late int perAccountStoreNotifiedCount; + void checkPerAccountStoreNotified({required int count}) { + check(perAccountStoreNotifiedCount).equals(count); + perAccountStoreNotifiedCount = 0; + } + void checkPerAccountStoreNotNotified() => checkPerAccountStoreNotified(count: 0); + void checkPerAccountStoreNotifiedOnce() => checkPerAccountStoreNotified(count: 1); + late FakeApiConnection connection; + // [messageList] is here only for the sake of checking when it notifies. // For anything deeper than that, use `message_list_test.dart`. late MessageListView messageList; @@ -55,12 +65,16 @@ void main() { Future prepare({ ZulipStream? stream, bool isChannelSubscribed = true, + List? starredMessages = const [], int? zulipFeatureLevel, }) async { stream ??= eg.stream(streamId: eg.defaultStreamMessageStreamId); final selfAccount = eg.selfAccount.copyWith(zulipFeatureLevel: zulipFeatureLevel); + store = eg.store(account: selfAccount, - initialSnapshot: eg.initialSnapshot(zulipFeatureLevel: zulipFeatureLevel)); + initialSnapshot: eg.initialSnapshot( + starredMessages: starredMessages, + zulipFeatureLevel: zulipFeatureLevel)); await store.addStream(stream); if (isChannelSubscribed) { subscription = eg.subscription(stream); @@ -68,7 +82,14 @@ void main() { } else { subscription = null; } + perAccountStoreNotifiedCount = 0; + store.addListener(() { + perAccountStoreNotifiedCount++; + }); + checkPerAccountStoreNotNotified(); + connection = store.connection as FakeApiConnection; + notifiedCount = 0; messageList = MessageListView.init(store: store, narrow: const CombinedFeedNarrow(), @@ -1440,7 +1461,7 @@ void main() { final originalMessage = eg.streamMessage( content: "

Hello, world

"); final updateEvent = eg.updateMessageEditEvent(originalMessage, - flags: [MessageFlag.starred], + flags: [MessageFlag.hasAlertWord], renderedContent: "

Hello, edited

", editTimestamp: 99999, isMeMessage: true, @@ -1637,6 +1658,20 @@ void main() { checkNotifiedOnce(); check(store).messages.values.single.id.equals(message1.id); }); + + test('delete a starred message', () async { + final message = eg.streamMessage(flags: [MessageFlag.starred]); + await prepare(starredMessages: [message.id]); + + // The actual message hasn't been fetched by a message list; + // we want to test [MessageStore.starredMessages] in isolation. + await prepareMessages([]); + + check(store).starredMessages.single.equals(message.id); + await store.handleEvent(eg.deleteMessageEvent([message])); + checkPerAccountStoreNotifiedOnce(); + check(store).starredMessages.isEmpty(); + }); }); group('handleUpdateMessageFlagsEvent', () { @@ -1698,13 +1733,25 @@ void main() { test('other flags not clobbered', () async { final message = eg.streamMessage(flags: [MessageFlag.starred]); - await prepare(); + await prepare(starredMessages: [message.id]); await prepareMessages([message]); await store.handleEvent(mkAddEvent(MessageFlag.read, [message.id])); checkNotifiedOnce(); check(store).messages.values .single.flags.deepEquals([MessageFlag.starred, MessageFlag.read]); }); + + test('add to starredMessages', () async { + final message1 = eg.streamMessage(flags: []); + final message2 = eg.streamMessage(flags: []); + await prepare(starredMessages: []); + await prepareMessages([message2]); + check(store).starredMessages.isEmpty(); + await store.handleEvent( + mkAddEvent(MessageFlag.starred, [message1.id, message2.id])); + checkPerAccountStoreNotifiedOnce(); + check(store).starredMessages.deepEquals([message1.id, message2.id]); + }); }); group('remove flag', () { @@ -1733,13 +1780,25 @@ void main() { test('other flags not affected', () async { final message = eg.streamMessage(flags: [MessageFlag.starred, MessageFlag.read]); - await prepare(); + await prepare(starredMessages: [message.id]); await prepareMessages([message]); await store.handleEvent(mkRemoveEvent(MessageFlag.read, [message])); checkNotifiedOnce(); check(store).messages.values .single.flags.deepEquals([MessageFlag.starred]); }); + + test('remove from starredMessages', () async { + final message1 = eg.streamMessage(flags: [MessageFlag.starred]); + final message2 = eg.streamMessage(flags: [MessageFlag.starred]); + await prepare(starredMessages: [message1.id, message2.id]); + await prepareMessages([message2]); + check(store).starredMessages.deepEquals([message1.id, message2.id]); + await store.handleEvent( + mkRemoveEvent(MessageFlag.starred, [message1, message2])); + checkPerAccountStoreNotifiedOnce(); + check(store).starredMessages.isEmpty(); + }); }); }); diff --git a/test/model/store_checks.dart b/test/model/store_checks.dart index dac078a434..7d38e8160c 100644 --- a/test/model/store_checks.dart +++ b/test/model/store_checks.dart @@ -68,6 +68,7 @@ extension PerAccountStoreChecks on Subject { Subject> get streamsByName => has((x) => x.streamsByName, 'streamsByName'); Subject> get subscriptions => has((x) => x.subscriptions, 'subscriptions'); Subject> get messages => has((x) => x.messages, 'messages'); + Subject> get starredMessages => has((x) => x.starredMessages, 'starredMessages'); Subject get unreads => has((x) => x.unreads, 'unreads'); Subject get recentDmConversationsView => has((x) => x.recentDmConversationsView, 'recentDmConversationsView'); Subject get autocompleteViewManager => has((x) => x.autocompleteViewManager, 'autocompleteViewManager'); diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index c6910473dd..70b5627690 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -82,6 +82,9 @@ Future setupToMessageActionSheet(WidgetTester tester, { await testBinding.globalStore.add( selfAccount, eg.initialSnapshot( + starredMessages: [ + if (message.flags.contains(MessageFlag.starred)) message.id, + ], realmUsers: [selfUser], realmAllowMessageEditing: realmAllowMessageEditing, realmMessageContentEditLimitSeconds: realmMessageContentEditLimitSeconds, @@ -1846,7 +1849,9 @@ void main() { testWidgets('not offered in StarredMessagesNarrow (composing to reply is not yet supported)', (tester) async { final message = eg.streamMessage(flags: [MessageFlag.starred]); - await setupToMessageActionSheet(tester, message: message, narrow: const StarredMessagesNarrow()); + await setupToMessageActionSheet(tester, + message: message, + narrow: const StarredMessagesNarrow()); check(findQuoteAndReplyButton(tester)).isNull(); }); diff --git a/test/widgets/checks.dart b/test/widgets/checks.dart index ef3f9b634f..3c686016d7 100644 --- a/test/widgets/checks.dart +++ b/test/widgets/checks.dart @@ -17,7 +17,7 @@ import 'package:zulip/widgets/message_list.dart'; import 'package:zulip/widgets/page.dart'; import 'package:zulip/widgets/profile.dart'; import 'package:zulip/widgets/store.dart'; -import 'package:zulip/widgets/unread_count_badge.dart'; +import 'package:zulip/widgets/counter_badge.dart'; import 'package:zulip/widgets/user.dart'; extension ChannelColorSwatchChecks on Subject { @@ -92,7 +92,7 @@ extension PerAccountStoreWidgetChecks on Subject { Subject get child => has((x) => x.child, 'child'); } -extension UnreadCountBadgeChecks on Subject { +extension UnreadCountBadgeChecks on Subject { Subject get count => has((b) => b.count, 'count'); Subject get channelIdForBackground => has((b) => b.channelIdForBackground, 'channelIdForBackground'); } diff --git a/test/widgets/unread_count_badge_test.dart b/test/widgets/counter_badge_test.dart similarity index 84% rename from test/widgets/unread_count_badge_test.dart rename to test/widgets/counter_badge_test.dart index 739146050a..42c55f0e68 100644 --- a/test/widgets/unread_count_badge_test.dart +++ b/test/widgets/counter_badge_test.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zulip/api/model/model.dart'; import 'package:zulip/widgets/channel_colors.dart'; -import 'package:zulip/widgets/unread_count_badge.dart'; +import 'package:zulip/widgets/counter_badge.dart'; import '../example_data.dart' as eg; import '../model/binding.dart'; @@ -36,7 +36,10 @@ void main() { testWidgets('smoke test; no crash', (tester) async { await prepare(tester, - child: const UnreadCountBadge(count: 1, channelIdForBackground: null)); + child: const CounterBadge( + kind: CounterBadgeKind.unread, + count: 1, + channelIdForBackground: null)); tester.widget(find.text("1")); }); @@ -49,7 +52,10 @@ void main() { testWidgets('default color', (tester) async { await prepare(tester, - child: UnreadCountBadge(count: 1, channelIdForBackground: null)); + child: CounterBadge( + kind: CounterBadgeKind.unread, + count: 1, + channelIdForBackground: null)); check(findBackgroundColor(tester)).isNotNull().isSameColorAs(const Color(0x26666699)); }); @@ -57,7 +63,8 @@ void main() { final subscription = eg.subscription(eg.stream(), color: 0xff76ce90); await prepare(tester, subscription: subscription, - child: UnreadCountBadge( + child: CounterBadge( + kind: CounterBadgeKind.unread, count: 1, channelIdForBackground: subscription.streamId)); check(findBackgroundColor(tester)).isNotNull() diff --git a/test/widgets/inbox_test.dart b/test/widgets/inbox_test.dart index 63aad6292c..294770732a 100644 --- a/test/widgets/inbox_test.dart +++ b/test/widgets/inbox_test.dart @@ -10,7 +10,7 @@ import 'package:zulip/widgets/home.dart'; import 'package:zulip/widgets/icons.dart'; import 'package:zulip/widgets/channel_colors.dart'; import 'package:zulip/widgets/theme.dart'; -import 'package:zulip/widgets/unread_count_badge.dart'; +import 'package:zulip/widgets/counter_badge.dart'; import '../example_data.dart' as eg; import '../flutter_checks.dart'; @@ -222,7 +222,7 @@ void main() { find.descendant( of: find.byWidget(findRowByLabel(tester, channel.name)!), matching: find.descendant( - of: find.byType(UnreadCountBadge), + of: find.byType(CounterBadge), matching: find.text('1')))); final expectedTextColor = DesignVariables.light.unreadCountBadgeTextForChannel; @@ -406,19 +406,19 @@ void main() { check(find.descendant( of: find.byWidget(findRowByLabel(tester, 'aaa')!), - matching: find.widgetWithText(UnreadCountBadge, '1'))).findsOne(); + matching: find.widgetWithText(CounterBadge, '1'))).findsOne(); await store.handleEvent(eg.updateMessageFlagsRemoveEvent(MessageFlag.read, [message2])); await tester.pump(); check(find.descendant( of: find.byWidget(findRowByLabel(tester, 'aaa')!), - matching: find.widgetWithText(UnreadCountBadge, '2'))).findsOne(); + matching: find.widgetWithText(CounterBadge, '2'))).findsOne(); await store.handleEvent(eg.updateMessageFlagsRemoveEvent(MessageFlag.read, [message3])); await tester.pump(); check(find.descendant( of: find.byWidget(findRowByLabel(tester, 'aaa')!), - matching: find.widgetWithText(UnreadCountBadge, '3'))).findsOne(); + matching: find.widgetWithText(CounterBadge, '3'))).findsOne(); }); }); diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart index 47d46a6bbe..95c2483bb9 100644 --- a/test/widgets/message_list_test.dart +++ b/test/widgets/message_list_test.dart @@ -71,6 +71,7 @@ void main() { List? mutedUserIds, List? subscriptions, UnreadMessagesSnapshot? unreadMsgs, + List? starredMessages, int? zulipFeatureLevel, List navObservers = const [], bool skipAssertAccountExists = false, @@ -84,7 +85,10 @@ void main() { final selfAccount = eg.selfAccount.copyWith(zulipFeatureLevel: zulipFeatureLevel); await testBinding.globalStore.add(selfAccount, eg.initialSnapshot( zulipFeatureLevel: zulipFeatureLevel, - streams: streams, subscriptions: subscriptions, unreadMsgs: unreadMsgs)); + streams: streams, + subscriptions: subscriptions, + unreadMsgs: unreadMsgs, + starredMessages: starredMessages)); store = await testBinding.globalStore.perAccount(selfAccount.id); connection = store.connection as FakeApiConnection; @@ -2230,11 +2234,16 @@ void main() { final navObserver = TestNavigatorObserver() ..onPushed = ((route, prevRoute) => lastPushedRoute = route); + final messages = [message]; + await setupMessageListPage( tester, narrow: narrow, - messages: [message], + messages: messages, subscriptions: [subscription], + starredMessages: messages + .where((message) => message.flags.contains(MessageFlag.starred)) + .map((message) => message.id).toList(), navObservers: [navObserver] ); lastPushedRoute = null; @@ -2445,13 +2454,15 @@ void main() { group('Starred messages', () { testWidgets('unstarred message', (tester) async { final message = eg.streamMessage(flags: []); - await setupMessageListPage(tester, messages: [message]); + await setupMessageListPage(tester, + messages: [message], starredMessages: []); check(find.byIcon(ZulipIcons.star_filled).evaluate()).isEmpty(); }); testWidgets('starred message', (tester) async { final message = eg.streamMessage(flags: [MessageFlag.starred]); - await setupMessageListPage(tester, messages: [message]); + await setupMessageListPage(tester, + messages: [message], starredMessages: [message.id]); check(find.byIcon(ZulipIcons.star_filled).evaluate()).length.equals(1); }); }); diff --git a/test/widgets/subscription_list_test.dart b/test/widgets/subscription_list_test.dart index 70ede6f70a..29ceaf385a 100644 --- a/test/widgets/subscription_list_test.dart +++ b/test/widgets/subscription_list_test.dart @@ -11,7 +11,7 @@ import 'package:zulip/widgets/icons.dart'; import 'package:zulip/widgets/channel_colors.dart'; import 'package:zulip/widgets/subscription_list.dart'; import 'package:zulip/widgets/text.dart'; -import 'package:zulip/widgets/unread_count_badge.dart'; +import 'package:zulip/widgets/counter_badge.dart'; import '../flutter_checks.dart'; import '../model/binding.dart'; @@ -186,7 +186,7 @@ void main() { await setupStreamListPage(tester, subscriptions: [ eg.subscription(stream), ], unreadMsgs: unreadMsgs); - check(find.byType(UnreadCountBadge).evaluate()).length.equals(1); + check(find.byType(CounterBadge).evaluate()).length.equals(1); check(find.byType(MutedUnreadBadge).evaluate().length).equals(0); }); @@ -206,7 +206,7 @@ void main() { )], unreadMsgs: unreadMsgs); check(tester.widget(find.descendant( - of: find.byType(UnreadCountBadge), matching: find.byType(Text)))) + of: find.byType(CounterBadge), matching: find.byType(Text)))) .data.equals('1'); check(find.byType(MutedUnreadBadge).evaluate().length).equals(0); }); @@ -217,7 +217,7 @@ void main() { await setupStreamListPage(tester, subscriptions: [ eg.subscription(stream), ], unreadMsgs: unreadMsgs); - check(find.byType(UnreadCountBadge).evaluate()).length.equals(0); + check(find.byType(CounterBadge).evaluate()).length.equals(0); check(find.byType(MutedUnreadBadge).evaluate().length).equals(0); }); @@ -274,7 +274,7 @@ void main() { check(tester.widget(find.byIcon(iconDataForStream(stream))).color) .isNotNull().isSameColorAs(swatch.iconOnPlainBackground); - final unreadCountBadgeRenderBox = tester.renderObject(find.byType(UnreadCountBadge)); + final unreadCountBadgeRenderBox = tester.renderObject(find.byType(CounterBadge)); check(unreadCountBadgeRenderBox).legacyMatcher( // `paints` isn't a [Matcher] so we wrap it with `equals`; // awkward but it works