Skip to content

Conversation

@MritunjayTiwari14
Copy link
Contributor

@MritunjayTiwari14 MritunjayTiwari14 commented Sep 28, 2025

Key Changes: (Screenshots Outdated)

Dark Theme Light Theme
1000051916 1000051917

I have kept the height of the Navigation to be fixed because a fully dynamic navigation bar height that adjusts based on label content across languages is not feasibly after having done some feasibility study. The UI jitter when the bar changes its size suddenly when the user changes the app language is extremely unlikely to be solved and can negatively impact user experience hence even in day to day app these types of dynamic navigation bar are not used.

integration of the page bodies and semantics has been done

Link to Figma Design was not found rather, the screenshot that was shared by Vlad Korobov in the following discuss was used to implement the UI : #mobile > Flutter feedback - Icons in app @ 💬

  • Language Consistency for all language for the added labels will be fixed after receiving feedback from the Moderators about the Implementation of the solution.

Thank you

Figma Design Link

Fixes #1857
Fixes #1960

@MritunjayTiwari14
Copy link
Contributor Author

MritunjayTiwari14 commented Sep 28, 2025

@gnprice I would be extremely grateful if you could give a review to this PR for the Issue that you stated.

@gnprice
Copy link
Member

gnprice commented Sep 28, 2025

There is a lot of text to read in that PR description. Did you use ChatGPT or another LLM to write it? If so, we'd much rather see whatever prompt you used, instead of the LLM output — the LLM fundamentally doesn't add any information that wasn't there already, and the prompt should be a lot shorter and so less work to read.

There's also no need to repeat information that's there in the diffs, like the names of files you touched and identifiers you added there.

@gnprice
Copy link
Member

gnprice commented Sep 28, 2025

Please also add a test for this change, and organize into clear and coherent commits according to the Zulip style — those are our two general requirements for a PR. See the repo's README for details, and links to further details.

Once those are met, this will be ready for a review. After another maintainer has reviewed it and you've revised to address their comments, I'll review the PR.

@MritunjayTiwari14
Copy link
Contributor Author

MritunjayTiwari14 commented Sep 29, 2025

There is a lot of text to read in that PR description. Did you use ChatGPT or another LLM to write it? If so, we'd much rather see whatever prompt you used, instead of the LLM output — the LLM fundamentally doesn't add any information that wasn't there already, and the prompt should be a lot shorter and so less work to read.

There's also no need to repeat information that's there in the diffs, like the names of files you touched and identifiers you added there.

Although i did write the whole description in a paragraph, I use LLM to fine tune my description into a formatted output like a pointer and reformat it output again myself. I have changed the description to a brief one.

@MritunjayTiwari14
Copy link
Contributor Author

Please also add a test for this change, and organize into clear and coherent commits according to the Zulip style — those are our two general requirements for a PR. See the repo's README for details, and links to further details.

Once those are met, this will be ready for a review. After another maintainer has reviewed it and you've revised to address their comments, I'll review the PR.

Alright Greg

MritunjayTiwari14 pushed a commit to MritunjayTiwari14/zulip-flutter that referenced this pull request Oct 1, 2025
Added Gesture Detector over Column, Temporary
set the onPressed of the Icon Button to null.

Fixes: zulip#1808

home: Improve performace and fix bug.

Replaced IconButton widgets to Icon improve
significantly performace of switching screens
and wrote test for the semantic labels for App Bar.

Fixes: zulip#1879
@MritunjayTiwari14 MritunjayTiwari14 force-pushed the issue_1857 branch 4 times, most recently from 55f7e12 to 1f1a1ee Compare October 5, 2025 10:31
@MritunjayTiwari14
Copy link
Contributor Author

@chrisbobbe This is the PR for which we discussed in the chats

For Design reference i have attached the difference.

Design Provided by Vlad

I have made the commits coherent as well as minimal as request by Greg.

@gnprice gnprice added the maintainer review PR ready for review by Zulip maintainers label Oct 8, 2025
@MritunjayTiwari14 MritunjayTiwari14 force-pushed the issue_1857 branch 3 times, most recently from e8fc538 to 406a86f Compare October 9, 2025 09:26
@MritunjayTiwari14
Copy link
Contributor Author

