Skip to content

Commit 933370a

Browse files
committed
wip; nav: Implement nav bar redesign
Signed-off-by: Zixuan James Li <[email protected]>
1 parent c670b70 commit 933370a

File tree

2 files changed

+152
-75
lines changed

2 files changed

+152
-75
lines changed

lib/widgets/home.dart

Lines changed: 138 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,160 @@
11
import 'package:flutter/material.dart';
22

3-
import '../generated/l10n/zulip_localizations.dart';
4-
import '../model/narrow.dart';
5-
import 'app_bar.dart';
3+
import 'icons.dart';
64
import 'inbox.dart';
7-
import 'message_list.dart';
85
import 'page.dart';
96
import 'recent_dm_conversations.dart';
10-
import 'store.dart';
117
import 'subscription_list.dart';
12-
import 'text.dart';
8+
import 'theme.dart';
139

14-
class HomePage extends StatelessWidget {
10+
class HomePage extends StatefulWidget {
1511
const HomePage({super.key});
1612

1713
static Route<void> buildRoute({required int accountId}) {
1814
return MaterialAccountWidgetRoute(accountId: accountId,
19-
page: const HomePage());
15+
page: const HomePage());
2016
}
2117

2218
@override
23-
Widget build(BuildContext context) {
24-
final store = PerAccountStoreWidget.of(context);
25-
final zulipLocalizations = ZulipLocalizations.of(context);
19+
State<HomePage> createState() => _HomePageState();
20+
}
2621

27-
final colorScheme = ColorScheme.of(context);
22+
class _HomePageState extends State<HomePage> {
23+
int _pageIndex = 0;
2824

29-
InlineSpan bold(String text) => TextSpan(
30-
style: const TextStyle().merge(weightVariableTextStyle(context, wght: 700)),
31-
text: text);
25+
@override
26+
Widget build(BuildContext context) {
27+
const pageBodies = [
28+
InboxPageBody(),
29+
SubscriptionListPageBody(),
30+
// Users
31+
RecentDmConversationsPageBody(),
32+
// Menu
33+
];
3234

33-
int? testStreamId;
34-
if (store.connection.realmUrl.origin == 'https://chat.zulip.org') {
35-
testStreamId = 7; // i.e. `#test here`; TODO cut this scaffolding hack
35+
Widget Function(int pageIndex) navigationButtonBuilder(IconData icon, String tooltip) {
36+
return (pageIndex) => NavigationButton(
37+
icon: icon, tooltip: tooltip,
38+
selected: _pageIndex == pageIndex,
39+
onPressed: () {
40+
setState(() {
41+
_pageIndex = pageIndex;
42+
});
43+
});
3644
}
3745

46+
final navigationButtonBuilders = [
47+
navigationButtonBuilder(ZulipIcons.inbox, 'Inbox'),
48+
navigationButtonBuilder(ZulipIcons.hash_italic, 'Channels'),
49+
// navigationButtonBuilder(ZulipIcons.contacts, 'Users'),
50+
navigationButtonBuilder(ZulipIcons.user, 'Direct messages'),
51+
// navigationButtonBuilder(ZulipIcons.menu, 'Menu'),
52+
];
53+
54+
final designVariables = DesignVariables.of(context);
55+
3856
return Scaffold(
39-
appBar: ZulipAppBar(title: const Text("Home")),
40-
body: ElevatedButtonTheme(
41-
data: ElevatedButtonThemeData(style: ButtonStyle(
42-
backgroundColor: WidgetStatePropertyAll(colorScheme.secondaryContainer),
43-
foregroundColor: WidgetStatePropertyAll(colorScheme.onSecondaryContainer))),
44-
child: Center(
45-
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
46-
DefaultTextStyle.merge(
47-
textAlign: TextAlign.center,
48-
style: const TextStyle(fontSize: 18),
49-
child: Column(children: [
50-
Text.rich(TextSpan(
51-
text: 'Connected to: ',
52-
children: [bold(store.realmUrl.toString())])),
53-
])),
54-
const SizedBox(height: 16),
55-
ElevatedButton(
56-
onPressed: () => Navigator.push(context,
57-
MessageListPage.buildRoute(context: context,
58-
narrow: const CombinedFeedNarrow())),
59-
child: Text(zulipLocalizations.combinedFeedPageTitle)),
60-
const SizedBox(height: 16),
61-
ElevatedButton(
62-
onPressed: () => Navigator.push(context,
63-
MessageListPage.buildRoute(context: context,
64-
narrow: const MentionsNarrow())),
65-
child: Text(zulipLocalizations.mentionsPageTitle)),
66-
const SizedBox(height: 16),
67-
ElevatedButton(
68-
onPressed: () => Navigator.push(context,
69-
MessageListPage.buildRoute(context: context,
70-
narrow: const StarredMessagesNarrow())),
71-
child: Text(zulipLocalizations.starredMessagesPageTitle)),
72-
const SizedBox(height: 16),
73-
ElevatedButton(
74-
onPressed: () => Navigator.push(context,
75-
InboxPage.buildRoute(context: context)),
76-
child: const Text("Inbox")), // TODO(i18n)
77-
const SizedBox(height: 16),
78-
ElevatedButton(
79-
onPressed: () => Navigator.push(context,
80-
SubscriptionListPage.buildRoute(context: context)),
81-
child: const Text("Subscribed channels")),
82-
const SizedBox(height: 16),
83-
ElevatedButton(
84-
onPressed: () => Navigator.push(context,
85-
RecentDmConversationsPage.buildRoute(context: context)),
86-
child: Text(zulipLocalizations.recentDmConversationsPageTitle)),
87-
if (testStreamId != null) ...[
88-
const SizedBox(height: 16),
89-
ElevatedButton(
90-
onPressed: () => Navigator.push(context,
91-
MessageListPage.buildRoute(context: context,
92-
narrow: ChannelNarrow(testStreamId!))),
93-
child: const Text("#test here")), // scaffolding hack, see above
94-
],
95-
]))));
57+
body: Stack(
58+
children: [
59+
for (int index = 0; index < pageBodies.length; index++)
60+
Offstage(offstage: index != _pageIndex, child: pageBodies[index])
61+
]),
62+
bottomNavigationBar: SafeArea(
63+
child: DecoratedBox(
64+
decoration: BoxDecoration(
65+
border: Border(top: BorderSide(color: designVariables.borderBar, width: 0)),
66+
color: designVariables.bgBotBar),
67+
child: ConstrainedBox(
68+
// TODO(design): determine a suitable max width
69+
constraints: const BoxConstraints(maxWidth: 600).tighten(height: 48),
70+
child: Row(
71+
mainAxisAlignment: MainAxisAlignment.spaceAround,
72+
crossAxisAlignment: CrossAxisAlignment.stretch,
73+
children: [
74+
for (final (pageIndex, buildButton) in navigationButtonBuilders.indexed)
75+
Expanded(child: buildButton(pageIndex)),
76+
])))));
77+
}
78+
}
79+
80+
class Scaled extends StatefulWidget {
81+
const Scaled({
82+
super.key,
83+
required this.scaleEnd,
84+
required this.duration,
85+
required this.child,
86+
});
87+
88+
final double scaleEnd;
89+
final Duration duration;
90+
final Widget child;
91+
92+
@override
93+
State<Scaled> createState() => _ScaledState();
94+
}
95+
96+
class _ScaledState extends State<Scaled> {
97+
double _scale = 1;
98+
99+
void _changeScale(double scale) {
100+
setState(() {
101+
_scale = scale;
102+
});
103+
}
104+
105+
@override
106+
Widget build(BuildContext context) {
107+
return GestureDetector(
108+
behavior: HitTestBehavior.translucent,
109+
onTapDown: (_) => _changeScale(widget.scaleEnd),
110+
onTapUp: (_) => _changeScale(1),
111+
onTapCancel: () => _changeScale(1),
112+
child: AnimatedScale(
113+
scale: _scale,
114+
duration: widget.duration,
115+
curve: Curves.easeOut,
116+
child: widget.child));
117+
}
118+
}
119+
120+
class NavigationButton extends StatelessWidget {
121+
const NavigationButton({
122+
super.key,
123+
required this.icon,
124+
required this.tooltip,
125+
required this.selected,
126+
required this.onPressed,
127+
});
128+
129+
final IconData icon;
130+
final String tooltip;
131+
final bool selected;
132+
final void Function() onPressed;
133+
134+
@override
135+
Widget build(BuildContext context) {
136+
final designVariables = DesignVariables.of(context);
137+
138+
final iconColor = WidgetStateColor.fromMap({
139+
WidgetState.pressed: designVariables.iconSelected,
140+
~WidgetState.pressed: (selected) ? designVariables.iconSelected
141+
: designVariables.icon,
142+
});
143+
144+
return Scaled(
145+
scaleEnd: 0.875,
146+
duration: const Duration(milliseconds: 100),
147+
child: IconButton(
148+
icon: Icon(icon),
149+
tooltip: tooltip,
150+
onPressed: onPressed,
151+
style: IconButton.styleFrom(
152+
// TODO(#417): Disable splash effects for all buttons globally.
153+
splashFactory: NoSplash.splashFactory,
154+
highlightColor: designVariables.iconSelected.withValues(alpha: 0.05),
155+
shape: const RoundedRectangleBorder(
156+
borderRadius: BorderRadius.all(Radius.circular(4))),
157+
).copyWith(foregroundColor: iconColor)),
158+
);
96159
}
97160
}

