Skip to content

Commit 4c593e2

Browse files
committed
ui: Extract ZulipAppBar for loading indicator.
Ideally we may have test to exhaustively ensure that all pages specific to a single PerAccountStore use ZulipAppBar. Some pages with `AppBar`s are skipped, such as AboutZulip and lightboxes. Fixes #465. Signed-off-by: Zixuan James Li <[email protected]>
1 parent 4fceab3 commit 4c593e2

8 files changed

+87
-11
lines changed

lib/widgets/app.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,10 @@ class ChooseAccountPage extends StatelessWidget {
179179
assert(!PerAccountStoreWidget.debugExistsOf(context));
180180
final globalStore = GlobalStoreWidget.of(context);
181181
return Scaffold(
182-
appBar: AppBar(
182+
appBar: ZulipAppBar(
183183
title: Text(zulipLocalizations.chooseAccountPageTitle),
184-
actions: const [ChooseAccountPageOverflowButton()]),
184+
actions: const [ChooseAccountPageOverflowButton()],
185+
isLoading: false),
185186
body: SafeArea(
186187
minimum: const EdgeInsets.fromLTRB(8, 0, 8, 8),
187188
child: Center(
@@ -252,7 +253,9 @@ class HomePage extends StatelessWidget {
252253
}
253254

254255
return Scaffold(
255-
appBar: AppBar(title: const Text("Home")),
256+
appBar: ZulipAppBar(
257+
title: const Text("Home"),
258+
isLoading: store.isLoading),
256259
body: Center(
257260
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
258261
DefaultTextStyle.merge(

lib/widgets/app_bar.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:flutter/material.dart';
2+
3+
class ZulipAppBar extends AppBar {
4+
ZulipAppBar({
5+
super.key,
6+
required super.title,
7+
super.backgroundColor,
8+
super.shape,
9+
super.actions,
10+
required bool isLoading,
11+
}) : super(
12+
bottom: PreferredSize(
13+
preferredSize: const Size.fromHeight(4.0),
14+
child: (isLoading)
15+
? const LinearProgressIndicator(minHeight: 4.0)
16+
: const SizedBox.shrink()));
17+
}

lib/widgets/inbox.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import '../api/model/model.dart';
44
import '../model/narrow.dart';
55
import '../model/recent_dm_conversations.dart';
66
import '../model/unreads.dart';
7+
import 'app_bar.dart';
78
import 'icons.dart';
89
import 'message_list.dart';
910
import 'page.dart';
@@ -160,7 +161,9 @@ class _InboxPageState extends State<InboxPage> with PerAccountStoreAwareStateMix
160161
}
161162

162163
return Scaffold(
163-
appBar: AppBar(title: const Text('Inbox')),
164+
appBar: ZulipAppBar(
165+
title: const Text('Inbox'),
166+
isLoading: store.isLoading),
164167
body: SafeArea(
165168
// Don't pad the bottom here; we want the list content to do that.
166169
bottom: false,

lib/widgets/message_list.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import '../model/store.dart';
1313
import '../model/typing_status.dart';
1414
import 'action_sheet.dart';
1515
import 'actions.dart';
16+
import 'app_bar.dart';
1617
import 'compose_box.dart';
1718
import 'content.dart';
1819
import 'dialog.dart';
@@ -255,12 +256,13 @@ class _MessageListPageState extends State<MessageListPage> implements MessageLis
255256
}
256257

257258
return Scaffold(
258-
appBar: AppBar(title: MessageListAppBarTitle(narrow: widget.narrow),
259+
appBar: ZulipAppBar(
260+
title: MessageListAppBarTitle(narrow: widget.narrow),
261+
isLoading: store.isLoading,
259262
backgroundColor: appBarBackgroundColor,
260263
shape: removeAppBarBottomBorder
261264
? const Border()
262-
: null, // i.e., inherit
263-
),
265+
: null),
264266
// TODO question for Vlad: for a stream view, should we set the Scaffold's
265267
// [backgroundColor] based on stream color, as in this frame:
266268
// https://www.figma.com/file/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=132%3A9684&mode=dev

lib/widgets/profile.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
66
import '../api/model/model.dart';
77
import '../model/content.dart';
88
import '../model/narrow.dart';
9+
import 'app_bar.dart';
910
import 'content.dart';
1011
import 'message_list.dart';
1112
import 'page.dart';
@@ -69,7 +70,9 @@ class ProfilePage extends StatelessWidget {
6970
];
7071

7172
return Scaffold(
72-
appBar: AppBar(title: Text(user.fullName)),
73+
appBar: ZulipAppBar(
74+
title: Text(user.fullName),
75+
isLoading: store.isLoading),
7376
body: SingleChildScrollView(
7477
child: Center(
7578
child: ConstrainedBox(
@@ -87,8 +90,11 @@ class _ProfileErrorPage extends StatelessWidget {
8790

8891
@override
8992
Widget build(BuildContext context) {
93+
final store = PerAccountStoreWidget.of(context);
9094
return Scaffold(
91-
appBar: AppBar(title: const Text('Error')),
95+
appBar: ZulipAppBar(
96+
title: const Text('Error'),
97+
isLoading: store.isLoading),
9298
body: const SingleChildScrollView(
9399
child: Padding(
94100
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 32),

lib/widgets/recent_dm_conversations.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
44
import '../model/narrow.dart';
55
import '../model/recent_dm_conversations.dart';
66
import '../model/unreads.dart';
7+
import 'app_bar.dart';
78
import 'content.dart';
89
import 'icons.dart';
910
import 'message_list.dart';
@@ -54,10 +55,13 @@ class _RecentDmConversationsPageState extends State<RecentDmConversationsPage> w
5455

5556
@override
5657
Widget build(BuildContext context) {
58+
final store = PerAccountStoreWidget.of(context);
5759
final zulipLocalizations = ZulipLocalizations.of(context);
5860
final sorted = model!.sorted;
5961
return Scaffold(
60-
appBar: AppBar(title: Text(zulipLocalizations.recentDmConversationsPageTitle)),
62+
appBar: ZulipAppBar(
63+
title: Text(zulipLocalizations.recentDmConversationsPageTitle),
64+
isLoading: store.isLoading),
6165
body: SafeArea(
6266
// Don't pad the bottom here; we want the list content to do that.
6367
bottom: false,

lib/widgets/subscription_list.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
33
import '../api/model/model.dart';
44
import '../model/narrow.dart';
55
import '../model/unreads.dart';
6+
import 'app_bar.dart';
67
import 'icons.dart';
78
import 'message_list.dart';
89
import 'page.dart';
@@ -89,7 +90,9 @@ class _SubscriptionListPageState extends State<SubscriptionListPage> with PerAcc
8990
_sortSubs(unpinned);
9091

9192
return Scaffold(
92-
appBar: AppBar(title: const Text("Channels")),
93+
appBar: ZulipAppBar(
94+
title: const Text("Channels"),
95+
isLoading: store.isLoading),
9396
body: SafeArea(
9497
// Don't pad the bottom here; we want the list content to do that.
9598
bottom: false,

test/widgets/app_bar_test.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'package:checks/checks.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:zulip/widgets/app_bar.dart';
5+
import 'package:zulip/widgets/profile.dart';
6+
7+
import '../example_data.dart' as eg;
8+
import '../model/binding.dart';
9+
import '../model/test_store.dart';
10+
import 'test_app.dart';
11+
12+
void main() {
13+
TestZulipBinding.ensureInitialized();
14+
15+
testWidgets('show progress indicator when loading', (tester) async {
16+
addTearDown(testBinding.reset);
17+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
18+
19+
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
20+
await store.addUser(eg.selfUser);
21+
22+
await tester.pumpWidget(TestZulipApp(accountId: eg.selfAccount.id,
23+
child: ProfilePage(userId: eg.selfUser.userId)));
24+
25+
final finder = find.descendant(
26+
of: find.byType(ZulipAppBar),
27+
matching: find.byType(LinearProgressIndicator));
28+
29+
await tester.pumpAndSettle();
30+
final rectBefore = tester.getRect(find.byType(ZulipAppBar));
31+
check(finder.evaluate()).isEmpty();
32+
store.isLoading = true;
33+
34+
await tester.pump();
35+
check(tester.getRect(find.byType(ZulipAppBar))).equals(rectBefore);
36+
check(finder.evaluate()).single;
37+
});
38+
}

0 commit comments

Comments
 (0)