diff --git a/lib/widgets/inbox.dart b/lib/widgets/inbox.dart index 00887b17ff..ce7b200327 100644 --- a/lib/widgets/inbox.dart +++ b/lib/widgets/inbox.dart @@ -311,7 +311,6 @@ abstract class _HeaderItem extends StatelessWidget { Padding(padding: const EdgeInsetsDirectional.only(end: 16), child: UnreadCountBadge( channelIdForBackground: channelId, - bold: true, count: count)), ]))); } diff --git a/lib/widgets/subscription_list.dart b/lib/widgets/subscription_list.dart index 327ac5265a..03714c734a 100644 --- a/lib/widgets/subscription_list.dart +++ b/lib/widgets/subscription_list.dart @@ -338,8 +338,7 @@ class SubscriptionItem extends StatelessWidget { opacity: opacity, child: UnreadCountBadge( count: unreadCount, - channelIdForBackground: subscription.streamId, - bold: true)), + channelIdForBackground: subscription.streamId)), ] else if (showMutedUnreadBadge) ...[ const SizedBox(width: 12), // TODO(#747) show @-mention indicator when it applies diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index a82909bcae..18540139d0 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -184,7 +184,7 @@ class DesignVariables extends ThemeExtension { foreground: const Color(0xff000000), icon: const Color(0xff6159e1), iconSelected: const Color(0xff222222), - labelCounterUnread: const Color(0xff222222), + labelCounterUnread: const Color(0xff1a1a1a), labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 0).toColor(), labelMenuButton: const Color(0xff222222), labelSearchPrompt: const Color(0xff000000).withValues(alpha: 0.5), @@ -285,7 +285,7 @@ class DesignVariables extends ThemeExtension { foreground: const Color(0xffffffff), icon: const Color(0xff7977fe), iconSelected: Colors.white.withValues(alpha: 0.8), - labelCounterUnread: 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), labelSearchPrompt: const Color(0xffffffff).withValues(alpha: 0.5), diff --git a/lib/widgets/topic_list.dart b/lib/widgets/topic_list.dart index 16111fc678..846f2202f7 100644 --- a/lib/widgets/topic_list.dart +++ b/lib/widgets/topic_list.dart @@ -14,6 +14,7 @@ import 'page.dart'; import 'store.dart'; import 'text.dart'; import 'theme.dart'; +import 'unread_count_badge.dart'; class TopicListPage extends StatelessWidget { const TopicListPage({super.key, required this.streamId}); @@ -264,43 +265,50 @@ class _TopicItem extends StatelessWidget { topic: topic, someMessageIdInTopic: maxId), splashFactory: NoSplash.splashFactory, - child: Padding(padding: EdgeInsetsDirectional.fromSTEB(6, 8, 12, 8), - child: Row( - spacing: 8, - // In the Figma design, the text and icons on the topic item row - // are aligned to the start on the cross axis - // (i.e., `align-items: flex-start`). The icons are padded down - // 2px relative to the start, to visibly sit on the baseline. - // To account for scaled text, we align everything on the row - // to [CrossAxisAlignment.center] instead ([Row]'s default), - // like we do for the topic items on the inbox page. - // TODO(#1528): align to baseline (and therefore to first line of - // topic name), but with adjustment for icons - // CZO discussion: - // https://chat.zulip.org/#narrow/channel/243-mobile-team/topic/topic.20list.20item.20alignment/near/2173252 - children: [ - // A null [Icon.icon] makes a blank space. - _IconMarker(icon: topic.isResolved ? ZulipIcons.check : null), - Expanded(child: Opacity( - opacity: opacity, - child: Text( - style: TextStyle( - fontSize: 17, - height: 20 / 17, - fontStyle: topic.displayName == null ? FontStyle.italic : null, - color: designVariables.textMessage, - ), - maxLines: 3, - overflow: TextOverflow.ellipsis, - topic.unresolve().displayName ?? store.realmEmptyTopicDisplayName))), - Opacity(opacity: opacity, child: Row( - spacing: 4, - children: [ - if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), - if (visibilityIcon != null) _IconMarker(icon: visibilityIcon), - if (unreadCount > 0) _UnreadCountBadge(count: unreadCount), - ])), - ])))); + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: 40), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(6, 4, 12, 4), + child: Row( + spacing: 8, + // In the Figma design, the text and icons on the topic item row + // are aligned to the start on the cross axis + // (i.e., `align-items: flex-start`). The icons are padded down + // 2px relative to the start, to visibly sit on the baseline. + // To account for scaled text, we align everything on the row + // to [CrossAxisAlignment.center] instead ([Row]'s default), + // like we do for the topic items on the inbox page. + // TODO(#1528): align to baseline (and therefore to first line of + // topic name), but with adjustment for icons + // CZO discussion: + // https://chat.zulip.org/#narrow/channel/243-mobile-team/topic/topic.20list.20item.20alignment/near/2173252 + children: [ + // A null [Icon.icon] makes a blank space. + _IconMarker(icon: topic.isResolved ? ZulipIcons.check : null), + Expanded(child: Opacity( + opacity: opacity, + child: Text( + style: TextStyle( + fontSize: 17, + height: 20 / 17, + fontStyle: topic.displayName == null ? FontStyle.italic : null, + color: designVariables.textMessage, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + topic.unresolve().displayName ?? store.realmEmptyTopicDisplayName))), + Opacity(opacity: opacity, child: Row( + spacing: 4, + children: [ + if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), + if (visibilityIcon != null) _IconMarker(icon: visibilityIcon), + if (unreadCount > 0) + UnreadCountBadge( + count: unreadCount, + channelIdForBackground: null), + ])), + ])), + ))); } } @@ -320,31 +328,3 @@ class _IconMarker extends StatelessWidget { color: designVariables.textMessage.withFadedAlpha(0.4)); } } - -// This is adapted from [UnreadCountBadge]. -// TODO(#1406) see if we can reuse this in redesign -// TODO(#1527) see if we can reuse this in redesign -class _UnreadCountBadge extends StatelessWidget { - const _UnreadCountBadge({required this.count}); - - final int count; - - @override - Widget build(BuildContext context) { - final designVariables = DesignVariables.of(context); - - return DecoratedBox( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: designVariables.bgCounterUnread, - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), - child: Text(count.toString(), - style: TextStyle( - fontSize: 15, - height: 16 / 15, - color: designVariables.labelCounterUnread, - ).merge(weightVariableTextStyle(context, wght: 500))))); - } -} diff --git a/lib/widgets/unread_count_badge.dart b/lib/widgets/unread_count_badge.dart index b14bd6972d..828d724dc9 100644 --- a/lib/widgets/unread_count_badge.dart +++ b/lib/widgets/unread_count_badge.dart @@ -6,24 +6,35 @@ import 'theme.dart'; /// A widget to display a given number of unreads in a conversation. /// -/// Implements the design for these in Figma: -/// +/// 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. +/// +/// Currently this widget supports only those other contexts (not the main menu) +/// and only the component's "kind=unread" variant (not "kind=quantity"). +/// For example, the "Channels" page and the topic-list page: +/// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=6205-26001&m=dev +/// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=6823-37113&m=dev +/// (We use this for the topic-list page even though the Figma makes it a bit +/// more compact there…the inconsistency seems worse and might be accidental.) +// TODO support the main-menu context, update dartdoc +// TODO support the "kind=quantity" variant, update dartdoc class UnreadCountBadge extends StatelessWidget { const UnreadCountBadge({ super.key, required this.count, required this.channelIdForBackground, - this.bold = false, }); final int count; - final bool bold; /// An optional [Subscription.streamId], for a channel-colorized background. /// /// Useful when this badge represents messages in one specific channel. /// /// If null, the default neutral background will be used. + // TODO remove; the Figma doesn't use this anymore. final int? channelIdForBackground; @override @@ -46,19 +57,17 @@ class UnreadCountBadge extends StatelessWidget { return DecoratedBox( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(3), + borderRadius: BorderRadius.circular(5), color: backgroundColor, ), child: Padding( - padding: const EdgeInsets.fromLTRB(4, 0, 4, 1), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3), child: Text( style: TextStyle( fontSize: 16, - height: (18 / 16), - fontFeatures: const [FontFeature.enable('smcp')], // small caps + height: (16 / 16), color: textColor, - ).merge(weightVariableTextStyle(context, - wght: bold ? 600 : null)), + ).merge(weightVariableTextStyle(context, wght: 500)), count.toString()))); } } diff --git a/test/widgets/checks.dart b/test/widgets/checks.dart index d3a2761ad1..ef3f9b634f 100644 --- a/test/widgets/checks.dart +++ b/test/widgets/checks.dart @@ -94,7 +94,6 @@ extension PerAccountStoreWidgetChecks on Subject { extension UnreadCountBadgeChecks on Subject { Subject get count => has((b) => b.count, 'count'); - Subject get bold => has((b) => b.bold, 'bold'); Subject get channelIdForBackground => has((b) => b.channelIdForBackground, 'channelIdForBackground'); }