lib/widgets/theme.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
114114
DesignVariables.light() :
115115
this._(
116116
background: const Color(0xffffffff),
117+
bgBotBar: const Color(0xfff6f6f6),
117118
bgContextMenu: const Color(0xfff2f2f2),
118119
bgCounterUnread: const Color(0xff666699).withValues(alpha: 0.15),
119120
bgTopBar: const Color(0xfff5f5f5),
@@ -125,6 +126,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
125126
editorButtonPressedBg: Colors.black.withValues(alpha: 0.06),
126127
foreground: const Color(0xff000000),
127128
icon: const Color(0xff6159e1),
129+
iconSelected: const Color(0xff222222),
128130
labelCounterUnread: const Color(0xff222222),
129131
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 0).toColor(),
130132
labelMenuButton: const Color(0xff222222),
@@ -155,6 +157,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
155157
DesignVariables.dark() :
156158
this._(
157159
background: const Color(0xff000000),
160+
bgBotBar: const Color(0xff222222),
158161
bgContextMenu: const Color(0xff262626),
159162
bgCounterUnread: const Color(0xff666699).withValues(alpha: 0.37),
160163
bgTopBar: const Color(0xff242424),
@@ -166,6 +169,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
166169
editorButtonPressedBg: Colors.white.withValues(alpha: 0.06),
167170
foreground: const Color(0xffffffff),
168171
icon: const Color(0xff7977fe),
172+
iconSelected: Colors.white.withValues(alpha: 0.8),
169173
labelCounterUnread: const Color(0xffffffff).withValues(alpha: 0.7),
170174
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 1).toColor(),
171175
labelMenuButton: const Color(0xffffffff).withValues(alpha: 0.85),
@@ -203,6 +207,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
203207