MritunjayTiwari14 commented Oct 9, 2025

Before After(Font Size-12) After(Font Size-14)
1000052074 1000052067 1000052071
1000052075 1000052068 1000052072


Thank you @gnprice for the advice in the mobile-design chats, i found the figma design for the labeled bottom navigation bar here, i have given the difference of implemented UI and design in the table above.

Comment on lines 135 to 139
// TODO(#535): Decide if we find it helpful to use something like
// [SemanticsProperties.namesRoute] to structure this UI better
// for screen-reader software.
Offstage(offstage: tab != _tab.value, child: body),
Offstage(
offstage: tab != _tab.value,
child: Semantics(
namesRoute: true,
child: body))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you read the TODO and decided that SemanticsProperties.namesRoute is helpful. Could you explain what you considered when making that decision?

@MritunjayTiwari14
Copy link
Contributor Author

MritunjayTiwari14 commented Oct 10, 2025

Thank you @chrisbobbe for pointing out the issue in sematics!

I have updated the semantic using SematicServices.sendAnnouncement for better interoperability. I can provide the TalkBack working on android recording for confirmation.

@MritunjayTiwari14 MritunjayTiwari14 force-pushed the issue_1857 branch 2 times, most recently from 2d51fd6 to adc6013 Compare October 10, 2025 10:29
@chrisbobbe
Copy link
Collaborator

Thank you @chrisbobbe for pointing out the issue in sematics!

Hmm, in #1879 (comment) , I didn't intend to point out any issue. 🙂 I just meant to ask for information that could help us understand your proposal.

Did you find some issue with SemanticsProperties.namesRoute that made you decide to fix the TODO with that "send announcement" method instead? I don't think we've used that before.

@MritunjayTiwari14
Copy link
Contributor Author

Hmm, in #1879 (comment) , I didn't intend to point out any issue. 🙂 I just meant to ask for information that could help us understand your proposal.

Did you find some issue with SemanticsProperties.namesRoute that made you decide to fix the TODO with that "send announcement" method instead? I don't think we've used that before.

Thank you @chrisbobbe for the review, I think the main reason is offstage do not allow the semantic to access its property for semantic widget to correctly get the label to speak out, I also tested it using TalkBack and the semantic widget even with a label was not able to make TalkBack trigger when the change in page body was detected.

@chrisbobbe
Copy link
Collaborator

chrisbobbe commented Oct 17, 2025

It sounds like you were expected to hear something from TalkBack when you changed tabs, and you didn't hear anything, so you assumed it wasn't a valid fix for the TODO.

I these are the main requirements to resolve the TODO:

  • We use the Semantics APIs as documented. Some questions for investigation:
  • If we make a change, then it shouldn't make the experience obviously worse, in manual testing with TalkBack or VoiceOver.

It's entirely possible that a namesRoute approach is helpful to users in some less obvious way than causing TalkBack to announce the name of a tab when you tap on it. For example maybe VoiceOver does that, or maybe it enables screen-reader software to answer the question "What route am I on?", and we would learn that if we used the software long enough. 🙂 It's our job to give screen-reader software the information it expects, but it's that software's job to decide what to do with it.

If it gets at all complicated, let's not try to resolve the TODO in this PR, to keep it focused on #1857.

@MritunjayTiwari14
Copy link
Contributor Author

Before Implementing the Nav Bar Label

Normal Oversized System Font
before_l 1000052413
before_b 1000052412


After Implementing the Nav Bar Label

Before After After with Oversized System Font
before_l norm_l extra_l
before_b norm_b extra_b

Case of Text Overflow( Upto 2 Lines )

Text Overflow Light Text Overflow Dark
over_l over_b


Thank you @chrisbobbe for your review. I feels like it better to concentrate on a single task right now (i.e the nav bar labelling).
I have made the commit history clear and coherent as well as uploaded the detailed updated screenshot comparison of the design.

This solution also fixes the constant size of nav bar even when the system font size was increased which was a user dissatisfier, it would allow the uses to easily identify the corresponding page using the enlarged label fitted within nav bar.

