-
Notifications
You must be signed in to change notification settings - Fork 237
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
Implement bottom tabs and main menu #1076
Conversation
eb8c896
to
5c927c1
Compare
(I turned the link to #1035 into the kind GitHub recognizes specially; that's helpful for example because it makes it appear in the issue's row on the project board). I realize the PR isn't ready to be merged as our solution to the issue, but that's expressed by its draft status; and I believe it is intended to be the PR that fixes the issue.) |
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.
Exciting! Looking forward to getting this landed.
Comments below on the draft.
The important high-level point (of which some corollaries appear below) is: the priority with this PR is to get to something we can merge in time for the blog post about the beta.
That means a version that has all the essential functionality, looks reasonable, and meets our usual standards for the code and tests; but any aspects of the design that turn out to be tricky to implement are a lower priority than getting it merged. Because the issue is complex already, anything that's tricky and not essential should be punted until everything essential is complete.
For a concrete timeline: the target for the blog post is by Dec 12, the week after next. I think that calls for a version ready for review by the first half of next week, to give time for review, revisions, and any follow-ups we realize we need after getting to use this a bit.
lib/widgets/home.dart
Outdated
child: Padding( | ||
padding: const EdgeInsets.only(top: 6), | ||
child: Row(children: [ | ||
// TODO: fetch organization icon and display name from the server settings |
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.
To be sure we're on the same page: this TODO is out of scope for now, as #1037:
// TODO: fetch organization icon and display name from the server settings | |
// TODO(#1037): fetch organization icon and display name from the server settings |
(or just leave out the TODO)
lib/widgets/home.dart
Outdated
} | ||
|
||
class _OrganizationsButton extends StatelessWidget { | ||
const _OrganizationsButton(); |
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.
lib/widgets/home.dart
Outdated
} | ||
} | ||
|
||
class _VersionInfo extends StatelessWidget { |
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 fine but it's also thoroughly dispensable for getting a mergeable version of this PR 🙂 So to the extent there are any open questions remaining on this widget, let's leave it out of scope.
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.
Opened #1095 as a follow-up.
lib/widgets/home.dart
Outdated
}), | ||
side: WidgetStateBorderSide.fromMap({ | ||
WidgetState.pressed: null, | ||
~WidgetState.pressed: (selected) ? borderSide : null, |
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 but I see a lot of examples of it:
~WidgetState.pressed: (selected) ? borderSide : null, | |
~WidgetState.pressed: selected ? borderSide : null, |
The ternary operator ?:
is just an operator — it doesn't call for any of its operands to necessarily get wrapped in parens, any more than any other operator does. An operand needs parens only when it has internal structure that would otherwise get split up, or where a reader is likely to be unsure if it gets split up.
(This operator is a little special semantically in that it short-circuits. But so do the operators &&
, ||
, and ??
, and we don't ordinarily wrap their first operands in parens either.)
lib/widgets/home.dart
Outdated
@override | ||
// TODO: implement icon | ||
IconData get icon => ZulipIcons.attach_file; | ||
|
||
@override | ||
String label(ZulipLocalizations zulipLocalizations) { | ||
return 'Attach file'; | ||
} | ||
|
||
@override | ||
void onPressed() { | ||
// TODO: implement onPressed | ||
} |
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.
Probably a good structure for these is to have a subclass for each item in the menu, similar to MessageActionSheetMenuItemButton
, and implement these there.
The subclasses will be a bit shorter than in the message action sheet, because onPressed
will mostly just call Navigator.push with a result of SomePage.buildRoute; but between that and looking up the label on the localizations object, I think they'll be bulky enough that a subclass is more convenient than trying to cram all the details into a big list in showMenu
. (There'll still be a list but it won't say much more than the names of each of the subclasses; just that and the non-static details like selected
and pageContext
.)
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.
Oh and I guess there'll also be a method or getter to compute what counter to show, if any.
Maybe a good structure for that is a method like Widget? buildTrailing(BuildContext context)
. Then most subclasses will return a _MenuItemCounter
, or null; but e.g. the "Notifications" row (in the future! it's out of scope for this PR) can return that yellow warning icon.
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 the record, we decided to defer the feature to #1088.
lib/widgets/home.dart
Outdated
} | ||
} | ||
|
||
class NavigationButton extends StatelessWidget { |
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: put this just after _HomePageState, since it's closely tied to that
lib/widgets/home.dart
Outdated
bool handleScroll(ScrollNotification notification) { | ||
animationController.paused = notification.metrics.extentBefore != 0; | ||
return 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.
wip; nav: Disable bottom sheet dragging when scrolled
I think the behavior before this commit is fine and actually a bit nicer. So let's leave out this complication.
I gather you're referring to this line in the Figma:
It would be nice if swipe down can close the menu, but it is important to do that only, when content is scrolled to its top position. And it isn’t just a swipe gesture but full menu dragging
But I think the thing Vlad means is just: if you've scrolled down in the content and start swiping down, it should scroll you back up in the content (just like normal for a scrollable) instead of having the menu start closing.
When I run with the version before this commit, that all works fine: if you're scrolled down, then swiping anywhere within the scrollable acts on the scrollable before doing anything to the menu as a whole. If you touch the header and drag that, then that does control the menu… but I think that's fine and actually good. After this commit, attempting to drag the header doesn't do anything if the options are scrolled, which to me feels oddly inert like something's not working.
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.
Yeah, I agree that leaving this out is better.
It would be nice if swipe down can close the menu
I think to fully implement this (out of scope) feature, we will need to allow over scrolling the inner content (swiping down the menu items and reaching the top of the menu) to drag the bottom sheet; once you scroll pass a certain extent the menu collapses, as it does when you control it by dragging the header. I guess the menu would otherwise rebound back in position if you release it.
It makes sense to leave this out because it will be quite complicated. An implementation might have to listen to notifications like OverscrollNotification
, ScrollEndNotification
and replicate the drag handlers from _BottomSheetState
and manipulate the animation controller.
f8a9e5a
to
15503a5
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.
I've read all the commits except the main commit:
nav: Add bottom tabs and main menu
which I've skimmed a bit. And I haven't done a detailed check against the Figma. But it's time for bed and I wanted to get a review in today. 🙂
Some things I noticed in manual testing:
-
If I tap "Switch account" from the new bottom sheet, then go back (with the back button or iOS swipe gesture), the bottom sheet is still open. Nicer to close it when you tap that button, I think. (Same for some other buttons like Mentions.)
-
If I do that after logging out the account I was looking at, I get an infinite loading spinner. This is what
PerAccountStoreWidget.routeToRemoveOnLogout
is meant to prevent. Can we use that directly, or do we need some changes to support auto-dismissing an action sheet on logout?If I close that action sheet (by dragging down), what's supposed to happen? I get a black screen:
Is there a route (in addition to the action sheet) that didn't get removed on logout, because it didn't use
PerAccountStoreWidget.routeToRemoveOnLogout
? —Ah, or is this what the app looks like when there are no routes in the stack? Can we listen for that condition (no routes) and respond by basically reapplyingMaterialApp.onGenerateInitialRoutes
? That should give you the choose-account page. This way might be nice because it also supports cases where you logged out without requesting it in the UI, like (not yet implemented) when we learn your auth is invalid. -
When you tap an account on the "Choose account" screen, I notice that a route is added to the stack, on top of the "Choose account" screen, which lets me go back to that screen. Instead, can we clear anything on the stack and replace it with just the bottom-tabs screen for the chosen account? The issue says 'there's no navigating "back" or "up" from [the screen with the bottom tab bar].'
lib/widgets/inbox.dart
Outdated
@@ -14,7 +14,7 @@ import 'text.dart'; | |||
import 'theme.dart'; | |||
import 'unread_count_badge.dart'; | |||
|
|||
class InboxPage extends StatefulWidget { | |||
class InboxPage extends StatelessWidget { |
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.
nav [nfc]: Extract *PageBody widgets
This commit introduces a new pattern but doesn't use it in all the places it could. (I see at least MessageListPage
is untouched.) That's fine, but the commit could be clearer that it's not focused on converting everything. Maybe:
nav [nfc]: Extract some *PageBody widgets
lib/widgets/action_sheet.dart
Outdated
).copyWith(backgroundColor: WidgetStateColor.fromMap({ | ||
WidgetState.pressed: designVariables.contextMenuCancelPressedBg, | ||
~WidgetState.pressed: designVariables.contextMenuCancelBg, | ||
})), |
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.
Cool!
assets/l10n/app_en.arb
Outdated
@@ -15,8 +15,8 @@ | |||
"@aboutPageTapToView": { | |||
"description": "Item subtitle in About Zulip page to navigate to Licenses page" | |||
}, | |||
"chooseAccountPageTitle": "Choose account", | |||
"@chooseAccountPageTitle": { | |||
"switchAccountPageTitle": "Switch account", |
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.
i10n: Change label from "Choose account" to "Switch account"
There are a bunch of other places where we still say "choose account":
git grep --ignore-case choose.?account
Also, what is "i10n", do you mean 'internationalization' ("intl") or localization ("l10n")? The interesting change isn't really about either. The commit implements a product choice, changing a UI string, with some boring/expected changes in assets/l10n
, like we make every time we change a UI string.
We could use ui:
, I guess, or (from a quick skim through this file's history):
switch-account: Rename "Choose account" page to "Switch Account"
Also, why do we make this choice? :) #1035 proposes a "Switch account" button on the new modal bottom sheet, but doesn't propose changing the choose-account page that it leads to:
For now the menu will also have a "Switch account" option at the bottom, which just leads to the choose-account page.
I think "Switch account" is appropriate for the menu button, but less appropriate for the screen with zero, one, or more accounts to choose from. "Switch account" sounds like you're in one account's context and you want to change to another. If you're seeing this page the first time you open the app, that won't be the case.
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.
Also, what is "i10n", do you mean 'internationalization' ("intl") or localization ("l10n")?
Yeah, that's a typo. It ought to be "l10n" but I mixed up with i18n
. In this PR we have been using the same strings for the page titles and the menu buttons. The button is labelled "Switch account", hence the change.
assets/l10n/app_en.arb
Outdated
@@ -529,6 +529,10 @@ | |||
"@userRoleUnknown": { | |||
"description": "Label for UserRole.unknown" | |||
}, | |||
"inboxButton": "Inbox", | |||
"@inboxButton": { | |||
"description": "Label for the menu button switching to inbox." |
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: it doesn't switch to the inbox if you're already there :) how about:
Bottom-tab navigation button for the 'Inbox' page.
(Similarly for the other strings like this.)
Or probably better:
"inboxPageTitle": "Inbox",
"@inboxPageTitle": {
"description": "Title for the 'Inbox' page."
},
Then we'll have one canonical name that identifies the page, for here and wherever else we need to do that. I see we don't need to do that in an app bar, because this page doesn't have an app bar. But there are probably other places we'll want to, like to structure the UI well for screen-reader software, perhaps with SemanticsProperties.namesRoute
. How about adding a TODO(a11y)
to find out if we're meeting users' expectations when they use Zulip with a screen reader, mentioning SemanticsProperties.namesRoute
?
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.
Updating this with the description altered to match the other existing page titles.
I guess this would also be a reason to also name the button as "Choose account" for the "Choose account" page. Both of them are temporary, and are subject to removal once we get to #1038.
The documentation for SemanticsProperties.namesRoute
doesn't seem quite clear on how it can be used. I assume that we need to use Semantics
with it? Because this might be a non-trivial project on its own, I feel that it might fit better as a separate issue.
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 guess this would also be a reason to also name the button as "Choose account" for the "Choose account" page.
As I said in #1076 (comment), the proposal in #1035 is to label the menu button "Switch account" without changing the page's name "Choose account". For why "Choose account" makes sense to me for the page's name:
"Switch account" sounds like you're in one account's context and you want to change to another. If you're seeing this page the first time you open the app, that won't be the case.
For this menu button, I think the "Switch account" label is helpful in orienting the user (reinforcing that they're in one account's context and could move to another), so worth keeping. If we think of it as an action button that happens to act by presenting a relevant page, then I think it's fine that its label doesn't match that page's title. When the user arrives at "Choose account", it will feel natural and expected. The page's app bar (and a Semantics
too?) will tell the user what page they're on, for as long as they're on the page, and we don't rely on this bottom-sheet button for that. We would if it were a bottom-tabs button, but it's not.
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 documentation for
SemanticsProperties.namesRoute
doesn't seem quite clear on how it can be used. I assume that we need to useSemantics
with it? Because this might be a non-trivial project on its own, I feel that it might fit better as a separate issue.
Yes, definitely fine to leave this for later 🙂:
How about adding a
TODO(a11y)
to find out if we're meeting users' expectations when they use Zulip with a screen reader, mentioningSemanticsProperties.namesRoute
?
You could file an issue if you want, but I think the TODO is fine and better than nothing.
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 found #535 to be relevant:
We should try out the app with iOS's VoiceOver and/or Android's TalkBack, and make sure it's possible to do the essentials to be able to use the app at all:
- get between the different top-level pages (Inbox, All messages, Subscribed streams, Direct messages)
Except that the top-level pages become the tabs on the new home screen. With an app bar it might become less of a concern, but still it would be helpful to experiment with this before launch. Updated the TODO comment to refer to this issue.
lib/widgets/page.dart
Outdated
appBar: AppBar(), | ||
body: const LoadingPlaceholder(), | ||
); | ||
return const Scaffold(body: LoadingPlaceholder()); |
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.
nav: Remove app bar from LoadingPlaceholderPage
The home page when loaded does not have an app bar, so be consistent
with that.
Signed-off-by: Zixuan James Li <zixuan@zulip.com>
This makes LoadingPlaceholderPage
inconsistent with the other pages it's used for that do have app bars, like the message list. But we don't have a design for this loading page 🤷♂️ and I think I'm agnostic about whether it should have an app bar or not; sure, fine to remove.
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 main reason this loading page has an app bar is to make it possible to back out of it (on iOS where you don't have a system back gesture/button) so e.g. you can go use some other account.
With the new nav, backing out wouldn't get you to ChooseAccountPage anyway because HomePage is the root of the navigation. But it'll be important to still have some way to switch accounts from the loading page.
I think that means we want two things here:
- LoadingPlaceholderPage, which is used by AccountPageRouteMixin in general, should continue to have an app bar. That way if you wind up on a page that isn't HomePage, e.g. by opening a notification, and it's stuck loading, you can back out.
- HomePage.buildRoute should provide some alternative loading page that's specific to HomePage. That alternative will provide a way to switch accounts, which can be ad hoc like a button in the bottom right (that opens ChooseAccountPage).
lib/widgets/home.dart
Outdated
for (final (tab, body) in pageBodies) | ||
Offstage(offstage: tab != _tab.value, child: body), | ||
]), | ||
bottomNavigationBar: SafeArea( |
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 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, thanks for testing that out. I found out today that while my Android device has safe area, those area aren't visible at all. Maybe there's still a way to test it without an iOS device?
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.
Maybe there's still a way to test it without an iOS device?
A good way to do that is to use the Android emulator. It has options to simulate a variety of shapes of insets.
return Scaffold( | ||
appBar: ZulipAppBar(title: const Text("Home")), |
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's a top bar in the Figma:
Ahh but I see #1035 says "There's no top bar" in the issue description. Fair enough 🤷♂️ but let's mention it in the commit message's list of differences from the Figma. Also, now we don't have a spec for what goes in the top inset; here's this revision:
Not scrolled | Scrolled a bit |
---|---|
The top inset is filled with the same color we use under the content in the y-direction when the list is short. I don't love that; it's like the content just abruptly disappears, instead of sliding under some different surface (what used to be the app bar). I don't know what's best here, perhaps just leave a TODO(design)
for how to fill the top inset on these pages.
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.
Yeah, that will be a separate redesign project. I recalled that we should have an issue for the top bar other than #1039, but I couldn't find it.
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 have an app bar at the top after all. (It'll be pretty plain — just a title, I think.)
I'm not sure what my thinking was when I said in the issue there's no top bar. But I see that's a bit of a challenge what should happen on scroll, if there's no app bar; and I just went and looked through several apps on my phone that have a bottom nav bar, and all of them have an app bar on those screens. In a couple of cases the app bar goes away as you scroll (making more room for the scrolling content), but that's trickier; let's skip that at least for launch, and have the app bar remain fixed at the top on scroll.
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 went to go update #1035 with this change, and rereading the context of that sentence I realized what happened: what I really meant was that there's no top tab bar. I probably didn't consider at that moment the question of whether there should or shouldn't be a plain title-bearing app bar instead.
Anyway, fixed there.
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.
To help accelerate things for this PR, here's a review of some commits that I think we can merge early. @PIG208 would you send a separate PR with these, after revision for the nit below and applicable comments by @chrisbobbe above?
48671b2 nav [nfc]: Extract *PageBody widgets
70777ba nav [nfc]: Move HomePage to a new home
8488162 action_sheet [nfc]: Centralize .withValues variants of contextMenuCancelBg
6d9037b nav: Add icons from Figma
In particular I think some of those are likely to be conflict-prone — icons changes always are, and theme changes often seem to be — so it's helpful to have them merged early.
lib/widgets/icons.dart
Outdated
/// The Zulip custom icon "contacts". | ||
static const IconData contacts = IconData(0xf109, fontFamily: "Zulip Icons"); |
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:
nav: Add icons from Figma
The icons are taken from
https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=544-22131&node-type=canvas&t=f4vYPq2CZUpFuf7V-0.
Write instead:
icons: Add icons for bottom nav bar
The icons are taken from Figma:
https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=544-22131&node-type=canvas&t=f4vYPq2CZUpFuf7V-0
Avoiding .
there avoids ambiguity about what the URL is.
The summary line can be more specific than "nav" about what the icons are; there are other icons involved in nav. And our usual prefix for editing icons is "icons:".
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.
Further in the interest of accelerating things for this PR, here's an initial review from me of the main commit:
6e8de91 nav: Add bottom tabs and main menu
I skipped points that are already discussed in Chris's review above.
lib/widgets/home.dart
Outdated
child: Align( | ||
// This is necessary to limit the width of the row of icons, | ||
// because [Align] allows the child to be smaller than itself. | ||
heightFactor: 1, |
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 don't follow this comment or what this Align widget is doing. A height factor is necessary to limit the width of the row?
Why Align and not Center?
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.
Moved this comment above and added another one explaining the heightFactor
. Also changed it to use Center
instead.
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.
Much clearer, thanks!
lib/widgets/home.dart
Outdated
// because [Align] allows the child to be smaller than itself. | ||
heightFactor: 1, | ||
child: ConstrainedBox( | ||
// TODO(design): determine a suitable max width |
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:
// TODO(design): determine a suitable max width | |
// TODO(design): determine a suitable max width for bottom nav bar |
That way it's easier to tell what this is about when grepping for TODO, or for TODO.design
.
lib/widgets/home.dart
Outdated
} | ||
} | ||
|
||
class NavigationButton extends StatelessWidget { |
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:
class NavigationButton extends StatelessWidget { | |
class _NavigationBarButton extends StatelessWidget { |
We have many buttons that are used for navigation; these are just those that appear in this particular context.
lib/widgets/home.dart
Outdated
child: Column( | ||
crossAxisAlignment: CrossAxisAlignment.stretch, | ||
mainAxisSize: MainAxisSize.max, |
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:
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.stretch, | |
mainAxisSize: MainAxisSize.max, | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.stretch, |
This is the default and we normally don't repeat it.
lib/widgets/home.dart
Outdated
child: ConstrainedBox( | ||
constraints: const BoxConstraints(minHeight: 44), | ||
child: const ActionSheetCancelButton()))), |
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.
Seems like this min-height should be the button's business.
Can it really be shorter than this anyway? I'd think TextButton (which this button uses) would take care of that.
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 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.
(Are the labels switched on those screenshots? The one labeled "w/ minHeight" has the button shorter than the other does.)
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.
Following up on this point:
I'd think TextButton (which this button uses) would take care of that.
by looking into TextButton, it looks like this is controlled by ButtonStyle.minimumSize
. The default for which in TextButton.defaultStyleOf
is:
@override
MaterialStateProperty<Size>? get minimumSize =>
const MaterialStatePropertyAll<Size>(Size(64.0, 40.0));
That's a minimum of 64px wide but only 40px high. So TextButton does indeed take care of this need — but it sets a slightly smaller minimum.
What if we set TextButton.style
to provide a minimumSize
property there?
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.
lib/widgets/home.dart
Outdated
child: Column( | ||
mainAxisAlignment: MainAxisAlignment.start, |
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:
child: Column( | |
mainAxisAlignment: MainAxisAlignment.start, | |
child: Column( |
} | ||
} | ||
|
||
abstract class _NavigationBarMenuButton extends _MenuButton { |
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 calls for a few words of dartdoc — from the name, it sounds like "a button for the navigation-bar menu", which is confusing because that's what _MenuButton
is.
lib/widgets/home.dart
Outdated
Expanded(child: Text(label(zulipLocalizations), | ||
textAlign: TextAlign.start, |
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.
Surely TextAlign.start is the default, right? So we shouldn't need to repeat it:
Expanded(child: Text(label(zulipLocalizations), | |
textAlign: TextAlign.start, | |
Expanded(child: Text(label(zulipLocalizations), |
await tapOpenMenu(tester); | ||
checkIconSelected(tester, inboxMenuIconFinder); | ||
checkIconNotSelected(tester, channelsMenuIconFinder); | ||
check(find.byType(InboxPageBody)).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.
check(find.byType(InboxPageBody)).findsOne(); | |
check(find.byType(InboxPageBody)).findsOne(); | |
check(find.byType(SubscriptionListPageBody)).findsNothing(); |
and similarly check(find.byType(InboxPageBody)).findsNothing();
below. That helps the reader be sure that something really changed as a result of the tap — that it's not just that both finders already would find something and continue to do so (e.g. because we got something wrong in the interaction of the finders with the Offstage
nature of the pages).
This addresses most of the comment except |
The prep PR #1099 was merged, so this can be updated for that. (Also I see CI is failing.) |
Yes. There are some wip changes to be finished before this is ready for review again. |
Thanks for the reviews @chrisbobbe @gnprice! The PR is ready again. Some UI changes: The button uses the default styling of This also adds a 16px Now we ensure that the HomePage is always at the root level, by removing all the existing routes when pushing it onto the navigator stack. There are also some minor changes like popping the bottom sheet for all menu buttons. |
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 revision!
It's late, so for tonight here's just a review of the points that were in my previous review #1076 (review) . All look good now except for the couple of items below.
lib/widgets/home.dart
Outdated
} | ||
} | ||
|
||
class _NavigationButton extends StatelessWidget { |
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.
bump #1076 (comment)
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. Missed the Bar
part. Fixed that and updated comment/string references to this name to match.
lib/widgets/home.dart
Outdated
child: ConstrainedBox( | ||
constraints: const BoxConstraints(minHeight: 44), | ||
child: const ActionSheetCancelButton()))), |
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.
(Are the labels switched on those screenshots? The one labeled "w/ minHeight" has the button shorter than the other does.)
lib/widgets/home.dart
Outdated
child: ConstrainedBox( | ||
constraints: const BoxConstraints(minHeight: 44), | ||
child: const ActionSheetCancelButton()))), |
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.
Following up on this point:
I'd think TextButton (which this button uses) would take care of that.
by looking into TextButton, it looks like this is controlled by ButtonStyle.minimumSize
. The default for which in TextButton.defaultStyleOf
is:
@override
MaterialStateProperty<Size>? get minimumSize =>
const MaterialStatePropertyAll<Size>(Size(64.0, 40.0));
That's a minimum of 64px wide but only 40px high. So TextButton does indeed take care of this need — but it sets a slightly smaller minimum.
What if we set TextButton.style
to provide a minimumSize
property there?
139247e
to
317464e
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 revision! I played with this UI and am happy to see that the behavior seems to be basically all working.
Partial review below, including one behavior issue in the switch-account button being potentially inaccessible. Posting the partial review now in the interest of pipelining work on this PR.
Another thing I noticed in the UI is that the menu takes up the full height of the screen even when its contents are much shorter. Is that something Vlad specifically addressed in his design? My instinct would be to let the menu be the height of its contents when there's room, and fill the screen only when it needs to.
@@ -67,6 +68,7 @@ class MaterialAccountPageRoute<T extends Object?> extends MaterialPageRoute<T> w | |||
MaterialAccountPageRoute({ | |||
int? accountId, | |||
BuildContext? context, | |||
this.loadingPlaceholderPage, |
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: add this option in a prep commit, to simplify the main commit a bit
|
||
Future<List<Route<dynamic>>> initialRoutes(WidgetTester tester) async { |
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:
app test [nfc]: Convert initialRoute into a generic helper
name misspelled in summary line
test/widgets/app_test.dart
Outdated
]); | ||
}); | ||
|
||
testWidgets('choosing an account clears the navigator stack', (tester) async { |
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 doesn't seem to belong in a group for "ZulipApp initial navigation". It's really about ChooseAccountPage.
I think the existing tests in this group can stay as they are (except the update to expectations), and this one can do its own setup of a nav observer. It doesn't really have the same needs in a nav observer as the other tests in this group do, and combining their needs in one helper makes the other tests more complicated to understand.
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.
Moving this to the "ChooseAccountPage" group.
test/widgets/login_test.dart
Outdated
await prepare(tester, serverSettings); | ||
checkNotLoggedInStartingRoutes(); | ||
pushedRoutes.clear(); |
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 no longer checks as much as it did before — it won't notice if there were excess routes pushed on top of the expected ones.
I think the previous take/check-empty structure was good. You can move that verbatim out of prepare
to its callers.
test/widgets/login_test.dart
Outdated
(it) => it.isA<MaterialAccountWidgetRoute>() | ||
..accountId.equals(eg.selfAccount.id) | ||
..page.isA<HomePage>(), | ||
(it) => it.isA<MaterialWidgetRoute>() | ||
..page.isA<LoginPage>(), |
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: this can be a lot terser, like the existing check:
(it) => it.isA<MaterialAccountWidgetRoute>() | |
..accountId.equals(eg.selfAccount.id) | |
..page.isA<HomePage>(), | |
(it) => it.isA<MaterialWidgetRoute>() | |
..page.isA<LoginPage>(), | |
(it) => it.isA<WidgetRoute>().page.isA<HomePage>(), | |
(it) => it.isA<WidgetRoute>().page.isA<LoginPage>(), |
The one substantive change there is that it doesn't check the account ID on the HomePage. That's fine because that HomePage isn't part of what this test is about, but rather part of the background to it.
test/widgets/login_test.dart
Outdated
check(poppedRoutes).deepEquals(<Condition<Object?>>[ | ||
(it) => it.isA<MaterialWidgetRoute>() | ||
..page.isA<LoginPage>(), | ||
(it) => it.isA<MaterialAccountWidgetRoute>() | ||
..accountId.equals(eg.selfAccount.id) | ||
..page.isA<HomePage>(), | ||
]); |
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 just popping all the routes that were on the stack, right? So I think just counting the pops covers it:
check(poppedRoutes).deepEquals(<Condition<Object?>>[ | |
(it) => it.isA<MaterialWidgetRoute>() | |
..page.isA<LoginPage>(), | |
(it) => it.isA<MaterialAccountWidgetRoute>() | |
..accountId.equals(eg.selfAccount.id) | |
..page.isA<HomePage>(), | |
]); | |
check(poppedRoutes).length.equals(2); |
test/widgets/login_test.dart
Outdated
pushedRoutes.clear(); | ||
|
||
await login(tester, eg.otherAccount); | ||
final loggedInAccount = testBinding.globalStore.accounts.singleWhere( |
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: "logged-in account" is a confusing name here, because both accounts are logged in.
final loggedInAccount = testBinding.globalStore.accounts.singleWhere( | |
final newAccount = testBinding.globalStore.accounts.singleWhere( |
lib/widgets/home.dart
Outdated
body: Column( | ||
crossAxisAlignment: CrossAxisAlignment.end, | ||
children: [ | ||
const Expanded(child: LoadingPlaceholder()), | ||
TextButton( |
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 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.
Probably cleanest is to make this a floating action button:
return Scaffold(
body: const LoadingPlaceholder(),
floatingActionButton: TextButton(
onPressed: () => Navigator.push(context,
MaterialWidgetRoute(page: const ChooseAccountPage())),
child: Text(zulipLocalizations.chooseAccountPageTitle)),
);
That takes care of applying SafeArea; and it also means the layout of LoadingPlaceholder and centering of the circular progress indicator doesn't get distorted by the presence of the button.
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.
In this revision I also added the AppBar
back because HomePage
has gained one in the previous update.
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.
OK, and here is the rest of a complete review! Eager to get this merged.
I think the one user-visible item is the layout for the "Switch account" button. So that's the most important to fix. Then there's a bit of a long list of comments, but I think the bulk of them will be quick to take care of.
lib/widgets/theme.dart
Outdated
seedColor: kZulipBrandColor); | ||
switch (brightness) { |
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: set off this stanza with blank lines (here and at top of comment):
seedColor: kZulipBrandColor); | |
switch (brightness) { | |
seedColor: kZulipBrandColor); | |
switch (brightness) { |
Similar to #1076 (comment).
Potentially would have been good in the existing code too; but in a list, like where the existing version of this was, it's already clear where one item ends and the next begins, so it's natural to take a comment just before an item as referring to that one item.
test/widgets/actions_test.dart
Outdated
// TODO(#737): switch to a realistic setup | ||
final account1Route = MaterialAccountWidgetRoute( | ||
accountId: account1.id, page: const InboxPageBody()); | ||
final account2Route = MaterialAccountWidgetRoute( | ||
accountId: account2.id, page: const InboxPageBody()); |
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.
// TODO(#737): switch to a realistic setup | |
final account1Route = MaterialAccountWidgetRoute( | |
accountId: account1.id, page: const InboxPageBody()); | |
final account2Route = MaterialAccountWidgetRoute( | |
accountId: account2.id, page: const InboxPageBody()); | |
// TODO(#737): switch to a realistic setup: | |
// https://github.com/zulip/zulip-flutter/pull/1076#discussion_r1874124363 | |
final account1Route = MaterialAccountWidgetRoute( | |
accountId: account1.id, page: const InboxPageBody()); | |
final account2Route = MaterialAccountWidgetRoute( | |
accountId: account2.id, page: const InboxPageBody()); |
lib/widgets/app.dart
Outdated
final hasBackButton = ModalRoute.of(context)?.impliesAppBarDismissal ?? false; | ||
return MenuButtonTheme( |
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.
bump #1076 (comment) :
final hasBackButton = ModalRoute.of(context)?.impliesAppBarDismissal ?? false; | |
return MenuButtonTheme( | |
final hasBackButton = ModalRoute.of(context)?.impliesAppBarDismissal ?? false; | |
return MenuButtonTheme( |
As I mentioned, the point is to make clear what code the comment is describing. So the blank line marking the end of that stretch of code is key for that.
lib/widgets/home.dart
Outdated
import 'recent_dm_conversations.dart'; | ||
import 'store.dart'; | ||
import 'subscription_list.dart'; | ||
import 'text.dart'; | ||
import 'theme.dart'; | ||
|
||
enum HomePageTab { |
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:
enum HomePageTab { | |
enum _HomePageTab { |
lib/widgets/home.dart
Outdated
} | ||
|
||
class _HomePageState extends State<HomePage> { | ||
late final ValueNotifier<HomePageTab> _tab = ValueNotifier(HomePageTab.inbox); |
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:
late final ValueNotifier<HomePageTab> _tab = ValueNotifier(HomePageTab.inbox); | |
late final _tab = ValueNotifier(HomePageTab.inbox); |
testWidgets('while loading, go back from ChooseAccountPage', (tester) async { | ||
testBinding.globalStore.loadPerAccountDuration = loadPerAccountDuration; | ||
await prepare(tester); | ||
await tester.pump(kTryAnotherAccountWaitPeriod); | ||
await tapChooseAccount(tester); | ||
|
||
await tester.tap(find.byType(BackButton)); | ||
await tester.pump(Duration.zero); // tap the button | ||
await tester.pump(const Duration(milliseconds: 350)); // wait for pop animation | ||
checkOnLoadingPage(); | ||
|
||
await tester.pump(loadPerAccountDuration); | ||
checkOnHomePage(tester, expectedAccount: eg.selfAccount); | ||
}); |
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 like these nice clear tests! The various helper functions really contribute to being able to tell these different stories cleanly.
test/widgets/home_test.dart
Outdated
// The second loadPerAccount finished. | ||
await tester.pump(loadPerAccountDuration); | ||
checkOnHomePage(tester, expectedAccount: eg.otherAccount); |
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: comment that says something is true, in the present tense, should go at a point where it's become true
// The second loadPerAccount finished. | |
await tester.pump(loadPerAccountDuration); | |
checkOnHomePage(tester, expectedAccount: eg.otherAccount); | |
await tester.pump(loadPerAccountDuration); | |
// The second loadPerAccount finished. | |
checkOnHomePage(tester, expectedAccount: eg.otherAccount); |
test/widgets/home_test.dart
Outdated
// The second loadPerAccount is still pending. | ||
await tester.pump(loadPerAccountDuration); | ||
checkOnLoadingPage(); |
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: similarly:
// The second loadPerAccount is still pending. | |
await tester.pump(loadPerAccountDuration); | |
checkOnLoadingPage(); | |
await tester.pump(loadPerAccountDuration); | |
// The second loadPerAccount is still pending. | |
checkOnLoadingPage(); |
Here the statement in the comment was already true before the pump, but it wasn't interesting — of course it's still pending, only a few seconds have passed. It's after the await that that fact becomes interesting (which corresponds to the fact that that's the point where you're checking it).
test/widgets/home_test.dart
Outdated
checkOnHomePage(tester, expectedAccount: eg.otherAccount); | ||
}); | ||
|
||
testWidgets('while loading, choosing an account disallow going back', (tester) async { |
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:
testWidgets('while loading, choosing an account disallow going back', (tester) async { | |
testWidgets('while loading, choosing an account disallows going back', (tester) async { |
// Switch to direct messages tab. | ||
await tester.tap(find.descendant( | ||
of: find.byType(Center), | ||
matching: find.byIcon(ZulipIcons.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.
Hmm, the use of Center
is awkward — it's rather an implementation detail, both the fact that the desired copy of the icon does have a Center ancestor and the fact that the undesired copies don't.
I don't have a better idea to suggest, though, so I guess just leave it. No need for a TODO, even; really it just becomes actionable if the test breaks (because of some innocent change to either the nav bar, or how DMs appear in the inbox, or something else) and we have to find a different solution.
I feel like the API I'd want here is to be able to say "tap the such-and-such icon, specifically the one that's in the bottom part of the screen". But I don't see an easy way to do that with the existing Finder
API.
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.
Yeah, I was a bit reluctant when writing this, but some alternatives would be customizing the initial view, adding a debug feature, or anything that only gets used in tests like this, which are not really realistic/useful. Leaving it seems fine to me.
There is tapAt
that we can use, but with that we still need to know roughly where the buttons are. It's probably doable but requires a few more steps of setup.
This is an initial implementation because some features were considered out-of-scope as of now for zulip#1035. Compared to the Figma, we swapped the order of _ChannelsButton and _DirectMessagesButton in the menu so they match their order on the navigation bar. See: https://chat.zulip.org/#narrow/channel/48-mobile/topic/Buttons.20on.20the.20bottom.20tabs.20and.20main.20menu We also added _CombinedFeedButton, using the same icon we have for "Combined feed" on the web app's topleft sidebar, added a "Switch account" button, and renamed "Streams" to "Channels". For now, we do not aim to fully implement the app bar redesign; we keep a simple one in this implementation. We maintain a 16px padding for the title text on the app bar for both ChooseAccountPage (when it is at the root navigation level) and HomePage (which is expected to be at the root level all the time). We allow user to navigate to the choose-account page from the loading screen after a certain timeout. This is helpful when loading the account data takes a while, and that the user, for example, wants to try another account. See: zulip#1076 (comment) Signed-off-by: Zixuan James Li <zixuan@zulip.com>
This is an initial implementation because some features were considered out-of-scope as of now for zulip#1035. Compared to the Figma, we swapped the order of _ChannelsButton and _DirectMessagesButton in the menu so they match their order on the navigation bar. See: https://chat.zulip.org/#narrow/channel/48-mobile/topic/Buttons.20on.20the.20bottom.20tabs.20and.20main.20menu We also added _CombinedFeedButton, using the same icon we have for "Combined feed" on the web app's topleft sidebar, added a "Switch account" button, and renamed "Streams" to "Channels". For now, we do not aim to fully implement the app bar redesign; we keep a simple one in this implementation. We maintain a 16px padding for the title text on the app bar for both ChooseAccountPage (when it is at the root navigation level) and HomePage (which is expected to be at the root level all the time). We allow user to navigate to the choose-account page from the loading screen after a certain timeout. This is helpful when loading the account data takes a while, and that the user, for example, wants to try another account. See: zulip#1076 (comment) Signed-off-by: Zixuan James Li <zixuan@zulip.com>
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 swift revision! Just two nits now.
test/widgets/actions_test.dart
Outdated
// TODO(#737): switch to a realistic setup: | ||
final account1Route = MaterialAccountWidgetRoute( |
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.
bump #1076 (comment) 🙂
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.
Ohh! I was wondering why there was only a colon added (which made some sense to me anyway).
lib/widgets/home.dart
Outdated
return Icon(icon, size: _iconSize, color: selected ? designVariables.iconSelected | ||
: designVariables.icon); |
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: misaligned
(probably just move color
to second line)
Widget buildLeading(BuildContext context) { | ||
final store = PerAccountStoreWidget.of(context); | ||
return Avatar( | ||
userId: store.selfUserId, size: _MenuButton._iconSize, borderRadius: 4); |
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, bonus factoring-out! 🙂
This is an initial implementation because some features were considered out-of-scope as of now for zulip#1035. Compared to the Figma, we swapped the order of _ChannelsButton and _DirectMessagesButton in the menu so they match their order on the navigation bar. See: https://chat.zulip.org/#narrow/channel/48-mobile/topic/Buttons.20on.20the.20bottom.20tabs.20and.20main.20menu We also added _CombinedFeedButton, using the same icon we have for "Combined feed" on the web app's topleft sidebar, added a "Switch account" button, and renamed "Streams" to "Channels". For now, we do not aim to fully implement the app bar redesign; we keep a simple one in this implementation. We maintain a 16px padding for the title text on the app bar for both ChooseAccountPage (when it is at the root navigation level) and HomePage (which is expected to be at the root level all the time). We allow user to navigate to the choose-account page from the loading screen after a certain timeout. This is helpful when loading the account data takes a while, and that the user, for example, wants to try another account. See: zulip#1076 (comment) Signed-off-by: Zixuan James Li <zixuan@zulip.com>
Otherwise all LGTM! |
With a small font size, the button can be shorter than it is specified in the Figma design. See: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3479-26262&node-type=symbol&t=hGZtzX2QTLse81un-0 Signed-off-by: Zixuan James Li <zixuan@zulip.com>
Signed-off-by: Zixuan James Li <zixuan@zulip.com>
Signed-off-by: Zixuan James Li <zixuan@zulip.com>
Signed-off-by: Zixuan James Li <zixuan@zulip.com>
This allows us to use `prepare` for testing the login page when the user is logged in. Signed-off-by: Zixuan James Li <zixuan@zulip.com>
Signed-off-by: Zixuan James Li <zixuan@zulip.com>
This preps for adding a special loading page for the home page. Signed-off-by: Zixuan James Li <zixuan@zulip.com>
This applies to all the `ElevatedButton`'s, which are exclusively used in `HomePage`, `LoginPage`, `ChooseAccountPage`, and `AddAccountPage, to fix their color in dark mode. Signed-off-by: Zixuan James Li <zixuan@zulip.com>
The colors are intended to match what we use for `ElevatedButton` (see `zulipThemeData`). Signed-off-by: Zixuan James Li <zixuan@zulip.com>
This is an initial implementation because some features were considered out-of-scope as of now for zulip#1035. Compared to the Figma, we swapped the order of _ChannelsButton and _DirectMessagesButton in the menu so they match their order on the navigation bar. See: https://chat.zulip.org/#narrow/channel/48-mobile/topic/Buttons.20on.20the.20bottom.20tabs.20and.20main.20menu We also added _CombinedFeedButton, using the same icon we have for "Combined feed" on the web app's topleft sidebar, added a "Switch account" button, and renamed "Streams" to "Channels". For now, we do not aim to fully implement the app bar redesign; we keep a simple one in this implementation. We maintain a 16px padding for the title text on the app bar for both ChooseAccountPage (when it is at the root navigation level) and HomePage (which is expected to be at the root level all the time). We allow user to navigate to the choose-account page from the loading screen after a certain timeout. This is helpful when loading the account data takes a while, and that the user, for example, wants to try another account. See: zulip#1076 (comment) Signed-off-by: Zixuan James Li <zixuan@zulip.com>
…Page Fixes: zulip#1035 Signed-off-by: Zixuan James Li <zixuan@zulip.com>
Rebased and fixed the conflict with |
Thanks! CI has reached the |
This is an initial implementation because some features were considered out-of-scope as of now for zulip#1035. Compared to the Figma, we swapped the order of _ChannelsButton and _DirectMessagesButton in the menu so they match their order on the navigation bar. See: https://chat.zulip.org/#narrow/channel/48-mobile/topic/Buttons.20on.20the.20bottom.20tabs.20and.20main.20menu We also added _CombinedFeedButton, using the same icon we have for "Combined feed" on the web app's topleft sidebar, added a "Switch account" button, and renamed "Streams" to "Channels". For now, we do not aim to fully implement the app bar redesign; we keep a simple one in this implementation. We maintain a 16px padding for the title text on the app bar for both ChooseAccountPage (when it is at the root navigation level) and HomePage (which is expected to be at the root level all the time). We allow user to navigate to the choose-account page from the loading screen after a certain timeout. This is helpful when loading the account data takes a while, and that the user, for example, wants to try another account. See: zulip#1076 (comment) Signed-off-by: Zixuan James Li <zixuan@zulip.com>
This is stacked on top of some commits cherry-picked from #1041:
See also: CZO discussion
Screenshots
Fixes: #1035