204208
DesignVariables._({
205209
required this.background,
210+
required this.bgBotBar,
206211
required this.bgContextMenu,
207212
required this.bgCounterUnread,
208213
required this.bgTopBar,
@@ -214,6 +219,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
214219
required this.editorButtonPressedBg,
215220
required this.foreground,
216221
required this.icon,
222+
required this.iconSelected,
217223
required this.labelCounterUnread,
218224
required this.labelEdited,
219225
required this.labelMenuButton,
@@ -252,6 +258,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
252258
}
253259

254260
final Color background;
261+
final Color bgBotBar;
255262
final Color bgContextMenu;
256263
final Color bgCounterUnread;
257264
final Color bgTopBar;
@@ -263,6 +270,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
263270
final Color editorButtonPressedBg;
264271
final Color foreground;
265272
final Color icon;
273+
final Color iconSelected;
266274
final Color labelCounterUnread;
267275
final Color labelEdited;
268276
final Color labelMenuButton;
@@ -296,6 +304,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
296304
@override
297305
DesignVariables copyWith({
298306
Color? background,
307+
Color? bgBotBar,
299308
Color? bgContextMenu,
300309
Color? bgCounterUnread,
301310
Color? bgTopBar,
@@ -307,6 +316,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
307316
Color? editorButtonPressedBg,
308317
Color? foreground,
309318
Color? icon,
319+
Color? iconSelected,
310320
Color? labelCounterUnread,
311321
Color? labelEdited,
312322
Color? labelMenuButton,
@@ -335,6 +345,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
335345
}) {
336346
return DesignVariables._(
337347
background: background ?? this.background,
348+
bgBotBar: bgBotBar ?? this.bgBotBar,
338349
bgContextMenu: bgContextMenu ?? this.bgContextMenu,
339350
bgCounterUnread: bgCounterUnread ?? this.bgCounterUnread,
340351
bgTopBar: bgTopBar ?? this.bgTopBar,
@@ -346,6 +357,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
346357
editorButtonPressedBg: editorButtonPressedBg ?? this.editorButtonPressedBg,
347358
foreground: foreground ?? this.foreground,
348359
icon: icon ?? this.icon,
360+
iconSelected: iconSelected ?? this.iconSelected,
349361
labelCounterUnread: labelCounterUnread ?? this.labelCounterUnread,
350362
labelEdited: labelEdited ?? this.labelEdited,
351363
labelMenuButton: labelMenuButton ?? this.labelMenuButton,
@@ -381,6 +393,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
381393
}
382394
return DesignVariables._(
383395
background: Color.lerp(background, other.background, t)!,
396+
bgBotBar: Color.lerp(bgBotBar, other.bgBotBar, t)!,
384397
bgContextMenu: Color.lerp(bgContextMenu, other.bgContextMenu, t)!,
385398
bgCounterUnread: Color.lerp(bgCounterUnread, other.bgCounterUnread, t)!,
386399
bgTopBar: Color.lerp(bgTopBar, other.bgTopBar, t)!,
@@ -392,6 +405,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
392405
editorButtonPressedBg: Color.lerp(editorButtonPressedBg, other.editorButtonPressedBg, t)!,
393406
foreground: Color.lerp(foreground, other.foreground, t)!,
394407
icon: Color.lerp(icon, other.icon, t)!,
408+
iconSelected: Color.lerp(iconSelected, other.iconSelected, t)!,
395409
labelCounterUnread: Color.lerp(labelCounterUnread, other.labelCounterUnread, t)!,
396410
labelEdited: Color.lerp(labelEdited, other.labelEdited, t)!,
397411
labelMenuButton: Color.lerp(labelMenuButton, other.labelMenuButton, t)!,

0 commit comments

Comments
 (0)