-
Notifications
You must be signed in to change notification settings - Fork 305
Better management of archived channel names #1344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
24ed3b6
to
432b103
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this! One more thing that I think needs to be handled is the topic actions sheet. None of the supported actions (follow/mute/unmute/resolve/unresolve) works on a topic in an archived channel. Not sure what's the best way to deal with this, though. It would be a good question to ask on #mobile-design.
lib/widgets/compose_box.dart
Outdated
if (channel == null || !store.hasPostingPermission(inChannel: channel, | ||
user: selfUser, byDate: DateTime.now())) { | ||
user: selfUser, byDate: DateTime.now()) || channel.isArchived) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's reformat this to show that these expressions are parallel:
if (channel == null || !store.hasPostingPermission(inChannel: channel, | |
user: selfUser, byDate: DateTime.now())) { | |
user: selfUser, byDate: DateTime.now()) || channel.isArchived) { | |
if (channel == null | |
|| !store.hasPostingPermission(inChannel: channel, | |
user: selfUser, byDate: DateTime.now()) | |
|| channel.isArchived) { |
test/widgets/compose_box_test.dart
Outdated
testWidgets('error banner is shown in $narrowType narrow', (tester) async { | ||
await prepareComposeBox(tester, | ||
narrow: narrow, | ||
selfUser: eg.user(role: UserRole.administrator), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the user role relevant here? If not, this can be removed.
lib/widgets/subscription_list.dart
Outdated
pinned.add(subscription); | ||
} else { | ||
unpinned.add(subscription); | ||
if(!subscription.isArchived) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we can use continue
here, so that the if
s don't need to be nested
lib/widgets/subscription_list.dart
Outdated
@@ -187,10 +189,13 @@ class _SubscriptionList extends StatelessWidget { | |||
|
|||
@override | |||
Widget build(BuildContext context) { | |||
// Filtering out the archived subscriptions. | |||
final activeSubscriptions = subscriptions.where((sub) => !sub.isArchived).toList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this line of code is pretty straightforward, and the comment doesn't add much here, so let's just leave it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, is it possible for subscriptions
to contain archived subscriptions, or is it the job of SubscriptionListPageBody
to ensure that no archived subscriptions are passed here?
If the latter, it might be better performance-wise to have an assertion that none of these subscriptions is archived, so that we don't need to re-filter the subscriptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah now that I check, its already being handled. removed this part.
eg.subscription(eg.stream(streamId: 1, isArchived: true), pinToTop: true), | ||
eg.subscription(eg.stream(streamId: 2), pinToTop: true), | ||
eg.subscription(eg.stream(streamId: 3, isArchived: true), pinToTop: false), | ||
eg.subscription(eg.stream(streamId: 4,), pinToTop: false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: extra comma
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also reorder the list to make it easier to read:
await setupStreamListPage(tester, subscriptions: [
eg.subscription(eg.stream(streamId: 1, isArchived: true), pinToTop: true),
eg.subscription(eg.stream(streamId: 2, isArchived: true), pinToTop: false),
eg.subscription(eg.stream(streamId: 3), pinToTop: true),
eg.subscription(eg.stream(streamId: 4), pinToTop: false),
]);
test/widgets/message_list_test.dart
Outdated
@@ -1062,7 +1085,7 @@ void main() { | |||
.initNarrow.equals(DmNarrow.withUser(eg.otherUser.userId, selfUserId: eg.selfUser.userId)); | |||
await tester.pumpAndSettle(); | |||
}); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks unintentional.
lib/widgets/message_list.dart
Outdated
fontStyle: FontStyle.italic, | ||
), | ||
), | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is a specific design that we are following here (and other UI changes), it would be helpful to have a link in the commit message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There isn't a particular design that i could reference. I'm trying to keep it consistent with web.
test/widgets/inbox_test.dart
Outdated
@@ -595,6 +616,36 @@ void main() { | |||
check(rectAfterTap).equals(rectBeforeTap); | |||
}); | |||
|
|||
testWidgets('shows archived label for archived streams', (tester) async { | |||
final zulipLocalizations = GlobalLocalizations.zulipLocalizations; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For tests, it is fine to not use ZulipLocalizations
and have plain strings unless we are specifically testing something related to l10n. This way we have a better idea of what exactly we are expecting in tests.
]); | ||
|
||
// Only non-archived streams | ||
check(getItemCount()).equals(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit ambiguous because it doesn't check if the remaining streams are the ones that are not archived.
lib/api/model/model.dart
Outdated
@@ -347,6 +347,8 @@ class ZulipStream { | |||
// TODO(server-8): added in FL 199, was previously only on [Subscription] objects | |||
int? streamWeeklyTraffic; | |||
|
|||
final bool isArchived; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Make sure to match the order of these fields in the API documentation. See the dartdoc of ZulipStream
to find the relevant piece.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to be the order in the API documentation as I checked. Please let me know if its not
https://zulip.com/api/register-queue and go to subscriptions
return value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. The location the dartdoc points to has a different order. Let's match that:
/// For docs, search for "if stream"
/// in <https://zulip.com/api/register-queue>.
@JsonSerializable(fieldRename: FieldRename.snake)
class ZulipStream {
1a8ae0f
to
1bc6ec1
Compare
1bc6ec1
to
f45792c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update! Left some more comments.
@@ -84,6 +84,7 @@ class _SubscriptionListPageBodyState extends State<SubscriptionListPageBody> wit | |||
final List<Subscription> pinned = []; | |||
final List<Subscription> unpinned = []; | |||
for (final subscription in store.subscriptions.values) { | |||
if (subscription.isArchived) continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make sure that _SubscriptionList
checks for this with an assertion in its build method.
lib/widgets/message_list.dart
Outdated
Padding( | ||
padding: const EdgeInsetsDirectional.fromSTEB(4, 4, 0, 4), | ||
child: Text( | ||
' ${zulipLocalizations.channelArchivedLabel}', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the other place, #1285 is applicable here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also mention that the design is inspired by web in the commit message.
lib/widgets/message_list.dart
Outdated
style: recipientHeaderTextStyle(context).copyWith( | ||
color: messageListTheme.streamRecipientHeaderChevronRight, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of calling copyWith
immediately after getting the TextStyle
from the helper, let's add color
to that helper so we can do it in a single call.
lib/widgets/message_list.dart
Outdated
Padding( | ||
padding: EdgeInsetsDirectional.fromSTEB(4, 4, 0, 4), | ||
child: Text( | ||
' ${zulipLocalizations.channelArchivedLabel}', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar issue here.
test/widgets/inbox_test.dart
Outdated
widget.text.toPlainText().contains(stream.name) | ||
), | ||
); | ||
expect(richTextFinder, findsOneWidget); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use check
in our codebase instead. This one can be written as check(richTextFinder).findsOne()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment also applies to other similar places in the PR, so please look for them and update.
test/widgets/inbox_test.dart
Outdated
final richTextFinder = find.descendant( | ||
of: find.byWidget(headerRowFinder!), | ||
matching: find.byWidgetPredicate((widget) => | ||
widget is RichText && | ||
widget.text.toPlainText().contains(stream.name) | ||
), | ||
); | ||
expect(richTextFinder, findsOneWidget); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the implementation of findStreamHeaderRow
, this stanza seems to be repetitive. Let's just remove this check. Another benefit of removing this is that we can focus more on checking the presence of the "(archived)" label.
test/widgets/inbox_test.dart
Outdated
// Check that the Text widget has italic style | ||
final archivedLabelText = tester.widget<Text>(archivedLabelFinder); | ||
expect(archivedLabelText.style?.fontStyle, FontStyle.italic); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We usually leave out checks for specific styling unless there is some extra logic to it (changing style dynamically to different inputs, etc.)
test/widgets/message_list_test.dart
Outdated
@@ -1101,6 +1101,28 @@ void main() { | |||
await tester.pump(); | |||
check(pushedRoutes).isEmpty(); | |||
}); | |||
|
|||
testWidgets('shows archived label for archived streams', (tester) async { | |||
final stream = eg.stream(streamId: 1, name: 'stream name', isArchived: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, we can remove streamId
here.
test/widgets/compose_box_test.dart
Outdated
testWidgets('error banner is shown in $narrowType narrow', (tester) async { | ||
await prepareComposeBox(tester, | ||
narrow: narrow, | ||
selfUser: eg.user(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove selfUser
if the tests do not rely on it.
testWidgets('archived subscriptions are filtered out', (tester) async { | ||
await setupStreamListPage(tester, subscriptions: [ | ||
eg.subscription(eg.stream(streamId: 1, isArchived: true), pinToTop: true), | ||
eg.subscription(eg.stream(streamId: 2, isArchived: true), pinToTop: false), | ||
eg.subscription(eg.stream(streamId: 3), pinToTop: true), | ||
eg.subscription(eg.stream(streamId: 4), pinToTop: false), | ||
]); | ||
|
||
check(getItemCount()).equals(2); | ||
check(isPinnedHeaderInTree()).isTrue(); | ||
check(isUnpinnedHeaderInTree()).isTrue(); | ||
|
||
check(find.text('stream 1')).findsNothing(); | ||
check(find.text('stream 2')).findsNothing(); | ||
|
||
check(find.text('stream 3')).findsOne(); | ||
check(find.text('stream 4')).findsOne(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Order this after "only unpinned subscriptions" because "basic subscriptions", "only pinned subscriptions" are more similar to that test and they should be next to each other.
While addressing the review comments, a useful thing to do (and often necessary) is to try to find other places where the comment applies to during your self-reviews. This way we can save some back-and-forth revisions when there are some remaining known issues. |
f45792c
to
63925aa
Compare
@PIG208 Pushed a revision. PTAL. Removed unsupported options from the channel and message action sheets for archived streams. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update! I just went through it again. Left some comments.
lib/widgets/action_sheet.dart
Outdated
final isChannelArchived = channel?.isArchived == true; | ||
if (!isChannelArchived) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's try to avoid making these checks more nested. We should keep them flat for readability.
I think the existing if/else structure makes it convenient to also check if the channel is archived; if it is, we just skip adding to visibilityOptions
.
This also applies to ResolveUnresolveButton
separately.
lib/widgets/inbox.dart
Outdated
@@ -236,6 +237,7 @@ abstract class _HeaderItem extends StatelessWidget { | |||
required this.count, | |||
required this.hasMention, | |||
required this.sectionContext, | |||
this.isArchived = false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it doesn't feel quite right for isArchived
to be on _HeaderItem
, since _AllDmsHeaderItem
subclasses this and cannot be archived (which is why the default value is needed).
_HeaderItem
can gain an abstract InlineSpan? buildTrailing(BuildContext context)
method. If buildTrailing
returns null
, then the marker is not built at all. _StreamHeaderItem
will override it to build "(archieved)" following the title.
Meanwhile, we can move isArchived
to _StreamHeaderItem
and make it a required
parameter without a default value.
You can find an example of this setup from _MenuButton.buildLeading
.
test/widgets/action_sheet_test.dart
Outdated
await tester.longPress(find.text(someChannel.name).hitTestable()); | ||
final row = findRowByLabel(tester, someChannel.name); | ||
check(row).isNotNull(); | ||
final materialFinders = find.ancestor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: materialFinders
-> materialFinder
(there is just a singular Finder
)
test/widgets/action_sheet_test.dart
Outdated
await setupToMessageActionSheet(tester, | ||
message: message, | ||
narrow: TopicNarrow.ofMessage(message)); | ||
check(findQuoteAndReplyButton(tester)).isNotNull(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check seems contradictory to the name of the test, which claims that quote-and-reply is "not offered in archived channels". Perhaps either the implementation or the test is not working as intended?
test/widgets/action_sheet_test.dart
Outdated
final row = findRowByLabel(tester, someChannel.name); | ||
check(row).isNotNull(); | ||
final materialFinders = find.ancestor( | ||
of: find.byWidget(row!), | ||
matching: find.byType(Material)).hitTestable(); | ||
await tester.longPress(materialFinders.first); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or perhaps it is better to simplify this to:
final row = findRowByLabel(tester, someChannel.name); | |
check(row).isNotNull(); | |
final materialFinders = find.ancestor( | |
of: find.byWidget(row!), | |
matching: find.byType(Material)).hitTestable(); | |
await tester.longPress(materialFinders.first); | |
await tester.longPress( | |
find.textContaining(findRichText: true, someChannel.name).first); |
and then we can remove the findRowByLabel
helper.
test/widgets/action_sheet_test.dart
Outdated
group('archived channels', () { | ||
testWidgets('limited topic actions for archived channels', (tester) async { | ||
final archivedChannel = eg.stream(isArchived: true); | ||
final someTopic = 'my topic'; | ||
|
||
final message = eg.streamMessage( | ||
stream: archivedChannel, | ||
topic: someTopic, | ||
flags: []); | ||
|
||
await prepare( | ||
channel: archivedChannel, | ||
topic: someTopic, | ||
isChannelSubscribed: true, | ||
isChannelMuted: false, | ||
visibilityPolicy: UserTopicVisibilityPolicy.none, | ||
unreadMsgs: eg.unreadMsgs(channels: [ | ||
eg.unreadChannelMsgs( | ||
streamId: archivedChannel.streamId, | ||
topic: someTopic, | ||
unreadMessageIds: [message.id]), | ||
])); | ||
|
||
await showFromAppBar(tester, | ||
channel: archivedChannel, | ||
topic: someTopic, | ||
messages: [message]); | ||
check(mute).findsNothing(); | ||
check(unmute).findsNothing(); | ||
check(follow).findsNothing(); | ||
check(unfollow).findsNothing(); | ||
|
||
check(findButtonForLabel('Mark as resolved')).findsNothing(); | ||
check(findButtonForLabel('Mark as unresolved')).findsNothing(); | ||
|
||
check(findButtonForLabel('Mark topic as read')).findsOne(); | ||
}); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can simplify this test by extending and making use of the existing helper:
diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart
index a44b20f71..9624c93a3 100644
--- a/test/widgets/action_sheet_test.dart
+++ b/test/widgets/action_sheet_test.dart
@@ -494,15 +494,17 @@ void main() {
/// If `isChannelMuted` is `null`, the user is not subscribed to the
/// channel.
Future<void> setupToTopicActionSheet(WidgetTester tester, {
+ ZulipStream? channel,
required bool? isChannelMuted,
required UserTopicVisibilityPolicy visibilityPolicy,
int? zulipFeatureLevel,
}) async {
addTearDown(testBinding.reset);
+ channel ??= someChannel;
topic = 'isChannelMuted: $isChannelMuted, policy: $visibilityPolicy';
await prepare(
- channel: someChannel,
+ channel: channel,
topic: topic,
isChannelSubscribed: isChannelMuted != null, // shorthand; see dartdoc
isChannelMuted: isChannelMuted,
@@ -511,9 +513,10 @@ void main() {
);
final message = eg.streamMessage(
- stream: someChannel, topic: topic, sender: eg.otherUser);
+ stream: channel, topic: topic, sender: eg.otherUser);
+ await store.handleEvent(MessageEvent(id: 1, message: message));
await showFromAppBar(tester,
- channel: someChannel, topic: topic, messages: [message]);
+ channel: channel, topic: topic, messages: [message]);
}
void checkButtons(List<Finder> expectedButtonFinders) {
@@ -619,43 +622,14 @@ void main() {
}
});
- group('archived channels', () {
- testWidgets('limited topic actions for archived channels', (tester) async {
- final archivedChannel = eg.stream(isArchived: true);
- final someTopic = 'my topic';
+ testWidgets('limited topic actions for archived channels', (tester) async {
+ final archivedChannel = eg.stream(isArchived: true);
- final message = eg.streamMessage(
- stream: archivedChannel,
- topic: someTopic,
- flags: []);
-
- await prepare(
- channel: archivedChannel,
- topic: someTopic,
- isChannelSubscribed: true,
- isChannelMuted: false,
- visibilityPolicy: UserTopicVisibilityPolicy.none,
- unreadMsgs: eg.unreadMsgs(channels: [
- eg.unreadChannelMsgs(
- streamId: archivedChannel.streamId,
- topic: someTopic,
- unreadMessageIds: [message.id]),
- ]));
-
- await showFromAppBar(tester,
- channel: archivedChannel,
- topic: someTopic,
- messages: [message]);
- check(mute).findsNothing();
- check(unmute).findsNothing();
- check(follow).findsNothing();
- check(unfollow).findsNothing();
-
- check(findButtonForLabel('Mark as resolved')).findsNothing();
- check(findButtonForLabel('Mark as unresolved')).findsNothing();
-
- check(findButtonForLabel('Mark topic as read')).findsOne();
- });
+ await setupToTopicActionSheet(tester,
+ channel: archivedChannel,
+ isChannelMuted: false,
+ visibilityPolicy: UserTopicVisibilityPolicy.none);
+ checkButtons([]);
});
While writing tests like this, it is usually helpful to see how the adjacent tests are written, and try to reuse the test helpers. Not only does that simplify the test, it also makes them look more coherent with the surrounding tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proposed test only checks the buttons relevant to the test group ("UserTopicUpdateButton") this is in, as I feel that checks for the other buttons like "Mark as resolved/unresolved" belong to the other corresponding test groups like "ResolveUnresolveButton".
test/widgets/action_sheet_test.dart
Outdated
testWidgets('offered in non-archived channels', (tester) async { | ||
final channel = eg.stream(isArchived: false); | ||
final message = eg.streamMessage(stream: channel); | ||
|
||
await setupToMessageActionSheet(tester, | ||
message: message, | ||
narrow: TopicNarrow.ofMessage(message)); | ||
check(findQuoteAndReplyButton(tester)).isNotNull(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the other tests above do not have a test for the opposite case because it is covered in the earlier tests that assume the channel is not archived (which is the boring/default case). We can drop this test and then flatten the group.
test/widgets/inbox_test.dart
Outdated
await tester.pumpAndSettle(); | ||
final headerRow = findStreamHeaderRow(tester, stream.streamId); | ||
check(headerRow).isNotNull(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove this empty line as the lines before/after it are relevant.
@@ -1131,6 +1131,19 @@ void main() { | |||
await tester.pump(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also needs a test for the app bar.
This enables the client to receive metadata about archived streams in the register response and events, allowing it to handle them properly.
63925aa
to
a045e5c
Compare
Design inspired by the web version. Fixes zulip#800
a045e5c
to
5390571
Compare
Pull Request
Description
This PR improves archived stream support in the mobile app to match the web app’s behavior. Key updates include:
Related Issues
Screenshots