11import 'dart:async' ;
2- import 'dart:ui' ;
32
43import 'package:flutter/material.dart' ;
4+ import 'package:flutter/rendering.dart' ;
55
66import '../generated/l10n/zulip_localizations.dart' ;
77import '../model/narrow.dart' ;
@@ -48,6 +48,9 @@ class HomePage extends StatefulWidget {
4848 HomePage .buildRoute (accountId: accountId)));
4949 }
5050
51+ static String contentSemanticsIdentifier = 'home-page-content' ;
52+ static String titleSemanticsIdentifier = 'home-page-title' ;
53+
5154 @override
5255 State <HomePage > createState () => _HomePageState ();
5356}
@@ -96,15 +99,20 @@ class _HomePageState extends State<HomePage> {
9699
97100 return Scaffold (
98101 appBar: ZulipAppBar (titleSpacing: 16 ,
99- title: Text (_currentTabTitle)),
100- body: Stack (
101- children: [
102- for (final (tab, body) in pageBodies)
103- // TODO(#535): Decide if we find it helpful to use something like
104- // [SemanticsProperties.namesRoute] to structure this UI better
105- // for screen-reader software.
106- Offstage (offstage: tab != _tab.value, child: body),
107- ]),
102+ title: Semantics (
103+ identifier: HomePage .titleSemanticsIdentifier,
104+ namesRoute: true ,
105+ child: Text (_currentTabTitle))),
106+ body: Semantics (
107+ role: SemanticsRole .tabPanel,
108+ identifier: HomePage .contentSemanticsIdentifier,
109+ container: true ,
110+ explicitChildNodes: true ,
111+ child: Stack (
112+ children: [
113+ for (final (tab, body) in pageBodies)
114+ Offstage (offstage: tab != _tab.value, child: body),
115+ ])),
108116 bottomNavigationBar: _BottomNavBar (tabNotifier: _tab));
109117 }
110118}
@@ -114,13 +122,17 @@ class _BottomNavBar extends StatelessWidget {
114122
115123 final ValueNotifier <_HomePageTab > tabNotifier;
116124
117- _NavigationBarButton _button (_HomePageTab tab, IconData icon, String label) {
125+ _NavigationBarButton _button ({
126+ required _HomePageTab tab,
127+ required IconData icon,
128+ required String label
129+ }) {
118130 return _NavigationBarButton (icon: icon,
131+ label: label,
119132 selected: tabNotifier.value == tab,
120133 onPressed: () {
121134 tabNotifier.value = tab;
122- },
123- label: label);
135+ });
124136 }
125137
126138 @override
@@ -130,39 +142,54 @@ class _BottomNavBar extends StatelessWidget {
130142
131143 // TODO(a11y): add tooltips for these buttons
132144 final navigationBarButtons = [
133- _button (_HomePageTab .inbox, ZulipIcons .inbox,
134- zulipLocalizations.inboxPageTitle),
135- _NavigationBarButton ( icon: ZulipIcons .message_feed,
145+ _button (tab: _HomePageTab .inbox,
146+ icon: ZulipIcons .inbox,
147+ label: zulipLocalizations.inboxPageTitle),
148+ _NavigationBarButton (
149+ icon: ZulipIcons .message_feed,
150+ label: zulipLocalizations.combinedFeedPageTitle,
136151 selected: false ,
137152 onPressed: () => Navigator .push (context,
138153 MessageListPage .buildRoute (context: context,
139- narrow: const CombinedFeedNarrow ())),
140- label : zulipLocalizations.combinedFeedPageTitle) ,
141- _button ( _HomePageTab .channels, ZulipIcons .hash_italic,
142- zulipLocalizations.channelsPageTitle),
154+ narrow: const CombinedFeedNarrow ()))) ,
155+ _button (tab : _HomePageTab .channels ,
156+ icon : ZulipIcons .hash_italic,
157+ label : zulipLocalizations.channelsPageTitle),
143158 // TODO(#1094): Users
144- _button (_HomePageTab .directMessages, ZulipIcons .two_person,
145- zulipLocalizations.recentDmConversationsPageTitle),
146- _NavigationBarButton ( icon: ZulipIcons .menu,
159+ _button (tab: _HomePageTab .directMessages,
160+ icon: ZulipIcons .two_person,
161+ label: zulipLocalizations.recentDmConversationsPageTitle),
162+ _NavigationBarButton (
163+ icon: ZulipIcons .menu,
164+ label: zulipLocalizations.navBarMenuLabel,
147165 selected: false ,
148- onPressed: () => _showMainMenu (context, tabNotifier: tabNotifier),
149- label: zulipLocalizations.navBarMenuLabel),
166+ onPressed: () => _showMainMenu (context, tabNotifier: tabNotifier)),
150167 ];
151168
152- return DecoratedBox (
169+ Widget result = DecoratedBox (
153170 decoration: BoxDecoration (
154171 border: Border (top: BorderSide (color: designVariables.borderBar)),
155172 color: designVariables.bgBotBar),
156- child: SafeArea (
157- child: ConstrainedBox (
158- // TODO(design): determine a suitable max width for bottom nav bar
159- constraints: const BoxConstraints (maxWidth: 600 , minHeight: 48 ),
160- child: Row (
161- crossAxisAlignment: CrossAxisAlignment .start,
162- children: [
163- for (final navigationBarButton in navigationBarButtons)
164- Expanded (child: navigationBarButton),
165- ]))));
173+ child: IntrinsicHeight (
174+ child: SafeArea (
175+ child: Center (
176+ child: ConstrainedBox (
177+ // TODO(design): determine a suitable max width for bottom nav bar
178+ constraints: const BoxConstraints (maxWidth: 600 , minHeight: 48 ),
179+ child: Row (
180+ crossAxisAlignment: CrossAxisAlignment .start,
181+ children: [
182+ for (final navigationBarButton in navigationBarButtons)
183+ Expanded (child: navigationBarButton),
184+ ]))))));
185+
186+ result = Semantics (
187+ container: true ,
188+ explicitChildNodes: true ,
189+ role: SemanticsRole .tabBar,
190+ child: result);
191+
192+ return result;
166193 }
167194}
168195
@@ -262,7 +289,7 @@ class _NavigationBarButton extends StatelessWidget {
262289 final designVariables = DesignVariables .of (context);
263290 final color = selected ? designVariables.iconSelected : designVariables.icon;
264291
265- return AnimatedScaleOnTap (
292+ Widget result = AnimatedScaleOnTap (
266293 scaleEnd: 0.875 ,
267294 duration: const Duration (milliseconds: 100 ),
268295 child: Material (
@@ -278,21 +305,31 @@ class _NavigationBarButton extends StatelessWidget {
278305 // text wrap before getting too close to the button's edge, which is
279306 // visible on tap-down.)
280307 padding: const EdgeInsets .fromLTRB (3 , 6 , 3 , 3 ),
281- child: Semantics (
282- role: SemanticsRole .tab,
283- selected: selected,
284- child: Column (
285- spacing: 3 ,
286- mainAxisSize: MainAxisSize .min,
287- children: [
288- Icon (icon, size: 24 , color: color),
289- Flexible (
290- child: Text (
291- label,
292- style: TextStyle (fontSize: 12 , color: color, height: 12 / 12 ),
293- textAlign: TextAlign .center,
294- textScaler: MediaQuery .textScalerOf (context).clamp (maxScaleFactor: 1.5 ))),
295- ]))))));
308+ child: Column (
309+ spacing: 3 ,
310+ mainAxisSize: MainAxisSize .min,
311+ children: [
312+ Icon (icon, size: 24 , color: color),
313+ Flexible (
314+ child: Text (
315+ label,
316+ style: TextStyle (fontSize: 12 , color: color, height: 12 / 12 ),
317+ textAlign: TextAlign .center,
318+ textScaler: MediaQuery .textScalerOf (context).clamp (maxScaleFactor: 1.5 ))),
319+ ])))));
320+
321+ result = MergeSemantics (
322+ child: Semantics (
323+ role: SemanticsRole .tab,
324+ controlsNodes: {
325+ HomePage .contentSemanticsIdentifier,
326+ HomePage .titleSemanticsIdentifier,
327+ },
328+ selected: selected,
329+ onTap: onPressed,
330+ child: result));
331+
332+ return result;
296333 }
297334}
298335
0 commit comments