Skip to content

Commit 680b9ff

Browse files
committed
accounts-ui: Make accounts list scrollable
Before this fix, the list of accounts did not scroll when they were more than a screenful and would show an overflow error on the screen. After this fix, the list of accounts scrolls properly with no overflow error and without shifting other content offscreen. Fixes: #100
1 parent 81feda5 commit 680b9ff

File tree

3 files changed

+124
-7
lines changed

3 files changed

+124
-7
lines changed

lib/widgets/app.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,15 @@ class ChooseAccountPage extends StatelessWidget {
194194
child: Center(
195195
child: ConstrainedBox(
196196
constraints: const BoxConstraints(maxWidth: 400),
197-
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
198-
for (final (:accountId, :account) in globalStore.accountEntries)
199-
_buildAccountItem(context,
200-
accountId: accountId,
201-
title: Text(account.realmUrl.toString()),
202-
subtitle: Text(account.email)),
197+
child: Column(mainAxisSize: MainAxisSize.min, children: [
198+
Flexible(child: SingleChildScrollView(
199+
child: Column(mainAxisSize: MainAxisSize.min, children: [
200+
for (final (:accountId, :account) in globalStore.accountEntries)
201+
_buildAccountItem(context,
202+
accountId: accountId,
203+
title: Text(account.realmUrl.toString()),
204+
subtitle: Text(account.email)),
205+
]))),
203206
const SizedBox(height: 12),
204207
ElevatedButton(
205208
onPressed: () => Navigator.push(context,

test/flutter_checks.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import 'package:checks/checks.dart';
55
import 'package:flutter/material.dart';
66
import 'package:flutter/services.dart';
77

8+
extension RectChecks on Subject<Rect> {
9+
Subject<double> get top => has((d) => d.top, 'top');
10+
Subject<double> get bottom => has((d) => d.bottom, 'bottom');
11+
12+
// TODO others
13+
}
814
extension AnimationChecks<T> on Subject<Animation<T>> {
915
Subject<AnimationStatus> get status => has((d) => d.status, 'status');
1016
Subject<T> get value => has((d) => d.value, 'value');

test/widgets/app_test.dart

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import 'package:checks/checks.dart';
2-
import 'package:flutter/widgets.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
34
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:zulip/model/database.dart';
46
import 'package:zulip/widgets/app.dart';
57
import 'package:zulip/widgets/inbox.dart';
68
import 'package:zulip/widgets/page.dart';
9+
import 'package:zulip/widgets/store.dart';
710

811
import '../example_data.dart' as eg;
12+
import '../flutter_checks.dart';
913
import '../model/binding.dart';
1014
import '../test_navigation.dart';
1115
import 'page_checks.dart';
@@ -51,4 +55,108 @@ void main() {
5155
]);
5256
});
5357
});
58+
59+
group('ChooseAccountPage', () {
60+
Future<void> setupChooseAccountPage(WidgetTester tester, {
61+
required List<Account> accounts,
62+
}) async {
63+
addTearDown(testBinding.reset);
64+
65+
for (final account in accounts) {
66+
await testBinding.globalStore
67+
.insertAccount(account.toCompanion(false));
68+
}
69+
70+
await tester.pumpWidget(
71+
const MaterialApp(
72+
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
73+
supportedLocales: ZulipLocalizations.supportedLocales,
74+
home: GlobalStoreWidget(
75+
child: ChooseAccountPage())));
76+
77+
// global store gets loaded
78+
await tester.pumpAndSettle();
79+
}
80+
81+
List<Account> generateAccounts(int count) {
82+
return List.generate(count, (i) => eg.account(
83+
id: i,
84+
user: eg.user(fullName: 'User $i', email: 'user$i@example'),
85+
apiKey: 'user${i}apikey',
86+
));
87+
}
88+
89+
Finder findAccount(Account account) => find.text(account.email).hitTestable();
90+
91+
Finder findButton<T extends ButtonStyleButton>({required String withText}) {
92+
return find
93+
.descendant(of: find.bySubtype<T>(), matching: find.text(withText))
94+
.hitTestable();
95+
}
96+
97+
void checkAccountShown(Account account, {required bool expected}) {
98+
check(findAccount(account).evaluate()).length.equals(expected ? 1 : 0);
99+
}
100+
101+
void checkButtonShown<T extends ButtonStyleButton>({
102+
required String withText,
103+
required bool expected,
104+
}) {
105+
check(findButton<T>(withText: withText).evaluate())
106+
.length.equals(expected ? 1 : 0);
107+
}
108+
109+
testWidgets('accounts list is scrollable when more than a screenful', (tester) async {
110+
final accounts = generateAccounts(15);
111+
await setupChooseAccountPage(tester, accounts: accounts);
112+
113+
// Accounts list is more than a screenful
114+
// * First account is shown
115+
// * Last account is out of view
116+
checkAccountShown(accounts.first, expected: true);
117+
checkAccountShown(accounts.last, expected: false);
118+
119+
// Button to add an account is visible
120+
// and not moved offscreen by the long list of accounts
121+
checkButtonShown(withText: 'Add an account', expected: true);
122+
123+
// Accounts list is scrollable to the bottom
124+
await tester.scrollUntilVisible(findAccount(accounts.last), 50);
125+
checkAccountShown(accounts.last, expected: true);
126+
});
127+
128+
testWidgets('with just one account, the layout is centered', (tester) async {
129+
final account = eg.selfAccount;
130+
await setupChooseAccountPage(tester, accounts: [account]);
131+
132+
const buttonText = 'Add an account';
133+
checkAccountShown(account, expected: true);
134+
checkButtonShown(withText: buttonText, expected: true);
135+
136+
final screenHeight =
137+
(tester.view.physicalSize / tester.view.devicePixelRatio).height;
138+
139+
check(tester.getRect(findAccount(account)))
140+
..top.isGreaterThan(1 / 3 * screenHeight)
141+
..bottom.isLessThan(2 / 3 * screenHeight);
142+
143+
check(tester.getRect(findButton(withText: buttonText)))
144+
..top.isGreaterThan(1 / 3 * screenHeight)
145+
..bottom.isLessThan(2 / 3 * screenHeight);
146+
});
147+
148+
testWidgets('with no accounts, the Add an Account button is centered', (tester) async {
149+
await setupChooseAccountPage(tester, accounts: []);
150+
151+
const buttonText = 'Add an account';
152+
checkButtonShown(withText: buttonText, expected: true);
153+
154+
final screenHeight =
155+
(tester.view.physicalSize / tester.view.devicePixelRatio).height;
156+
157+
check(tester.getRect(findButton(withText: buttonText)))
158+
..top.isGreaterThan(1 / 3 * screenHeight)
159+
..bottom.isLessThan(2 / 3 * screenHeight);
160+
});
161+
});
54162
}

0 commit comments

Comments
 (0)