Skip to content

Commit 5390571

Browse files
committed
inbox: Add label for archived channels in headers
Design inspired by the web version. Fixes #800
1 parent b29c004 commit 5390571

16 files changed

+249
-36
lines changed

Diff for: assets/l10n/app_en.arb

+4
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,10 @@
375375
"@unknownChannelName": {
376376
"description": "Replacement name for channel when it cannot be found in the store."
377377
},
378+
"channelArchivedLabel": "(archived)",
379+
"@channelArchivedLabel": {
380+
"description": "Label shown next to an archived channel's name in headers."
381+
},
378382
"composeBoxTopicHintText": "Topic",
379383
"@composeBoxTopicHintText": {
380384
"description": "Hint text for topic input widget in compose box."

Diff for: lib/generated/l10n/zulip_localizations.dart

+6
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,12 @@ abstract class ZulipLocalizations {
603603
/// **'(unknown channel)'**
604604
String get unknownChannelName;
605605

606+
/// Label shown next to an archived channel's name in headers.
607+
///
608+
/// In en, this message translates to:
609+
/// **'(archived)'**
610+
String get channelArchivedLabel;
611+
606612
/// Hint text for topic input widget in compose box.
607613
///
608614
/// In en, this message translates to:

Diff for: lib/generated/l10n/zulip_localizations_ar.dart

+3
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
295295
@override
296296
String get unknownChannelName => '(unknown channel)';
297297

298+
@override
299+
String get channelArchivedLabel => '(archived)';
300+
298301
@override
299302
String get composeBoxTopicHintText => 'Topic';
300303

Diff for: lib/generated/l10n/zulip_localizations_en.dart

+3
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
295295
@override
296296
String get unknownChannelName => '(unknown channel)';
297297

298+
@override
299+
String get channelArchivedLabel => '(archived)';
300+
298301
@override
299302
String get composeBoxTopicHintText => 'Topic';
300303

Diff for: lib/generated/l10n/zulip_localizations_ja.dart

+3
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
295295
@override
296296
String get unknownChannelName => '(unknown channel)';
297297

298+
@override
299+
String get channelArchivedLabel => '(archived)';
300+
298301
@override
299302
String get composeBoxTopicHintText => 'Topic';
300303

Diff for: lib/generated/l10n/zulip_localizations_nb.dart

+3
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
295295
@override
296296
String get unknownChannelName => '(unknown channel)';
297297

298+
@override
299+
String get channelArchivedLabel => '(archived)';
300+
298301
@override
299302
String get composeBoxTopicHintText => 'Topic';
300303

Diff for: lib/generated/l10n/zulip_localizations_pl.dart

+3
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
295295
@override
296296
String get unknownChannelName => '(nieznany kanał)';
297297

298+
@override
299+
String get channelArchivedLabel => '(archived)';
300+
298301
@override
299302
String get composeBoxTopicHintText => 'Wątek';
300303

Diff for: lib/generated/l10n/zulip_localizations_ru.dart

+3
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
295295
@override
296296
String get unknownChannelName => '(unknown channel)';
297297

298+
@override
299+
String get channelArchivedLabel => '(archived)';
300+
298301
@override
299302
String get composeBoxTopicHintText => 'Тема';
300303

Diff for: lib/generated/l10n/zulip_localizations_sk.dart

+3
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
295295
@override
296296
String get unknownChannelName => '(unknown channel)';
297297

298+
@override
299+
String get channelArchivedLabel => '(archived)';
300+
298301
@override
299302
String get composeBoxTopicHintText => 'Topic';
300303

Diff for: lib/widgets/action_sheet.dart

+13-5
Original file line numberDiff line numberDiff line change
@@ -230,19 +230,22 @@ void showTopicActionSheet(BuildContext context, {
230230
final pageContext = PageRoot.contextOf(context);
231231

232232
final store = PerAccountStoreWidget.of(pageContext);
233+
final channel = store.streams[channelId];
233234
final subscription = store.subscriptions[channelId];
234235

235236
final optionButtons = <ActionSheetMenuItemButton>[];
236237

238+
final isChannelArchived = channel?.isArchived == false;
237239
// TODO(server-7): simplify this condition away
238240
final supportsUnmutingTopics = store.zulipFeatureLevel >= 170;
239241
// TODO(server-8): simplify this condition away
240242
final supportsFollowingTopics = store.zulipFeatureLevel >= 219;
241243

242244
final visibilityOptions = <UserTopicVisibilityPolicy>[];
243245
final visibilityPolicy = store.topicVisibilityPolicy(channelId, topic);
244-
if (subscription == null) {
245-
// Not subscribed to the channel; there is no user topic change to be made.
246+
if (subscription == null || !isChannelArchived) {
247+
// Not subscribed to the channel or the channel is archived;
248+
// there is no user topic change to be made.
246249
} else if (!subscription.isMuted) {
247250
// Channel is subscribed and not muted.
248251
switch (visibilityPolicy) {
@@ -306,7 +309,8 @@ void showTopicActionSheet(BuildContext context, {
306309
// limit for editing topics).
307310
if (someMessageIdInTopic != null
308311
// ignore: unnecessary_null_comparison // null topic names soon to be enabled
309-
&& topic.displayName != null) {
312+
&& topic.displayName != null
313+
&& isChannelArchived) {
310314
optionButtons.add(ResolveUnresolveButton(pageContext: pageContext,
311315
topic: topic,
312316
someMessageIdInTopic: someMessageIdInTopic));
@@ -564,14 +568,18 @@ void showMessageActionSheet({required BuildContext context, required Message mes
564568
final messageListPage = MessageListPage.ancestorOf(pageContext);
565569
final isComposeBoxOffered = messageListPage.composeBoxController != null;
566570

571+
bool isInArchivedChannel = false;
572+
if (message is StreamMessage) {
573+
final channel = store.streams[message.streamId];
574+
isInArchivedChannel = channel?.isArchived == true;
575+
}
567576
final isMessageRead = message.flags.contains(MessageFlag.read);
568577
final markAsUnreadSupported = store.zulipFeatureLevel >= 155; // TODO(server-6)
569578
final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead;
570-
571579
final optionButtons = [
572580
ReactionButtons(message: message, pageContext: pageContext),
573581
StarButton(message: message, pageContext: pageContext),
574-
if (isComposeBoxOffered)
582+
if (isComposeBoxOffered && !isInArchivedChannel)
575583
QuoteAndReplyButton(message: message, pageContext: pageContext),
576584
if (showMarkAsUnreadButton)
577585
MarkAsUnreadButton(message: message, pageContext: pageContext),

Diff for: lib/widgets/inbox.dart

+39-12
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ abstract class _HeaderItem extends StatelessWidget {
240240

241241
String title(ZulipLocalizations zulipLocalizations);
242242
IconData get icon;
243+
InlineSpan? buildTrailing(BuildContext context) => null;
243244
Color collapsedIconColor(BuildContext context);
244245
Color uncollapsedIconColor(BuildContext context);
245246
Color uncollapsedBackgroundColor(BuildContext context);
@@ -285,18 +286,24 @@ abstract class _HeaderItem extends StatelessWidget {
285286
: uncollapsedIconColor(context),
286287
icon),
287288
const SizedBox(width: 5),
288-
Expanded(child: Padding(
289-
padding: const EdgeInsets.symmetric(vertical: 4),
290-
child: Text(
291-
style: TextStyle(
292-
fontSize: 17,
293-
height: (20 / 17),
294-
// TODO(design) check if this is the right variable
295-
color: designVariables.labelMenuButton,
296-
).merge(weightVariableTextStyle(context, wght: 600)),
297-
maxLines: 1,
298-
overflow: TextOverflow.ellipsis,
299-
title(zulipLocalizations)))),
289+
Expanded(
290+
child: Padding(
291+
padding: const EdgeInsets.symmetric(vertical: 4),
292+
child: RichText(
293+
maxLines: 1,
294+
overflow: TextOverflow.ellipsis,
295+
text: TextSpan(
296+
children: [
297+
TextSpan(
298+
text: title(zulipLocalizations),
299+
style: TextStyle(
300+
fontSize: 17,
301+
height: 20 / 17,
302+
// TODO(design) check if this is the right variable
303+
color: designVariables.labelMenuButton,
304+
).merge(weightVariableTextStyle(context, wght: 600))),
305+
buildTrailing(context) ?? const TextSpan(),
306+
])))),
300307
const SizedBox(width: 12),
301308
if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign),
302309
Padding(padding: const EdgeInsetsDirectional.only(end: 16),
@@ -436,9 +443,11 @@ mixin _LongPressable on _HeaderItem {
436443

437444
class _StreamHeaderItem extends _HeaderItem with _LongPressable {
438445
final Subscription subscription;
446+
final bool isArchived;
439447

440448
const _StreamHeaderItem({
441449
required this.subscription,
450+
required this.isArchived,
442451
required super.collapsed,
443452
required super.pageState,
444453
required super.count,
@@ -449,6 +458,23 @@ class _StreamHeaderItem extends _HeaderItem with _LongPressable {
449458
@override String title(ZulipLocalizations zulipLocalizations) =>
450459
subscription.name;
451460
@override IconData get icon => iconDataForStream(subscription);
461+
@override InlineSpan? buildTrailing(BuildContext context) {
462+
if (!isArchived) return null;
463+
464+
final designVariables = DesignVariables.of(context);
465+
final zulipLocalizations = ZulipLocalizations.of(context);
466+
467+
return WidgetSpan(
468+
child: Padding(
469+
padding: const EdgeInsetsDirectional.only(start: 4),
470+
child: Text(
471+
zulipLocalizations.channelArchivedLabel,
472+
style: TextStyle(
473+
fontSize: 17,
474+
height: 20 / 17,
475+
color: designVariables.labelMessageHeaderArchived,
476+
fontStyle: FontStyle.italic))));
477+
}
452478
@override Color collapsedIconColor(context) =>
453479
colorSwatchFor(context, subscription).iconOnPlainBackground;
454480
@override Color uncollapsedIconColor(context) =>
@@ -495,6 +521,7 @@ class _StreamSection extends StatelessWidget {
495521
collapsed: collapsed,
496522
pageState: pageState,
497523
sectionContext: context,
524+
isArchived: subscription.isArchived,
498525
);
499526
return StickyHeaderItem(
500527
header: header,

Diff for: lib/widgets/message_list.dart

+30-2
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ class MessageListAppBarTitle extends StatelessWidget {
313313
ZulipStream? stream,
314314
}) {
315315
final zulipLocalizations = ZulipLocalizations.of(context);
316+
final designVariables = DesignVariables.of(context);
316317
// A null [Icon.icon] makes a blank space.
317318
final icon = stream != null ? iconDataForStream(stream) : null;
318319
return Row(
@@ -326,6 +327,17 @@ class MessageListAppBarTitle extends StatelessWidget {
326327
const SizedBox(width: 4),
327328
Flexible(child: Text(
328329
stream?.name ?? zulipLocalizations.unknownChannelName)),
330+
if (stream?.isArchived ?? false)
331+
// TODO(#1285): Avoid concatenating translated strings
332+
Padding(
333+
padding: EdgeInsetsDirectional.fromSTEB(4, 4, 0, 4),
334+
child: Text(
335+
zulipLocalizations.channelArchivedLabel,
336+
style: TextStyle(
337+
fontSize: 18,
338+
// TODO(design): check if this is the right variable
339+
color: designVariables.labelMessageHeaderArchived,
340+
fontStyle: FontStyle.italic))),
329341
]);
330342
}
331343

@@ -1102,6 +1114,18 @@ class StreamMessageRecipientHeader extends StatelessWidget {
11021114
style: recipientHeaderTextStyle(context),
11031115
overflow: TextOverflow.ellipsis),
11041116
),
1117+
if (stream?.isArchived ?? false)
1118+
// TODO(#1285): Avoid concatenating translated strings
1119+
Padding(
1120+
padding: const EdgeInsetsDirectional.fromSTEB(4, 4, 0, 4),
1121+
child: Text(
1122+
zulipLocalizations.channelArchivedLabel,
1123+
style: recipientHeaderTextStyle(context,
1124+
// TODO(design): check if this is the right variable
1125+
color: designVariables.labelMessageHeaderArchived,
1126+
fontStyle: FontStyle.italic),
1127+
overflow: TextOverflow.ellipsis,
1128+
maxLines: 1)),
11051129
Padding(
11061130
// Figma has 5px horizontal padding around an 8px wide icon.
11071131
// Icon is 16px wide here so horizontal padding is 1px.
@@ -1220,9 +1244,13 @@ class DmRecipientHeader extends StatelessWidget {
12201244
}
12211245
}
12221246

1223-
TextStyle recipientHeaderTextStyle(BuildContext context, {FontStyle? fontStyle}) {
1247+
TextStyle recipientHeaderTextStyle(
1248+
BuildContext context, {
1249+
Color? color,
1250+
FontStyle? fontStyle,
1251+
}) {
12241252
return TextStyle(
1225-
color: DesignVariables.of(context).title,
1253+
color: color ?? DesignVariables.of(context).title,
12261254
fontSize: 16,
12271255
letterSpacing: proportionalLetterSpacing(context, 0.02, baseFontSize: 16),
12281256
height: (18 / 16),

Diff for: lib/widgets/theme.dart

+7
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
172172
groupDmConversationIcon: Colors.black.withValues(alpha: 0.5),
173173
groupDmConversationIconBg: const Color(0x33808080),
174174
inboxItemIconMarker: const HSLColor.fromAHSL(0.5, 0, 0, 0.2).toColor(),
175+
labelMessageHeaderArchived: const HSLColor.fromAHSL(0.5, 0, 0, 0.5).toColor(),
175176
loginOrDivider: const Color(0xffdedede),
176177
loginOrDividerText: const Color(0xff575757),
177178
modalBarrierColor: const Color(0xff000000).withValues(alpha: 0.3),
@@ -232,6 +233,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
232233
// TODO(design-dark) need proper dark-theme color (this is ad hoc)
233234
groupDmConversationIconBg: const Color(0x33cccccc),
234235
inboxItemIconMarker: const HSLColor.fromAHSL(0.4, 0, 0, 1).toColor(),
236+
labelMessageHeaderArchived: const HSLColor.fromAHSL(0.5, 0, 0, 0.5).toColor(),
235237
loginOrDivider: const Color(0xff424242),
236238
loginOrDividerText: const Color(0xffa8a8a8),
237239
modalBarrierColor: const Color(0xff000000).withValues(alpha: 0.5),
@@ -294,6 +296,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
294296
required this.groupDmConversationIcon,
295297
required this.groupDmConversationIconBg,
296298
required this.inboxItemIconMarker,
299+
required this.labelMessageHeaderArchived,
297300
required this.loginOrDivider,
298301
required this.loginOrDividerText,
299302
required this.modalBarrierColor,
@@ -364,6 +367,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
364367
final Color groupDmConversationIcon;
365368
final Color groupDmConversationIconBg;
366369
final Color inboxItemIconMarker;
370+
final Color labelMessageHeaderArchived;
367371
final Color loginOrDivider; // TODO(design-dark) need proper dark-theme color (this is ad hoc)
368372
final Color loginOrDividerText; // TODO(design-dark) need proper dark-theme color (this is ad hoc)
369373
final Color modalBarrierColor;
@@ -421,6 +425,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
421425
Color? groupDmConversationIcon,
422426
Color? groupDmConversationIconBg,
423427
Color? inboxItemIconMarker,
428+
Color? labelMessageHeaderArchived,
424429
Color? loginOrDivider,
425430
Color? loginOrDividerText,
426431
Color? modalBarrierColor,
@@ -477,6 +482,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
477482
groupDmConversationIcon: groupDmConversationIcon ?? this.groupDmConversationIcon,
478483
groupDmConversationIconBg: groupDmConversationIconBg ?? this.groupDmConversationIconBg,
479484
inboxItemIconMarker: inboxItemIconMarker ?? this.inboxItemIconMarker,
485+
labelMessageHeaderArchived: labelMessageHeaderArchived ?? this.labelMessageHeaderArchived,
480486
loginOrDivider: loginOrDivider ?? this.loginOrDivider,
481487
loginOrDividerText: loginOrDividerText ?? this.loginOrDividerText,
482488
modalBarrierColor: modalBarrierColor ?? this.modalBarrierColor,
@@ -540,6 +546,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
540546
groupDmConversationIcon: Color.lerp(groupDmConversationIcon, other.groupDmConversationIcon, t)!,
541547
groupDmConversationIconBg: Color.lerp(groupDmConversationIconBg, other.groupDmConversationIconBg, t)!,
542548
inboxItemIconMarker: Color.lerp(inboxItemIconMarker, other.inboxItemIconMarker, t)!,
549+
labelMessageHeaderArchived: Color.lerp(labelMessageHeaderArchived, other.labelMessageHeaderArchived, t)!,
543550
loginOrDivider: Color.lerp(loginOrDivider, other.loginOrDivider, t)!,
544551
loginOrDividerText: Color.lerp(loginOrDividerText, other.loginOrDividerText, t)!,
545552
modalBarrierColor: Color.lerp(modalBarrierColor, other.modalBarrierColor, t)!,

0 commit comments

Comments
 (0)