Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/widgets/inbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,6 @@ abstract class _HeaderItem extends StatelessWidget {
Padding(padding: const EdgeInsetsDirectional.only(end: 16),
child: UnreadCountBadge(
channelIdForBackground: channelId,
bold: true,
count: count)),
])));
}
Expand Down
3 changes: 1 addition & 2 deletions lib/widgets/subscription_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions lib/widgets/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
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),
Expand Down Expand Up @@ -285,7 +285,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
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),
Expand Down
110 changes: 45 additions & 65 deletions lib/widgets/topic_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down Expand Up @@ -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),
])),
])),
)));
}
}

Expand All @@ -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)))));
}
}
29 changes: 19 additions & 10 deletions lib/widgets/unread_count_badge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
/// <https://www.figma.com/file/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=341%3A12387&mode=dev>
/// 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
Expand All @@ -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())));
}
}
Expand Down
1 change: 0 additions & 1 deletion test/widgets/checks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ extension PerAccountStoreWidgetChecks on Subject<PerAccountStoreWidget> {

extension UnreadCountBadgeChecks on Subject<UnreadCountBadge> {
Subject<int> get count => has((b) => b.count, 'count');
Subject<bool> get bold => has((b) => b.bold, 'bold');
Subject<int?> get channelIdForBackground => has((b) => b.channelIdForBackground, 'channelIdForBackground');
}

Expand Down