@MritunjayTiwari14
Copy link
Contributor Author

MritunjayTiwari14 commented Oct 21, 2025

Screenshots

Screenshot Comparison

Light Theme Dark Theme
Screenshot_1761040184 Screenshot_1761040123

Video Recording of implementation

video.mp4

Possible enhancement: Adding a padding in the top which was not implemented as it was not described in Figma Design of the same.

Thanks for the patience, the PR is ready for review! @chrisbobbe, the Ink splash effect on the button has been restored using material and inkwell combination which also includes the padding as per the Figma design.

@MritunjayTiwari14 MritunjayTiwari14 force-pushed the issue_1857 branch 2 times, most recently from ef68b0c to 4499f3f Compare October 22, 2025 11:07
@MritunjayTiwari14
Copy link
Contributor Author

MritunjayTiwari14 commented Oct 27, 2025

Thank you @chrisbobbe for the detailed feedback, the text font scaling has been handled using textScaler with a maxScaleFacctor as requested with the other changes also.

Comparison Through Screenshots

Maxing Android Font and Display Size:

Before After
Screenshot_1761559270 Screenshot_1761559233
Before After
Screenshot_1761559263 Screenshot_1761559248

Copy link
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Small comments below.

Comment on lines 121 to 123
await tester.tap(find.descendant(
of: find.byType(DecoratedBox),
matching: find.text('Channels')));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this finder more specific. DecoratedBox widgets are pretty commonly used, so there's some risk that this finder will find one that has nothing to do with creating the visual appearance of the nav bar.

We can add the expectation that it has a top border of a certain color and no other borders. For example, at the top of this 'bottom nav navigation' group, we could define a helper like findInBottomNav:

    final findBottomNavDecoratedBox = find.byWidgetPredicate((widget) {
      if (widget is! DecoratedBox) return false;

      final decoration = widget.decoration;
      if (decoration is! BoxDecoration) return false;

      final expectedBorderTop = BorderSide(color: Colors.black.withValues(alpha: 0.2));
      return decoration.border == Border(top: expectedBorderTop);
    });

    Finder findInBottomNav(Finder finder) =>
      find.descendant(of: findBottomNavDecoratedBox, matching: finder);

and use it like this in the tests:

await tester.tap(findInBottomNav(find.text('Channels')));

Comment on lines 135 to 137
check(find.descendant(
of: find.byType(ZulipAppBar),
matching: find.text('Direct messages'))).findsOne();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We use two-space indents, not four

Comment on lines 124 to 134
await tester.pump();

