From a14da27f786fff66694267656102e6afdfae7a53 Mon Sep 17 00:00:00 2001 From: manthansubhash01 Date: Sat, 6 Dec 2025 17:10:08 +0530 Subject: [PATCH 1/5] channel rows: Use channelTopicLabelSpan for consistent alignment. --- lib/widgets/all_channels.dart | 13 +++--- lib/widgets/inbox.dart | 50 +++++++++++++++++++++++- lib/widgets/subscription_list.dart | 16 ++++---- test/widgets/all_channels_test.dart | 8 ++-- test/widgets/inbox_test.dart | 4 +- test/widgets/subscription_list_test.dart | 8 ++-- 6 files changed, 75 insertions(+), 24 deletions(-) diff --git a/lib/widgets/all_channels.dart b/lib/widgets/all_channels.dart index c6a765a11e..67b15c6e41 100644 --- a/lib/widgets/all_channels.dart +++ b/lib/widgets/all_channels.dart @@ -106,12 +106,8 @@ class AllChannelsListEntry extends StatelessWidget { child: ConstrainedBox(constraints: const BoxConstraints(minHeight: 44), child: Padding(padding: const EdgeInsetsDirectional.only(start: 8, end: 12), child: Row(spacing: 6, children: [ - Icon( - size: 20, - color: colorSwatchFor(context, subscription).iconOnPlainBackground, - iconDataForStream(channel)), Expanded( - child: Text( + child: Text.rich( maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -119,7 +115,12 @@ class AllChannelsListEntry extends StatelessWidget { fontSize: 17, height: 20 / 17, ).merge(weightVariableTextStyle(context, wght: 600)), - channel.name)), + channelTopicLabelSpan( + context: context, + channelId: channel.streamId, + fontSize: 16, + color: designVariables.unreadCountBadgeTextForChannel, + ))), if (hasContentAccess) _SubscribeToggle(channel: channel), ])))); } diff --git a/lib/widgets/inbox.dart b/lib/widgets/inbox.dart index ce7b200327..fedec262be 100644 --- a/lib/widgets/inbox.dart +++ b/lib/widgets/inbox.dart @@ -305,7 +305,8 @@ abstract class _HeaderItem extends StatelessWidget { ).merge(weightVariableTextStyle(context, wght: 600)), maxLines: 1, overflow: TextOverflow.ellipsis, - title(zulipLocalizations)))), + title(zulipLocalizations)) + )), const SizedBox(width: 12), if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), Padding(padding: const EdgeInsetsDirectional.only(end: 16), @@ -480,6 +481,53 @@ class _StreamHeaderItem extends _HeaderItem with _LongPressable { Future onLongPress() async { showChannelActionSheet(sectionContext, channelId: subscription.streamId); } + @override + Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); + final designVariables = DesignVariables.of(context); + return Material( + color: collapsed + ? designVariables.background // TODO(design) check if this is the right variable + : uncollapsedBackgroundColor(context), + child: InkWell( + // TODO use onRowTap to handle taps that are not on the collapse button. + // Probably we should give the collapse button a 44px or 48px square + // touch target: + // + // But that's in tension with the Figma, which gives these header rows + // 40px min height. + onTap: onCollapseButtonTap, + onLongPress: onLongPress, + child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ + Padding(padding: const EdgeInsets.all(10), + child: Icon(size: 20, color: designVariables.sectionCollapseIcon, + collapsed ? ZulipIcons.arrow_right : ZulipIcons.arrow_down)), + const SizedBox(width: 5), + Expanded(child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text.rich( + style: TextStyle( + fontSize: 17, + height: (20 / 17), + // TODO(design) check if this is the right variable + color: designVariables.labelMenuButton, + ).merge(weightVariableTextStyle(context, wght: 600)), + maxLines: 1, + overflow: TextOverflow.ellipsis, + channelTopicLabelSpan( + context: context, + channelId: subscription.streamId, + fontSize: 16, + color: designVariables.unreadCountBadgeTextForChannel, + )))), + const SizedBox(width: 12), + if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), + Padding(padding: const EdgeInsetsDirectional.only(end: 16), + child: UnreadCountBadge( + channelIdForBackground: channelId, + count: count)), + ]))); + } } class _StreamSection extends StatelessWidget { diff --git a/lib/widgets/subscription_list.dart b/lib/widgets/subscription_list.dart index 03714c734a..8bc677fe69 100644 --- a/lib/widgets/subscription_list.dart +++ b/lib/widgets/subscription_list.dart @@ -305,13 +305,6 @@ class SubscriptionItem extends StatelessWidget { showTopicListButton: showTopicListButtonInActionSheet), child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(width: 16), - Padding( - padding: const EdgeInsets.symmetric(vertical: 11), - child: Opacity( - opacity: opacity, - child: Icon(size: 18, color: swatch.iconOnPlainBackground, - iconDataForStream(subscription)))), - const SizedBox(width: 5), Expanded( child: Padding( padding: const EdgeInsets.symmetric(vertical: 10), @@ -320,7 +313,7 @@ class SubscriptionItem extends StatelessWidget { // https://github.com/zulip/zulip-flutter/pull/397#pullrequestreview-1742524205 child: Opacity( opacity: opacity, - child: Text( + child: Text.rich( style: TextStyle( fontSize: 18, height: (20 / 18), @@ -330,7 +323,12 @@ class SubscriptionItem extends StatelessWidget { wght: hasUnreads && !subscription.isMuted ? 600 : null)), maxLines: 1, overflow: TextOverflow.ellipsis, - subscription.name)))), + channelTopicLabelSpan( + context: context, + channelId: subscription.streamId, + fontSize: 16, + color: designVariables.unreadCountBadgeTextForChannel, + ))))), if (hasUnreads) ...[ const SizedBox(width: 12), // TODO(#747) show @-mention indicator when it applies diff --git a/test/widgets/all_channels_test.dart b/test/widgets/all_channels_test.dart index f6e1092781..744eed3fb3 100644 --- a/test/widgets/all_channels_test.dart +++ b/test/widgets/all_channels_test.dart @@ -193,7 +193,7 @@ void main() { final colorSwatch = colorSwatchFor(element, maybeSubscription); check(icon).color.equals(colorSwatch.iconOnPlainBackground); - check(findInRow(find.text(channel.name))).findsOne(); + check(findInRow(find.textContaining(channel.name, findRichText: true))).findsOne(); final maybeToggle = tester.widgetList( findInRow(find.byType(Toggle))).singleOrNull; @@ -230,8 +230,10 @@ void main() { await transitionDurationObserver.pumpPastTransition(tester); check(find.descendant( - of: find.byType(MessageListPage), - matching: find.text('some-channel')), + of: find.descendant( + of: find.byType(MessageListPage), + matching: find.byType(ZulipAppBar)), + matching: find.textContaining('some-channel', findRichText: true)), ).findsOne(); }); diff --git a/test/widgets/inbox_test.dart b/test/widgets/inbox_test.dart index 63aad6292c..12c659a3fa 100644 --- a/test/widgets/inbox_test.dart +++ b/test/widgets/inbox_test.dart @@ -122,7 +122,7 @@ void main() { /// Find a row with the given label. Widget? findRowByLabel(WidgetTester tester, String label) { final rowLabel = tester.widgetList( - find.text(label), + find.textContaining(label, findRichText: true), ).firstOrNull; if (rowLabel == null) { return null; @@ -550,7 +550,7 @@ void main() { check(collapseIcon).icon.equals(ZulipIcons.arrow_down); final streamIcon = findStreamHeaderIcon(tester, streamId); check(streamIcon).color.isNotNull().isSameColorAs( - ChannelColorSwatch.light(subscription.color).iconOnBarBackground); + ChannelColorSwatch.light(subscription.color).iconOnPlainBackground); check(streamHeaderBackgroundColor(tester, streamId)) .isNotNull().isSameColorAs(ChannelColorSwatch.light(subscription.color).barBackground); check(tester.widgetList(findSectionContent)).isNotEmpty(); diff --git a/test/widgets/subscription_list_test.dart b/test/widgets/subscription_list_test.dart index 70ede6f70a..9c86b2baec 100644 --- a/test/widgets/subscription_list_test.dart +++ b/test/widgets/subscription_list_test.dart @@ -283,7 +283,7 @@ void main() { testWidgets('muted streams are displayed as faded', (tester) async { void checkOpacityForStreamAndBadge(String streamName, int unreadCount, double opacity) { - final streamFinder = find.text(streamName); + final streamFinder = find.textContaining(streamName, findRichText: true); final streamOpacity = tester.widget( find.ancestor(of: streamFinder, matching: find.byType(Opacity))); final badgeFinder = find.text('$unreadCount'); @@ -316,8 +316,10 @@ void main() { testWidgets('stream name of unmuted streams with unmuted unreads is bold', (tester) async { void checkStreamNameWght(String streamName, double? expectedWght) { - final streamFinder = find.text(streamName); - final wght = wghtFromTextStyle(tester.widget(streamFinder).style!); + final streamFinder = find.textContaining(streamName, findRichText: true); + final textWidget = tester.widget(streamFinder); + final TextStyle? style = textWidget is Text ? textWidget.style : (textWidget as RichText).text.style; + final wght = wghtFromTextStyle(style!); check(wght).equals(expectedWght); } From 65a15cd5786fff3202962ccd6f18abea2dd6c891 Mon Sep 17 00:00:00 2001 From: manthansubhash01 Date: Sat, 6 Dec 2025 17:10:48 +0530 Subject: [PATCH 2/5] message list: Use channelTopicLabelSpan for app bar title. --- lib/widgets/message_list.dart | 59 +++++++++++++++++++---------- test/widgets/message_list_test.dart | 15 ++++---- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index 7ff3d395b8..7f020d25f4 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -529,6 +529,7 @@ class MessageListAppBarTitle extends StatelessWidget { ZulipStream? stream, }) { final store = PerAccountStoreWidget.of(context); + final designVariables = DesignVariables.of(context); final zulipLocalizations = ZulipLocalizations.of(context); // A null [Icon.icon] makes a blank space. @@ -547,10 +548,21 @@ class MessageListAppBarTitle extends StatelessWidget { // https://github.com/zulip/zulip-flutter/pull/219#discussion_r1281024746 crossAxisAlignment: CrossAxisAlignment.center, children: [ - Icon(size: 16, color: iconColor, icon), - const SizedBox(width: 4), - Flexible(child: Text( - stream?.name ?? zulipLocalizations.unknownChannelName)), + if (stream != null) + Flexible(child: Text.rich( + channelTopicLabelSpan( + context: context, + channelId: stream.streamId, + fontSize: 16, + color: designVariables.unreadCountBadgeTextForChannel, + ))) + else + Flexible(child: Text( + zulipLocalizations.unknownChannelName, + style: TextStyle( + fontStyle: FontStyle.italic, + color: designVariables.unreadCountBadgeTextForChannel, + ))), ]); } @@ -1719,22 +1731,29 @@ class StreamMessageRecipientHeader extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Padding( - // Figma specifies 5px horizontal spacing around an icon that's - // 18x18 and includes 1px padding. The icon SVG is flush with - // the edges, so make it 16x16 with 6px horizontal padding. - // Bottom padding added here to shift icon up to - // match alignment with text visually. - padding: const EdgeInsets.only(left: 6, right: 6, bottom: 3), - child: Icon(size: 16, color: iconColor, - // A null [Icon.icon] makes a blank space. - stream != null ? iconDataForStream(stream) : null)), - Padding( - padding: const EdgeInsets.symmetric(vertical: 11), - child: Text(streamName, - style: recipientHeaderTextStyle(context), - overflow: TextOverflow.ellipsis), - ), + if (stream != null) + Padding( + padding: const EdgeInsets.symmetric(vertical: 11), + child: Text.rich( + channelTopicLabelSpan( + context: context, + channelId: streamId, + fontSize: 16, + color: designVariables.unreadCountBadgeTextForChannel, + ), + style: recipientHeaderTextStyle(context), + overflow: TextOverflow.ellipsis), + ) + else + Padding( + padding: const EdgeInsets.symmetric(vertical: 11), + child: Text(streamName, + style: recipientHeaderTextStyle(context).copyWith( + fontStyle: FontStyle.italic, + color: designVariables.unreadCountBadgeTextForChannel, + ), + overflow: TextOverflow.ellipsis), + ), Padding( // Figma has 5px horizontal padding around an 8px wide icon. // Icon is 16px wide here so horizontal padding is 1px. diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart index 89b2a31bd8..0963b130b9 100644 --- a/test/widgets/message_list_test.dart +++ b/test/widgets/message_list_test.dart @@ -119,9 +119,9 @@ void main() { void checkAppBarChannelTopic(String channelName, String topic) { final appBarFinder = find.byType(MessageListAppBarTitle); check(appBarFinder).findsOne(); - check(find.descendant(of: appBarFinder, matching: find.text(channelName))) + check(find.descendant(of: appBarFinder, matching: find.textContaining(channelName, findRichText: true))) .findsOne(); - check(find.descendant(of: appBarFinder, matching: find.text(topic))) + check(find.descendant(of: appBarFinder, matching: find.textContaining(topic, findRichText: true))) .findsOne(); } @@ -268,7 +268,8 @@ void main() { matching: find.byIcon(expectedIcon))); check(Theme.brightnessOf(iconElement)).equals(Brightness.light); - check(iconElement.widget as Icon).color.equals(Color(0xff5972fc)); + final swatch = ChannelColorSwatch.light(color); + check(iconElement.widget as Icon).color.equals(swatch.iconOnPlainBackground); }); } testChannelIconInChannelRow(ZulipIcons.globe, isWebPublic: true, inviteOnly: false); @@ -1292,7 +1293,7 @@ void main() { // Stream name shows up in [AppBar] so need to avoid matching that return find.descendant( of: find.byType(MessageList), - matching: find.text(text)).evaluate(); + matching: find.textContaining(text, findRichText: true)).evaluate(); } testWidgets('show stream name in CombinedFeedNarrow', (tester) async { @@ -1407,7 +1408,7 @@ void main() { subscriptions: [subscription]); await tester.pump(); check(tester.widget(find.byIcon(ZulipIcons.globe))) - .color.isNotNull().isSameColorAs(swatch.iconOnBarBackground); + .color.isNotNull().isSameColorAs(swatch.iconOnPlainBackground); }); testWidgets('normal streams show hash icon', (tester) async { @@ -1477,7 +1478,7 @@ void main() { eg.streamMessage(stream: streamBefore), ]); await tester.pump(); - tester.widget(find.text('new stream name')); + tester.widget(find.textContaining('new stream name', findRichText: true)); }); testWidgets('navigates to ChannelNarrow on tapping channel in CombinedFeedNarrow', (tester) async { @@ -1500,7 +1501,7 @@ void main() { foundOldest: true, messages: [message]).toJson()); await tester.tap(find.descendant( of: find.byType(StreamMessageRecipientHeader), - matching: find.text(channel.name))); + matching: find.textContaining(channel.name, findRichText: true))); await tester.pump(); check(pushedRoutes).single.isA().page.isA() .initNarrow.equals(ChannelNarrow(channel.streamId)); From 33c0dbf6d0ffcf7f9346069417c3e082ac2a938a Mon Sep 17 00:00:00 2001 From: manthansubhash01 Date: Sat, 6 Dec 2025 17:11:25 +0530 Subject: [PATCH 3/5] topic list: Update tests for channelTopicLabelSpan behavior. --- test/widgets/action_sheet_test.dart | 8 ++++---- test/widgets/topic_list_test.dart | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index c43c6daab7..4ecccc1d72 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -199,7 +199,7 @@ void main() { await tester.pump(); check(find.byType(InboxPageBody)).findsOne(); - await tester.longPress(find.text(someChannel.name).hitTestable()); + await tester.longPress(find.textContaining(someChannel.name, findRichText: true).hitTestable()); await tester.pump(const Duration(milliseconds: 250)); } @@ -211,7 +211,7 @@ void main() { await tester.pump(); check(find.byType(SubscriptionListPageBody)).findsOne(); - await tester.longPress(find.text(someChannel.name).hitTestable()); + await tester.longPress(find.textContaining(someChannel.name, findRichText: true).hitTestable()); await tester.pump(const Duration(milliseconds: 250)); } @@ -236,7 +236,7 @@ void main() { await tester.longPress(find.descendant( of: find.byType(ZulipAppBar), - matching: find.text(channel.name))); + matching: find.textContaining(channel.name, findRichText: true))); await tester.pump(const Duration(milliseconds: 250)); } @@ -253,7 +253,7 @@ void main() { await tester.longPress(find.descendant( of: find.byType(RecipientHeader), - matching: find.text(message.displayRecipient ?? ''))); + matching: find.textContaining(message.displayRecipient ?? '', findRichText: true))); await tester.pump(const Duration(milliseconds: 250)); } diff --git a/test/widgets/topic_list_test.dart b/test/widgets/topic_list_test.dart index 1cd9c4bb26..152a57df2d 100644 --- a/test/widgets/topic_list_test.dart +++ b/test/widgets/topic_list_test.dart @@ -88,8 +88,10 @@ void main() { await tester.pump(); await tester.pump(Duration.zero); check(find.descendant( - of: find.byType(MessageListPage), - matching: find.text('channel foo')), + of: find.descendant( + of: find.byType(MessageListPage), + matching: find.byType(ZulipAppBar)), + matching: find.textContaining('channel foo', findRichText: true)), ).findsOne(); }); From 474edbc49fbe61417e406b593d64dff51cf951d7 Mon Sep 17 00:00:00 2001 From: manthansubhash01 Date: Tue, 9 Dec 2025 01:03:42 +0530 Subject: [PATCH 4/5] removed unwanted variables --- lib/widgets/all_channels.dart | 2 -- lib/widgets/inbox.dart | 1 - lib/widgets/message_list.dart | 11 ----------- lib/widgets/subscription_list.dart | 1 - 4 files changed, 15 deletions(-) diff --git a/lib/widgets/all_channels.dart b/lib/widgets/all_channels.dart index 67b15c6e41..4506e7a4b0 100644 --- a/lib/widgets/all_channels.dart +++ b/lib/widgets/all_channels.dart @@ -10,7 +10,6 @@ import 'action_sheet.dart'; import 'actions.dart'; import 'app_bar.dart'; import 'button.dart'; -import 'icons.dart'; import 'message_list.dart'; import 'page.dart'; import 'remote_settings.dart'; @@ -95,7 +94,6 @@ class AllChannelsListEntry extends StatelessWidget { final store = PerAccountStoreWidget.of(context); final designVariables = DesignVariables.of(context); final channel = this.channel; - final Subscription? subscription = channel is Subscription ? channel : null; final hasContentAccess = store.selfHasContentAccess(channel); return InkWell( diff --git a/lib/widgets/inbox.dart b/lib/widgets/inbox.dart index fedec262be..19c6615b97 100644 --- a/lib/widgets/inbox.dart +++ b/lib/widgets/inbox.dart @@ -483,7 +483,6 @@ class _StreamHeaderItem extends _HeaderItem with _LongPressable { } @override Widget build(BuildContext context) { - final zulipLocalizations = ZulipLocalizations.of(context); final designVariables = DesignVariables.of(context); return Material( color: collapsed diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index 7f020d25f4..a5a4e9cd74 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -528,19 +528,9 @@ class MessageListAppBarTitle extends StatelessWidget { Widget _buildStreamRow(BuildContext context, { ZulipStream? stream, }) { - final store = PerAccountStoreWidget.of(context); final designVariables = DesignVariables.of(context); final zulipLocalizations = ZulipLocalizations.of(context); - // A null [Icon.icon] makes a blank space. - IconData? icon; - Color? iconColor; - if (stream != null) { - icon = iconDataForStream(stream); - iconColor = colorSwatchFor(context, store.subscriptions[stream.streamId]) - .iconOnBarBackground; - } - return Row( mainAxisSize: MainAxisSize.min, // TODO(design): The vertical alignment of the stream privacy icon is a bit ad hoc. @@ -1712,7 +1702,6 @@ class StreamMessageRecipientHeader extends StatelessWidget { final swatch = colorSwatchFor(context, store.subscriptions[streamId]); final backgroundColor = swatch.barBackground; - final iconColor = swatch.iconOnBarBackground; final Widget streamWidget; if (!_containsDifferentChannels(narrow)) { diff --git a/lib/widgets/subscription_list.dart b/lib/widgets/subscription_list.dart index 8bc677fe69..e7dba8aa46 100644 --- a/lib/widgets/subscription_list.dart +++ b/lib/widgets/subscription_list.dart @@ -292,7 +292,6 @@ class SubscriptionItem extends StatelessWidget { Widget build(BuildContext context) { final designVariables = DesignVariables.of(context); - final swatch = colorSwatchFor(context, subscription); final hasUnreads = (unreadCount > 0); final opacity = subscription.isMuted ? 0.55 : 1.0; return Material( From 69521fccb5e31d4fb97890f1b79f077eb18d8362 Mon Sep 17 00:00:00 2001 From: manthansubhash01 Date: Sat, 13 Dec 2025 17:25:50 +0530 Subject: [PATCH 5/5] message list: Fix channel icon vertical alignment in app bar and made the required UI changes --- lib/widgets/all_channels.dart | 15 ++++---- lib/widgets/inbox.dart | 49 +----------------------- lib/widgets/message_list.dart | 25 +++++++----- lib/widgets/subscription_list.dart | 17 ++++---- test/widgets/all_channels_test.dart | 2 +- test/widgets/inbox_test.dart | 4 +- test/widgets/subscription_list_test.dart | 8 ++-- 7 files changed, 40 insertions(+), 80 deletions(-) diff --git a/lib/widgets/all_channels.dart b/lib/widgets/all_channels.dart index 4506e7a4b0..c6a765a11e 100644 --- a/lib/widgets/all_channels.dart +++ b/lib/widgets/all_channels.dart @@ -10,6 +10,7 @@ import 'action_sheet.dart'; import 'actions.dart'; import 'app_bar.dart'; import 'button.dart'; +import 'icons.dart'; import 'message_list.dart'; import 'page.dart'; import 'remote_settings.dart'; @@ -94,6 +95,7 @@ class AllChannelsListEntry extends StatelessWidget { final store = PerAccountStoreWidget.of(context); final designVariables = DesignVariables.of(context); final channel = this.channel; + final Subscription? subscription = channel is Subscription ? channel : null; final hasContentAccess = store.selfHasContentAccess(channel); return InkWell( @@ -104,8 +106,12 @@ class AllChannelsListEntry extends StatelessWidget { child: ConstrainedBox(constraints: const BoxConstraints(minHeight: 44), child: Padding(padding: const EdgeInsetsDirectional.only(start: 8, end: 12), child: Row(spacing: 6, children: [ + Icon( + size: 20, + color: colorSwatchFor(context, subscription).iconOnPlainBackground, + iconDataForStream(channel)), Expanded( - child: Text.rich( + child: Text( maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -113,12 +119,7 @@ class AllChannelsListEntry extends StatelessWidget { fontSize: 17, height: 20 / 17, ).merge(weightVariableTextStyle(context, wght: 600)), - channelTopicLabelSpan( - context: context, - channelId: channel.streamId, - fontSize: 16, - color: designVariables.unreadCountBadgeTextForChannel, - ))), + channel.name)), if (hasContentAccess) _SubscribeToggle(channel: channel), ])))); } diff --git a/lib/widgets/inbox.dart b/lib/widgets/inbox.dart index 19c6615b97..ce7b200327 100644 --- a/lib/widgets/inbox.dart +++ b/lib/widgets/inbox.dart @@ -305,8 +305,7 @@ abstract class _HeaderItem extends StatelessWidget { ).merge(weightVariableTextStyle(context, wght: 600)), maxLines: 1, overflow: TextOverflow.ellipsis, - title(zulipLocalizations)) - )), + title(zulipLocalizations)))), const SizedBox(width: 12), if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), Padding(padding: const EdgeInsetsDirectional.only(end: 16), @@ -481,52 +480,6 @@ class _StreamHeaderItem extends _HeaderItem with _LongPressable { Future onLongPress() async { showChannelActionSheet(sectionContext, channelId: subscription.streamId); } - @override - Widget build(BuildContext context) { - final designVariables = DesignVariables.of(context); - return Material( - color: collapsed - ? designVariables.background // TODO(design) check if this is the right variable - : uncollapsedBackgroundColor(context), - child: InkWell( - // TODO use onRowTap to handle taps that are not on the collapse button. - // Probably we should give the collapse button a 44px or 48px square - // touch target: - // - // But that's in tension with the Figma, which gives these header rows - // 40px min height. - onTap: onCollapseButtonTap, - onLongPress: onLongPress, - child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Padding(padding: const EdgeInsets.all(10), - child: Icon(size: 20, color: designVariables.sectionCollapseIcon, - collapsed ? ZulipIcons.arrow_right : ZulipIcons.arrow_down)), - const SizedBox(width: 5), - Expanded(child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Text.rich( - style: TextStyle( - fontSize: 17, - height: (20 / 17), - // TODO(design) check if this is the right variable - color: designVariables.labelMenuButton, - ).merge(weightVariableTextStyle(context, wght: 600)), - maxLines: 1, - overflow: TextOverflow.ellipsis, - channelTopicLabelSpan( - context: context, - channelId: subscription.streamId, - fontSize: 16, - color: designVariables.unreadCountBadgeTextForChannel, - )))), - const SizedBox(width: 12), - if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), - Padding(padding: const EdgeInsetsDirectional.only(end: 16), - child: UnreadCountBadge( - channelIdForBackground: channelId, - count: count)), - ]))); - } } class _StreamSection extends StatelessWidget { diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index a5a4e9cd74..ac71bbba27 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -543,7 +543,7 @@ class MessageListAppBarTitle extends StatelessWidget { channelTopicLabelSpan( context: context, channelId: stream.streamId, - fontSize: 16, + fontSize: 20, color: designVariables.unreadCountBadgeTextForChannel, ))) else @@ -1722,12 +1722,15 @@ class StreamMessageRecipientHeader extends StatelessWidget { children: [ if (stream != null) Padding( - padding: const EdgeInsets.symmetric(vertical: 11), + // Figma specifies 5px horizontal spacing around an icon that's + // 18x18 and includes 1px padding. The icon SVG is flush with + // the edges, so make it 16x16 with 6px horizontal padding. + padding: const EdgeInsets.only(left: 6, right: 6), child: Text.rich( channelTopicLabelSpan( context: context, channelId: streamId, - fontSize: 16, + fontSize: 20, color: designVariables.unreadCountBadgeTextForChannel, ), style: recipientHeaderTextStyle(context), @@ -1735,13 +1738,15 @@ class StreamMessageRecipientHeader extends StatelessWidget { ) else Padding( - padding: const EdgeInsets.symmetric(vertical: 11), - child: Text(streamName, - style: recipientHeaderTextStyle(context).copyWith( - fontStyle: FontStyle.italic, - color: designVariables.unreadCountBadgeTextForChannel, - ), - overflow: TextOverflow.ellipsis), + padding: const EdgeInsets.only(left: 6, right: 6), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11), + child: Text(streamName, + style: recipientHeaderTextStyle(context).copyWith( + fontStyle: FontStyle.italic, + color: designVariables.unreadCountBadgeTextForChannel, + ), + overflow: TextOverflow.ellipsis)), ), Padding( // Figma has 5px horizontal padding around an 8px wide icon. diff --git a/lib/widgets/subscription_list.dart b/lib/widgets/subscription_list.dart index e7dba8aa46..03714c734a 100644 --- a/lib/widgets/subscription_list.dart +++ b/lib/widgets/subscription_list.dart @@ -292,6 +292,7 @@ class SubscriptionItem extends StatelessWidget { Widget build(BuildContext context) { final designVariables = DesignVariables.of(context); + final swatch = colorSwatchFor(context, subscription); final hasUnreads = (unreadCount > 0); final opacity = subscription.isMuted ? 0.55 : 1.0; return Material( @@ -304,6 +305,13 @@ class SubscriptionItem extends StatelessWidget { showTopicListButton: showTopicListButtonInActionSheet), child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(width: 16), + Padding( + padding: const EdgeInsets.symmetric(vertical: 11), + child: Opacity( + opacity: opacity, + child: Icon(size: 18, color: swatch.iconOnPlainBackground, + iconDataForStream(subscription)))), + const SizedBox(width: 5), Expanded( child: Padding( padding: const EdgeInsets.symmetric(vertical: 10), @@ -312,7 +320,7 @@ class SubscriptionItem extends StatelessWidget { // https://github.com/zulip/zulip-flutter/pull/397#pullrequestreview-1742524205 child: Opacity( opacity: opacity, - child: Text.rich( + child: Text( style: TextStyle( fontSize: 18, height: (20 / 18), @@ -322,12 +330,7 @@ class SubscriptionItem extends StatelessWidget { wght: hasUnreads && !subscription.isMuted ? 600 : null)), maxLines: 1, overflow: TextOverflow.ellipsis, - channelTopicLabelSpan( - context: context, - channelId: subscription.streamId, - fontSize: 16, - color: designVariables.unreadCountBadgeTextForChannel, - ))))), + subscription.name)))), if (hasUnreads) ...[ const SizedBox(width: 12), // TODO(#747) show @-mention indicator when it applies diff --git a/test/widgets/all_channels_test.dart b/test/widgets/all_channels_test.dart index 744eed3fb3..c53ffca0ac 100644 --- a/test/widgets/all_channels_test.dart +++ b/test/widgets/all_channels_test.dart @@ -193,7 +193,7 @@ void main() { final colorSwatch = colorSwatchFor(element, maybeSubscription); check(icon).color.equals(colorSwatch.iconOnPlainBackground); - check(findInRow(find.textContaining(channel.name, findRichText: true))).findsOne(); + check(findInRow(find.text(channel.name))).findsOne(); final maybeToggle = tester.widgetList( findInRow(find.byType(Toggle))).singleOrNull; diff --git a/test/widgets/inbox_test.dart b/test/widgets/inbox_test.dart index 12c659a3fa..63aad6292c 100644 --- a/test/widgets/inbox_test.dart +++ b/test/widgets/inbox_test.dart @@ -122,7 +122,7 @@ void main() { /// Find a row with the given label. Widget? findRowByLabel(WidgetTester tester, String label) { final rowLabel = tester.widgetList( - find.textContaining(label, findRichText: true), + find.text(label), ).firstOrNull; if (rowLabel == null) { return null; @@ -550,7 +550,7 @@ void main() { check(collapseIcon).icon.equals(ZulipIcons.arrow_down); final streamIcon = findStreamHeaderIcon(tester, streamId); check(streamIcon).color.isNotNull().isSameColorAs( - ChannelColorSwatch.light(subscription.color).iconOnPlainBackground); + ChannelColorSwatch.light(subscription.color).iconOnBarBackground); check(streamHeaderBackgroundColor(tester, streamId)) .isNotNull().isSameColorAs(ChannelColorSwatch.light(subscription.color).barBackground); check(tester.widgetList(findSectionContent)).isNotEmpty(); diff --git a/test/widgets/subscription_list_test.dart b/test/widgets/subscription_list_test.dart index 9c86b2baec..70ede6f70a 100644 --- a/test/widgets/subscription_list_test.dart +++ b/test/widgets/subscription_list_test.dart @@ -283,7 +283,7 @@ void main() { testWidgets('muted streams are displayed as faded', (tester) async { void checkOpacityForStreamAndBadge(String streamName, int unreadCount, double opacity) { - final streamFinder = find.textContaining(streamName, findRichText: true); + final streamFinder = find.text(streamName); final streamOpacity = tester.widget( find.ancestor(of: streamFinder, matching: find.byType(Opacity))); final badgeFinder = find.text('$unreadCount'); @@ -316,10 +316,8 @@ void main() { testWidgets('stream name of unmuted streams with unmuted unreads is bold', (tester) async { void checkStreamNameWght(String streamName, double? expectedWght) { - final streamFinder = find.textContaining(streamName, findRichText: true); - final textWidget = tester.widget(streamFinder); - final TextStyle? style = textWidget is Text ? textWidget.style : (textWidget as RichText).text.style; - final wght = wghtFromTextStyle(style!); + final streamFinder = find.text(streamName); + final wght = wghtFromTextStyle(tester.widget(streamFinder).style!); check(wght).equals(expectedWght); }