Skip to content

Commit 1b742f3

Browse files
committed
app: On launch, go to the last visited account
Fixes: #524
1 parent bef992a commit 1b742f3

File tree

6 files changed

+165
-17
lines changed

6 files changed

+165
-17
lines changed

lib/widgets/app.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,15 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
212212
}
213213

214214
final globalStore = GlobalStoreWidget.of(context);
215-
// TODO(#524) choose initial account as last one used
216-
final initialAccountId = globalStore.accounts.firstOrNull?.id;
215+
final lastAccountId = globalStore.settings.getInt(IntGlobalSetting.lastVisitedAccountId);
216+
final lastAccountMissing =
217+
lastAccountId == null || globalStore.getAccount(lastAccountId) == null;
218+
217219
return [
218-
if (initialAccountId == null)
220+
if (lastAccountMissing)
219221
MaterialWidgetRoute(page: const ChooseAccountPage())
220222
else
221-
HomePage.buildRoute(accountId: initialAccountId),
223+
HomePage.buildRoute(accountId: lastAccountId),
222224
];
223225
}
224226

lib/widgets/share.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../host/android_intents.dart';
1010
import '../log.dart';
1111
import '../model/binding.dart';
1212
import '../model/narrow.dart';
13+
import '../model/settings.dart';
1314
import 'app.dart';
1415
import 'color.dart';
1516
import 'compose_box.dart';
@@ -60,9 +61,12 @@ class ShareService {
6061

6162
final globalStore = GlobalStoreWidget.of(context);
6263

63-
// TODO(#524) use last account used, not the first in the list
6464
// TODO(#1779) allow selecting account, if there are multiple
65-
final accountId = globalStore.accounts.firstOrNull?.id;
65+
final lastAccountId = globalStore.settings.getInt(IntGlobalSetting.lastVisitedAccountId);
66+
final lastAccountMissing =
67+
lastAccountId == null || globalStore.getAccount(lastAccountId) == null;
68+
final accountId = lastAccountMissing
69+
? globalStore.accounts.firstOrNull?.id : lastAccountId;
6670

6771
if (accountId == null) {
6872
final zulipLocalizations = ZulipLocalizations.of(context);

test/notifications/open_test.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:zulip/host/notifications.dart';
1010
import 'package:zulip/model/database.dart';
1111
import 'package:zulip/model/localizations.dart';
1212
import 'package:zulip/model/narrow.dart';
13+
import 'package:zulip/model/settings.dart';
1314
import 'package:zulip/notifications/open.dart';
1415
import 'package:zulip/notifications/receive.dart';
1516
import 'package:zulip/widgets/app.dart';
@@ -20,6 +21,7 @@ import 'package:zulip/widgets/page.dart';
2021
import '../example_data.dart' as eg;
2122
import '../model/binding.dart';
2223
import '../model/narrow_checks.dart';
24+
import '../model/store_checks.dart';
2325
import '../stdlib_checks.dart';
2426
import '../test_navigation.dart';
2527
import '../widgets/checks.dart';
@@ -190,23 +192,41 @@ void main() {
190192
testWidgets('stream message', (tester) async {
191193
addTearDown(testBinding.reset);
192194
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
195+
await testBinding.globalStore.settings
196+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
193197
await prepare(tester);
194198
await checkOpenNotification(tester, eg.selfAccount, eg.streamMessage());
195199
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
196200

197201
testWidgets('direct message', (tester) async {
198202
addTearDown(testBinding.reset);
199203
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
204+
await testBinding.globalStore.settings
205+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
200206
await prepare(tester);
201207
await checkOpenNotification(tester, eg.selfAccount,
202208
eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]));
203209
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
204210

211+
testWidgets('changes last visited account', (tester) async {
212+
addTearDown(testBinding.reset);
213+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
214+
await testBinding.globalStore.add(eg.otherAccount, eg.initialSnapshot());
215+
await testBinding.globalStore.settings
216+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
217+
await prepare(tester);
218+
await checkOpenNotification(tester, eg.otherAccount, eg.streamMessage());
219+
check(testBinding.globalStore.settings)
220+
.getInt(IntGlobalSetting.lastVisitedAccountId).equals(eg.otherAccount.id);
221+
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
222+
205223
testWidgets('account queried by realmUrl origin component', (tester) async {
206224
addTearDown(testBinding.reset);
207225
await testBinding.globalStore.add(
208226
eg.selfAccount.copyWith(realmUrl: Uri.parse('http://chat.example')),
209227
eg.initialSnapshot());
228+
await testBinding.globalStore.settings
229+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
210230
await prepare(tester);
211231

212232
await checkOpenNotification(tester,
@@ -230,6 +250,8 @@ void main() {
230250
testWidgets('mismatching account', (tester) async {
231251
addTearDown(testBinding.reset);
232252
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
253+
await testBinding.globalStore.settings
254+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
233255
await prepare(tester);
234256
await openNotification(tester, eg.otherAccount, eg.streamMessage());
235257
await tester.pump();
@@ -254,6 +276,8 @@ void main() {
254276
for (final account in accounts) {
255277
await testBinding.globalStore.add(account, eg.initialSnapshot());
256278
}
279+
await testBinding.globalStore.settings
280+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
257281
await prepare(tester);
258282

259283
await checkOpenNotification(tester, accounts[0], eg.streamMessage());
@@ -265,6 +289,8 @@ void main() {
265289
testWidgets('wait for app to become ready', (tester) async {
266290
addTearDown(testBinding.reset);
267291
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
292+
await testBinding.globalStore.settings
293+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
268294
await prepare(tester, early: true);
269295
final message = eg.streamMessage();
270296
await openNotification(tester, eg.selfAccount, message);

test/widgets/app_test.dart

Lines changed: 115 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart';
66
import 'package:zulip/log.dart';
77
import 'package:zulip/model/actions.dart';
88
import 'package:zulip/model/database.dart';
9+
import 'package:zulip/model/settings.dart';
910
import 'package:zulip/widgets/app.dart';
1011
import 'package:zulip/widgets/home.dart';
1112
import 'package:zulip/widgets/page.dart';
@@ -43,18 +44,43 @@ void main() {
4344
]);
4445
});
4546

46-
testWidgets('when have accounts, go to home page for first account', (tester) async {
47-
// We'll need per-account data for the account that a page will be opened
48-
// for, but not for the other account.
49-
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
50-
await testBinding.globalStore.insertAccount(eg.otherAccount.toCompanion(false));
51-
await prepare(tester);
47+
group('when have accounts', () {
48+
testWidgets('with no last account visited, go to choose account', (tester) async {
49+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
50+
check(testBinding.globalStore.settings)
51+
.getInt(IntGlobalSetting.lastVisitedAccountId).isNull();
52+
await prepare(tester);
5253

53-
check(pushedRoutes).deepEquals(<Condition<Object?>>[
54-
(it) => it.isA<MaterialAccountWidgetRoute>()
55-
..accountId.equals(eg.selfAccount.id)
56-
..page.isA<HomePage>(),
57-
]);
54+
check(pushedRoutes).deepEquals(<Condition<Object?>>[
55+
(it) => it.isA<WidgetRoute>().page.isA<ChooseAccountPage>(),
56+
]);
57+
});
58+
59+
testWidgets('with last account id matching none of the accounts, go to choose account', (tester) async {
60+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
61+
await testBinding.globalStore.settings
62+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.otherAccount.id);
63+
check(testBinding.globalStore).getAccount(eg.otherAccount.id).isNull();
64+
await prepare(tester);
65+
66+
check(pushedRoutes).deepEquals(<Condition<Object?>>[
67+
(it) => it.isA<WidgetRoute>().page.isA<ChooseAccountPage>(),
68+
]);
69+
});
70+
71+
testWidgets('with last account visited, go to home page for last account', (tester) async {
72+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
73+
await testBinding.globalStore.add(eg.otherAccount, eg.initialSnapshot());
74+
await testBinding.globalStore.settings
75+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.otherAccount.id);
76+
await prepare(tester);
77+
78+
check(pushedRoutes).deepEquals(<Condition<Object?>>[
79+
(it) => it.isA<MaterialAccountWidgetRoute>()
80+
..accountId.equals(eg.otherAccount.id)
81+
..page.isA<HomePage>(),
82+
]);
83+
});
5884
});
5985
});
6086

@@ -82,6 +108,8 @@ void main() {
82108

83109
testWidgets('push route when removing last route on stack', (tester) async {
84110
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
111+
await testBinding.globalStore.settings
112+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
85113
await prepare(tester);
86114
// The navigator stack should contain only a home page route.
87115

@@ -99,6 +127,8 @@ void main() {
99127
testWidgets('push route when popping last route on stack', (tester) async {
100128
// Set up the loading of per-account data to fail.
101129
await testBinding.globalStore.insertAccount(eg.selfAccount.toCompanion(false));
130+
await testBinding.globalStore.settings
131+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
102132
testBinding.globalStore.loadPerAccountDuration = Duration.zero;
103133
testBinding.globalStore.loadPerAccountException = eg.apiExceptionUnauthorized();
104134
await prepare(tester);
@@ -133,6 +163,8 @@ void main() {
133163
const loadPerAccountDuration = Duration(seconds: 30);
134164
assert(loadPerAccountDuration > kTryAnotherAccountWaitPeriod);
135165
await testBinding.globalStore.insertAccount(eg.selfAccount.toCompanion(false));
166+
await testBinding.globalStore.settings
167+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
136168
testBinding.globalStore.loadPerAccountDuration = loadPerAccountDuration;
137169
testBinding.globalStore.loadPerAccountException = eg.apiExceptionUnauthorized();
138170
await prepare(tester);
@@ -282,6 +314,8 @@ void main() {
282314
addTearDown(testBinding.reset);
283315
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
284316
await testBinding.globalStore.add(eg.otherAccount, eg.initialSnapshot());
317+
await testBinding.globalStore.settings
318+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
285319

286320
final pushedRoutes = <Route<void>>[];
287321
final poppedRoutes = <Route<void>>[];
@@ -318,6 +352,47 @@ void main() {
318352
..page.isA<HomePage>();
319353
});
320354

355+
group('choosing an account changes the last visited account', () {
356+
testWidgets('first null, then changes to the chosen account', (tester) async {
357+
addTearDown(testBinding.reset);
358+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
359+
360+
await tester.pumpWidget(ZulipApp());
361+
await tester.pump();
362+
363+
check(testBinding.globalStore.settings)
364+
.getInt(IntGlobalSetting.lastVisitedAccountId).isNull();
365+
await tester.tap(find.text(eg.selfAccount.email));
366+
await tester.pump();
367+
check(testBinding.globalStore.settings)
368+
.getInt(IntGlobalSetting.lastVisitedAccountId).equals(eg.selfAccount.id);
369+
});
370+
371+
testWidgets('first non-null, then changes to the chosen account', (tester) async {
372+
addTearDown(testBinding.reset);
373+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
374+
await testBinding.globalStore.add(eg.otherAccount, eg.initialSnapshot());
375+
await testBinding.globalStore.settings
376+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
377+
378+
await tester.pumpWidget(ZulipApp());
379+
await tester.pump();
380+
381+
final navigator = await ZulipApp.navigator;
382+
unawaited(navigator.push(
383+
MaterialWidgetRoute(page: const ChooseAccountPage())));
384+
await tester.pump();
385+
await tester.pump();
386+
387+
check(testBinding.globalStore.settings)
388+
.getInt(IntGlobalSetting.lastVisitedAccountId).equals(eg.selfAccount.id);
389+
await tester.tap(find.text(eg.otherAccount.email));
390+
await tester.pump();
391+
check(testBinding.globalStore.settings)
392+
.getInt(IntGlobalSetting.lastVisitedAccountId).equals(eg.otherAccount.id);
393+
});
394+
});
395+
321396
group('log out', () {
322397
Future<(Widget, Widget)> prepare(WidgetTester tester, {
323398
List<Account>? accounts,
@@ -360,6 +435,35 @@ void main() {
360435
await tester.pumpAndSettle();
361436
check(testBinding.globalStore).accounts.deepEquals([eg.selfAccount]);
362437
});
438+
439+
group('last visited account', () {
440+
testWidgets('is the logged out one -> last account set to null', (tester) async {
441+
await testBinding.globalStore.settings
442+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
443+
444+
final (actionButton, _) = await prepare(tester,
445+
accounts: [eg.selfAccount, eg.otherAccount], logoutAccount: eg.selfAccount);
446+
await tester.tap(find.byWidget(actionButton));
447+
await tester.pump(TestGlobalStore.removeAccountDuration);
448+
check(testBinding.globalStore)
449+
..accounts.deepEquals([eg.otherAccount])
450+
..settings.getInt(IntGlobalSetting.lastVisitedAccountId).isNull();
451+
});
452+
453+
testWidgets('is not the logged out one -> last account not changed', (tester) async {
454+
await testBinding.globalStore.settings
455+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.otherAccount.id);
456+
457+
final (actionButton, _) = await prepare(tester,
458+
accounts: [eg.selfAccount, eg.otherAccount], logoutAccount: eg.selfAccount);
459+
await tester.tap(find.byWidget(actionButton));
460+
await tester.pump(TestGlobalStore.removeAccountDuration);
461+
check(testBinding.globalStore)
462+
..accounts.deepEquals([eg.otherAccount])
463+
..settings.getInt(IntGlobalSetting.lastVisitedAccountId)
464+
.equals(eg.otherAccount.id);
465+
});
466+
});
363467
});
364468
});
365469

test/widgets/home_test.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter_checks/flutter_checks.dart';
44
import 'package:flutter_test/flutter_test.dart';
55
import 'package:zulip/model/actions.dart';
66
import 'package:zulip/model/narrow.dart';
7+
import 'package:zulip/model/settings.dart';
78
import 'package:zulip/model/store.dart';
89
import 'package:zulip/widgets/about_zulip.dart';
910
import 'package:zulip/widgets/app.dart';
@@ -277,6 +278,8 @@ void main () {
277278
pushedRoutes = [];
278279
lastPoppedRoute = null;
279280
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
281+
await testBinding.globalStore.settings
282+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
280283

281284
await tester.pumpWidget(ZulipApp(navigatorObservers: [testNavObserver]));
282285
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
@@ -334,6 +337,8 @@ void main () {
334337
lastPoppedRoute = null;
335338
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
336339
await testBinding.globalStore.add(eg.otherAccount, eg.initialSnapshot());
340+
await testBinding.globalStore.settings
341+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
337342
await tester.pumpWidget(ZulipApp(navigatorObservers: [testNavObserver]));
338343
await tester.pump(Duration.zero); // wait for the loading page
339344
checkOnLoadingPage();
@@ -523,6 +528,8 @@ void main () {
523528
// Regression test for: https://github.com/zulip/zulip-flutter/issues/1219
524529
addTearDown(testBinding.reset);
525530
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
531+
await testBinding.globalStore.settings
532+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
526533
await tester.pumpWidget(const ZulipApp());
527534
await tester.pump(); // wait for the loading page
528535
checkOnLoadingPage();
@@ -539,6 +546,8 @@ void main () {
539546
// Regression test for: https://github.com/zulip/zulip-flutter/issues/1219
540547
addTearDown(testBinding.reset);
541548
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
549+
await testBinding.globalStore.settings
550+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
542551
await tester.pumpWidget(const ZulipApp());
543552
await tester.pump(); // wait for the loading page
544553
await tester.pump(); // wait for store

test/widgets/login_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:zulip/api/route/realm.dart';
1212
import 'package:zulip/model/binding.dart';
1313
import 'package:zulip/model/database.dart';
1414
import 'package:zulip/model/localizations.dart';
15+
import 'package:zulip/model/settings.dart';
1516
import 'package:zulip/widgets/app.dart';
1617
import 'package:zulip/widgets/home.dart';
1718
import 'package:zulip/widgets/login.dart';
@@ -262,6 +263,8 @@ void main() {
262263

263264
testWidgets('logging into a second account', (tester) async {
264265
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
266+
await testBinding.globalStore.settings
267+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.selfAccount.id);
265268
final serverSettings = eg.serverSettings();
266269
await prepare(tester, serverSettings);
267270
check(poppedRoutes).isEmpty();

0 commit comments

Comments
 (0)