check(find.descendant(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Remove this blank line, to group together the "tap, pump, check result" actions. Similarly below.

mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 34,
child: Center(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump on #1879 (comment)

@MritunjayTiwari14
Copy link
Contributor Author

MritunjayTiwari14 commented Oct 28, 2025

Thank you @chrisbobbe for the review. The following changes have been implemented:

Reordered tests to verify internal consistency (state), then correct UI reflection (title), and finally user interaction (taps)

Inside the group('bottom nav navigation', () { ...

Order of test before

  1. testWidget('Preserve states when switching between views' ...);
  2. testWidget('View switches when labels are tapped' ...);
  3. testWidget('Update app bar title when switching between views' ...);

Order of test after

  1. testWidget('Preserve states when switching between views' ...);
  2. testWidget('Update app bar title when switching between views' ...);
  3. testWidget('View switches when labels are tapped' ...);

This was done to ensure that appbar title are switched only when views are changed since if testWidget('Update app bar title when switching between views' ...); passes it would imply that logic is correct, then the View switches when labels are tapped widget test uses that logic to test the view are changing by that fact that changing appbar title implies changing view so thereby checking the appbar title when the labels are tapped!.

Also:

  • Organized related "tap, pump, check result" actions together
  • Cleared the bump on #1904 (comment)
  • Resolved all nits
  • Added One Comment in test file

@MritunjayTiwari14
Copy link
Contributor Author

MritunjayTiwari14 commented Nov 1, 2025

@gnprice Thank you for updating regarding the new issue filed which can be easily solve within this.

After trying for few hours, I would like to point out few things regarding the testing of TalkBack. From the trials i have done. The TalkBack was being displayed neither by Live Transcript not via any Screen Recording software which has support to record android internal audio. Hence i think its not possible to able to capture it and display it over here.

However, I have tested this on my android device and the changes works as expected. The only way I think it can be shown is by setting up this branch in the local environment to test the talk back or using an external device(microphone) near the speaker to recording the TalkBack. Would it be helpful if I post the latter one result?

@MritunjayTiwari14 MritunjayTiwari14 force-pushed the issue_1857 branch 2 times, most recently from 8a6f94f to 07695e4 Compare November 1, 2025 07:25
Copy link
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Comments below.

Icon(icon, size: 24, color: color),
Flexible(
child: Text(
semanticsLabel: '$label tab',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the doc for Text.semanticsLabel:

  /// An alternative semantics label for this text.
  ///
  /// If present, the semantics of this widget will contain this value instead
  /// of the actual text. This will overwrite any of the semantics labels applied
  /// directly to the [TextSpan]s.
  ///
  /// This is useful for replacing abbreviations or shorthands with the full
  /// text value:
  ///
  /// ```dart
  /// const Text(r'$$', semanticsLabel: 'Double dollars')
  /// ```

In this case the actual text is the value of label (e.g. "Combined feed" or "綜合饋給"), so if we didn't pass semanticsLabel, then that would be the semantics label too. If we pass semanticsLabel: '$label tab', as you're proposing here, then that would be the semantics label (e.g. "Combined feed tab" or "綜合饋給 tab").

We don't merge user-facing text that's not set up for translation, so if we wanted versions of these names that include "tab", we'd need to add them to assets/l10n/app_en.arb and read them via ZulipLocalizations.

Instead of doing that, though, let's use Flutter's documented API for declaring (to screen-reader software) than an element is a tab. See our existing use of SemanticsRole.tab and its documentation.

Comment on lines 151 to 153
check(find.descendant(
of: find.byType(ZulipAppBar),
matching: find.text('Inbox'))).findsOne();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use two-space indent, not four; I've already pointed this out: #1879 (comment)

@MritunjayTiwari14 MritunjayTiwari14 force-pushed the issue_1857 branch 2 times, most recently from c6aa556 to 8f15233 Compare November 4, 2025 10:00
@MritunjayTiwari14
Copy link
Contributor Author

Thank you @chrisbobbe for the review. Have cleared out some nits as well as given SemanticsRole.tab to the Semantics and the other necessary things for it, PTAL.

@chrisbobbe
Copy link
Collaborator

Thanks! I've tested that out with VoiceOver and found a way to improve the experience. See the two commits I've pushed on top of yours; here's the list of all three:

8f15233 home: Add label to bottom navbar icons
ca48d88 home [nfc]: Factor bottom nav into its own widget
28ad215 home: Adjust semantics of bottom nav bar

I'll mark this for Greg's review. If he requests changes, please pull the branch including my commits so we don't lose them, before applying your changes to the branch.

@chrisbobbe chrisbobbe requested a review from gnprice November 5, 2025 21:14
@chrisbobbe chrisbobbe assigned gnprice and unassigned chrisbobbe Nov 5, 2025
@chrisbobbe chrisbobbe added integration review Added by maintainers when PR may be ready for integration and removed maintainer review PR ready for review by Zulip maintainers labels Nov 5, 2025
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @MritunjayTiwari14 for taking care of this, and @chrisbobbe for the previous reviews! Comments below.

Comment on lines 166 to 168
child: ConstrainedBox(
// TODO(design): determine a suitable max width for bottom nav bar
constraints: const BoxConstraints(maxWidth: 600, minHeight: 48),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this maxWidth still work without the Center widget? What happens when the screen is wider than 600px?

Comment on lines 127 to 132
return _NavigationBarButton(icon: icon,
selected: tabNotifier.value == tab,
onPressed: () {
tabNotifier.value = tab;
},
label: label);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: keep label next to icon (here and below) — they're logically closely related, as the two pieces of visible content

Comment on lines 153 to 155
_button(_HomePageTab.directMessages, ZulipIcons.two_person,
zulipLocalizations.recentDmConversationsPageTitle),
_NavigationBarButton( icon: ZulipIcons.menu,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this tabular-style format no longer really works now that there's the label making the _button call overflow onto two lines.

Instead, let's switch to a more standard format; and let's make icon and label be named parameters to _button, in order to be consistent with the direct _NavigationBarButton calls:

Suggested change
_button(_HomePageTab.directMessages, ZulipIcons.two_person,
zulipLocalizations.recentDmConversationsPageTitle),
_NavigationBarButton( icon: ZulipIcons.menu,
_button(_HomePageTab.directMessages,
icon: ZulipIcons.two_person,
label: zulipLocalizations.recentDmConversationsPageTitle),
_NavigationBarButton(
icon: ZulipIcons.menu,
label: zulipLocalizations.navBarMenuLabel,

Comment on lines +100 to +111
return Scaffold(
appBar: ZulipAppBar(titleSpacing: 16,
title: Semantics(
identifier: HomePage.titleSemanticsIdentifier,
namesRoute: true,
child: Text(_currentTabTitle))),
body: Semantics(
role: SemanticsRole.tabPanel,
identifier: HomePage.contentSemanticsIdentifier,
container: true,
explicitChildNodes: true,
child: Stack(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, interesting.

The rest is just a best effort at following the docs, which doesn't
make the experience obviously worse, but I didn't notice an effect
in my testing.

@chrisbobbe Were there particular docs you found especially helpful which you can link to? I'm curious for things we can consult in the future. (No need to spend a lot of time digging now, though — if things seem to behave well, that's good enough for the code at this stage.)

Comment on lines 115 to 116
]),
),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
]),
),
])),

Comment on lines 91 to 97
final expectedBorderTop = BorderSide(color: Colors.black.withValues(alpha: 0.2));
return decoration.border == Border(top: expectedBorderTop);
});

// Finds a widget within the bottom navbar's decorated box subtree.
Finder findInBottomNav(Finder finder) =>
find.descendant(of: findBottomNavDecoratedBox, matching: finder);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems fairly brittle. (In fact I believe it already wouldn't work in dark theme.) Conversely, I agree with @chrisbobbe's comment at #1879 (comment) above that looking for just DecoratedBox is too broad.

How about finding by SemanticsRole.tab? Or SemanticsRole.tabBar.

@MritunjayTiwari14
Copy link
Contributor Author

MritunjayTiwari14 commented Nov 6, 2025

Thank you @gnprice for the review and @chrisbobbe for the commits. I have previously tried to use Center widget wrapped over constrained box before proposing these layouts. I found that Center disrupt the layout by causing the ConstrainedBox to align in 'literally' center of the screen, both vertically and horizontally since there is no restriction over ConstrainedBox in the vertical given by it's parent widgets.
i.e

Wrapping ConstrainedBox with Center

Wrapping ConstrainedBox with Center

After After
Screenshot 2025-11-06 193637 Screenshot 2025-11-06 193627
Effect of using `IntrinsicHeight` Widget

Device Configurations

Screenshot 2025-11-06 191737

Before After
Screenshot 2025-11-06 191611 Screenshot 2025-11-06 200444
Before After
Screenshot 2025-11-06 191558 Screenshot 2025-11-06 200455

IntrinsicHeight allowed the DecoratedBox to tightly bound to the height constrains given by ConstrainedBox, fixing the layout issue due to Center. Hence, Overall maintained the center positioning of ConstrainedBox.

@MritunjayTiwari14
Copy link
Contributor Author

Will push the updates by tomorrow after resolving some conflicts to clean commit history.

Fixes zulip#1857.
Fixes zulip#1960.
Some of this has an effect I observes in my testing on iOS with
VoiceOver:

- MergeSemantics on each tab causes the tab to be represented by a
  single node instead of multiple, so it isn't double-visited when
  swiping right to traverse the tree.

The rest is just a best effort at following the docs, which doesn't
make the experience obviously worse, but I didn't notice an effect
in my testing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration review Added by maintainers when PR may be ready for integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Semantic labels on main nav bar Label icons on main nav bar

3 participants