Skip to content

Commit 63925aa

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

16 files changed

+297
-72
lines changed

assets/l10n/app_en.arb

Lines changed: 4 additions & 0 deletions
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."

lib/generated/l10n/zulip_localizations.dart

Lines changed: 6 additions & 0 deletions
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:

lib/generated/l10n/zulip_localizations_ar.dart

Lines changed: 3 additions & 0 deletions
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

lib/generated/l10n/zulip_localizations_en.dart

Lines changed: 3 additions & 0 deletions
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

lib/generated/l10n/zulip_localizations_ja.dart

Lines changed: 3 additions & 0 deletions
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

lib/generated/l10n/zulip_localizations_nb.dart

Lines changed: 3 additions & 0 deletions
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

lib/generated/l10n/zulip_localizations_pl.dart

Lines changed: 3 additions & 0 deletions
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

lib/generated/l10n/zulip_localizations_ru.dart

Lines changed: 3 additions & 0 deletions
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

lib/generated/l10n/zulip_localizations_sk.dart

Lines changed: 3 additions & 0 deletions
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

lib/widgets/action_sheet.dart

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -228,53 +228,31 @@ void showTopicActionSheet(BuildContext context, {
228228
final pageContext = PageRoot.contextOf(context);
229229

230230
final store = PerAccountStoreWidget.of(pageContext);
231+
final channel = store.streams[channelId];
231232
final subscription = store.subscriptions[channelId];
232233

233234
final optionButtons = <ActionSheetMenuItemButton>[];
234235

235-
// TODO(server-7): simplify this condition away
236-
final supportsUnmutingTopics = store.zulipFeatureLevel >= 170;
237-
// TODO(server-8): simplify this condition away
238-
final supportsFollowingTopics = store.zulipFeatureLevel >= 219;
239-
240-
final visibilityOptions = <UserTopicVisibilityPolicy>[];
241-
final visibilityPolicy = store.topicVisibilityPolicy(channelId, topic);
242-
if (subscription == null) {
243-
// Not subscribed to the channel; there is no user topic change to be made.
244-
} else if (!subscription.isMuted) {
245-
// Channel is subscribed and not muted.
246-
switch (visibilityPolicy) {
247-
case UserTopicVisibilityPolicy.muted:
248-
visibilityOptions.add(UserTopicVisibilityPolicy.none);
249-
if (supportsFollowingTopics) {
250-
visibilityOptions.add(UserTopicVisibilityPolicy.followed);
251-
}
252-
case UserTopicVisibilityPolicy.none:
253-
case UserTopicVisibilityPolicy.unmuted:
254-
visibilityOptions.add(UserTopicVisibilityPolicy.muted);
255-
if (supportsFollowingTopics) {
256-
visibilityOptions.add(UserTopicVisibilityPolicy.followed);
257-
}
258-
case UserTopicVisibilityPolicy.followed:
259-
visibilityOptions.add(UserTopicVisibilityPolicy.muted);
260-
if (supportsFollowingTopics) {
261-
visibilityOptions.add(UserTopicVisibilityPolicy.none);
262-
}
263-
case UserTopicVisibilityPolicy.unknown:
264-
// TODO(#1074): This should be unreachable as we keep `unknown` out of
265-
// our data structures.
266-
assert(false);
267-
}
268-
} else {
269-
// Channel is muted.
270-
if (supportsUnmutingTopics) {
236+
final isChannelArchived = channel?.isArchived == true;
237+
if (!isChannelArchived) {
238+
// TODO(server-7): simplify this condition away
239+
final supportsUnmutingTopics = store.zulipFeatureLevel >= 170;
240+
// TODO(server-8): simplify this condition away
241+
final supportsFollowingTopics = store.zulipFeatureLevel >= 219;
242+
243+
final visibilityOptions = <UserTopicVisibilityPolicy>[];
244+
final visibilityPolicy = store.topicVisibilityPolicy(channelId, topic);
245+
if (subscription == null) {
246+
// Not subscribed to the channel; there is no user topic change to be made.
247+
} else if (!subscription.isMuted) {
248+
// Channel is subscribed and not muted.
271249
switch (visibilityPolicy) {
272-
case UserTopicVisibilityPolicy.none:
273250
case UserTopicVisibilityPolicy.muted:
274-
visibilityOptions.add(UserTopicVisibilityPolicy.unmuted);
251+
visibilityOptions.add(UserTopicVisibilityPolicy.none);
275252
if (supportsFollowingTopics) {
276253
visibilityOptions.add(UserTopicVisibilityPolicy.followed);
277254
}
255+
case UserTopicVisibilityPolicy.none:
278256
case UserTopicVisibilityPolicy.unmuted:
279257
visibilityOptions.add(UserTopicVisibilityPolicy.muted);
280258
if (supportsFollowingTopics) {
@@ -290,20 +268,46 @@ void showTopicActionSheet(BuildContext context, {
290268
// our data structures.
291269
assert(false);
292270
}
271+
} else {
272+
// Channel is muted.
273+
if (supportsUnmutingTopics) {
274+
switch (visibilityPolicy) {
275+
case UserTopicVisibilityPolicy.none:
276+
case UserTopicVisibilityPolicy.muted:
277+
visibilityOptions.add(UserTopicVisibilityPolicy.unmuted);
278+
if (supportsFollowingTopics) {
279+
visibilityOptions.add(UserTopicVisibilityPolicy.followed);
280+
}
281+
case UserTopicVisibilityPolicy.unmuted:
282+
visibilityOptions.add(UserTopicVisibilityPolicy.muted);
283+
if (supportsFollowingTopics) {
284+
visibilityOptions.add(UserTopicVisibilityPolicy.followed);
285+
}
286+
case UserTopicVisibilityPolicy.followed:
287+
visibilityOptions.add(UserTopicVisibilityPolicy.muted);
288+
if (supportsFollowingTopics) {
289+
visibilityOptions.add(UserTopicVisibilityPolicy.none);
290+
}
291+
case UserTopicVisibilityPolicy.unknown:
292+
// TODO(#1074): This should be unreachable as we keep `unknown` out of
293+
// our data structures.
294+
assert(false);
295+
}
296+
}
297+
}
298+
optionButtons.addAll(visibilityOptions.map((to) {
299+
return UserTopicUpdateButton(
300+
currentVisibilityPolicy: visibilityPolicy,
301+
newVisibilityPolicy: to,
302+
narrow: TopicNarrow(channelId, topic),
303+
pageContext: pageContext);
304+
}));
305+
306+
if (someMessageIdInTopic != null) {
307+
optionButtons.add(ResolveUnresolveButton(pageContext: pageContext,
308+
topic: topic,
309+
someMessageIdInTopic: someMessageIdInTopic));
293310
}
294-
}
295-
optionButtons.addAll(visibilityOptions.map((to) {
296-
return UserTopicUpdateButton(
297-
currentVisibilityPolicy: visibilityPolicy,
298-
newVisibilityPolicy: to,
299-
narrow: TopicNarrow(channelId, topic),
300-
pageContext: pageContext);
301-
}));
302-
303-
if (someMessageIdInTopic != null) {
304-
optionButtons.add(ResolveUnresolveButton(pageContext: pageContext,
305-
topic: topic,
306-
someMessageIdInTopic: someMessageIdInTopic));
307311
}
308312

309313
final unreadCount = store.unreads.countInTopicNarrow(channelId, topic);
@@ -558,14 +562,19 @@ void showMessageActionSheet({required BuildContext context, required Message mes
558562
final messageListPage = MessageListPage.ancestorOf(pageContext);
559563
final isComposeBoxOffered = messageListPage.composeBoxController != null;
560564

565+
bool isInArchivedChannel = false;
566+
if (message is StreamMessage) {
567+
final channel = store.streams[message.streamId];
568+
isInArchivedChannel = channel?.isArchived == true;
569+
}
561570
final isMessageRead = message.flags.contains(MessageFlag.read);
562571
final markAsUnreadSupported = store.zulipFeatureLevel >= 155; // TODO(server-6)
563572
final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead;
564573

565574
final optionButtons = [
566575
ReactionButtons(message: message, pageContext: pageContext),
567576
StarButton(message: message, pageContext: pageContext),
568-
if (isComposeBoxOffered)
577+
if (isComposeBoxOffered && !isInArchivedChannel)
569578
QuoteAndReplyButton(message: message, pageContext: pageContext),
570579
if (showMarkAsUnreadButton)
571580
MarkAsUnreadButton(message: message, pageContext: pageContext),

lib/widgets/inbox.dart

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ abstract class _HeaderItem extends StatelessWidget {
222222
final _InboxPageState pageState;
223223
final int count;
224224
final bool hasMention;
225+
final bool isArchived;
225226

226227
/// A build context within the [_StreamSection] or [_AllDmsSection].
227228
///
@@ -236,6 +237,7 @@ abstract class _HeaderItem extends StatelessWidget {
236237
required this.count,
237238
required this.hasMention,
238239
required this.sectionContext,
240+
this.isArchived = false,
239241
});
240242

241243
String title(ZulipLocalizations zulipLocalizations);
@@ -287,16 +289,33 @@ abstract class _HeaderItem extends StatelessWidget {
287289
const SizedBox(width: 5),
288290
Expanded(child: Padding(
289291
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)),
292+
child: RichText(
297293
maxLines: 1,
298294
overflow: TextOverflow.ellipsis,
299-
title(zulipLocalizations)))),
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+
if (isArchived)
306+
// TODO(#1285): Avoid concatenating translated strings
307+
WidgetSpan(
308+
child: Padding(
309+
padding: EdgeInsetsDirectional.only(start: 4),
310+
child: Text(
311+
zulipLocalizations.channelArchivedLabel,
312+
style: TextStyle(
313+
fontSize: 17,
314+
height: 20 / 17,
315+
// TODO(design) check if this is the right variable
316+
color: designVariables.labelMessageHeaderArchived,
317+
fontStyle: FontStyle.italic)))),
318+
])))),
300319
const SizedBox(width: 12),
301320
if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign),
302321
Padding(padding: const EdgeInsetsDirectional.only(end: 16),
@@ -444,6 +463,7 @@ class _StreamHeaderItem extends _HeaderItem with _LongPressable {
444463
required super.count,
445464
required super.hasMention,
446465
required super.sectionContext,
466+
required super.isArchived,
447467
});
448468

449469
@override String title(ZulipLocalizations zulipLocalizations) =>
@@ -495,6 +515,7 @@ class _StreamSection extends StatelessWidget {
495515
collapsed: collapsed,
496516
pageState: pageState,
497517
sectionContext: context,
518+
isArchived: subscription.isArchived,
498519
);
499520
return StickyHeaderItem(
500521
header: header,

lib/widgets/message_list.dart

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ class MessageListAppBarTitle extends StatelessWidget {
311311
ZulipStream? stream,
312312
}) {
313313
final zulipLocalizations = ZulipLocalizations.of(context);
314+
final designVariables = DesignVariables.of(context);
314315
// A null [Icon.icon] makes a blank space.
315316
final icon = stream != null ? iconDataForStream(stream) : null;
316317
return Row(
@@ -324,6 +325,17 @@ class MessageListAppBarTitle extends StatelessWidget {
324325
const SizedBox(width: 4),
325326
Flexible(child: Text(
326327
stream?.name ?? zulipLocalizations.unknownChannelName)),
328+
if (stream?.isArchived ?? false)
329+
// TODO(#1285): Avoid concatenating translated strings
330+
Padding(
331+
padding: EdgeInsetsDirectional.fromSTEB(4, 4, 0, 4),
332+
child: Text(
333+
zulipLocalizations.channelArchivedLabel,
334+
style: TextStyle(
335+
fontSize: 18,
336+
// TODO(design): check if this is the right variable
337+
color: designVariables.labelMessageHeaderArchived,
338+
fontStyle: FontStyle.italic))),
327339
]);
328340
}
329341

@@ -1099,6 +1111,18 @@ class StreamMessageRecipientHeader extends StatelessWidget {
10991111
style: recipientHeaderTextStyle(context),
11001112
overflow: TextOverflow.ellipsis),
11011113
),
1114+
if (stream?.isArchived ?? false)
1115+
// TODO(#1285): Avoid concatenating translated strings
1116+
Padding(
1117+
padding: const EdgeInsetsDirectional.fromSTEB(4, 4, 0, 4),
1118+
child: Text(
1119+
zulipLocalizations.channelArchivedLabel,
1120+
style: recipientHeaderTextStyle(context,
1121+
// TODO(design): check if this is the right variable
1122+
color: designVariables.labelMessageHeaderArchived,
1123+
fontStyle: FontStyle.italic),
1124+
overflow: TextOverflow.ellipsis,
1125+
maxLines: 1)),
11021126
Padding(
11031127
// Figma has 5px horizontal padding around an 8px wide icon.
11041128
// Icon is 16px wide here so horizontal padding is 1px.
@@ -1217,9 +1241,13 @@ class DmRecipientHeader extends StatelessWidget {
12171241
}
12181242
}
12191243

1220-
TextStyle recipientHeaderTextStyle(BuildContext context, {FontStyle? fontStyle}) {
1244+
TextStyle recipientHeaderTextStyle(
1245+
BuildContext context, {
1246+
Color? color,
1247+
FontStyle? fontStyle,
1248+
}) {
12211249
return TextStyle(
1222-
color: DesignVariables.of(context).title,
1250+
color: color ?? DesignVariables.of(context).title,
12231251
fontSize: 16,
12241252
letterSpacing: proportionalLetterSpacing(context, 0.02, baseFontSize: 16),
12251253
height: (18 / 16),

0 commit comments

Comments
 (0)