Skip to content

Commit 39ab202

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

File tree

6 files changed

+138
-23
lines changed

6 files changed

+138
-23
lines changed

lib/widgets/app.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,16 @@ 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)
221+
// No account has been visited, or the last-visited account was logged out.
219222
MaterialWidgetRoute(page: const ChooseAccountPage())
220223
else
221-
HomePage.buildRoute(accountId: initialAccountId),
224+
HomePage.buildRoute(accountId: lastAccountId),
222225
];
223226
}
224227

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: 27 additions & 6 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';
@@ -74,10 +76,12 @@ void main() {
7476
TestZulipBinding.ensureInitialized();
7577
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
7678

77-
Future<void> init({required List<Account> accounts}) async {
79+
Future<void> init({required List<Account> accounts, required int? lastVisitedAccountId}) async {
7880
for (final account in accounts) {
7981
await testBinding.globalStore.add(account, eg.initialSnapshot());
8082
}
83+
await testBinding.globalStore.settings
84+
.setInt(IntGlobalSetting.lastVisitedAccountId, lastVisitedAccountId);
8185
addTearDown(testBinding.reset);
8286
testBinding.firebaseMessagingInitialToken = '012abc';
8387
addTearDown(NotificationService.debugReset);
@@ -89,11 +93,12 @@ void main() {
8993
late List<Route<void>> pushedRoutes;
9094

9195
void takeStartingRoutes({Account? account, bool withAccount = true}) {
92-
account ??= eg.selfAccount;
96+
final accountId = account?.id
97+
?? testBinding.globalStore.settings.getInt(IntGlobalSetting.lastVisitedAccountId);
9398
final expected = <Condition<Object?>>[
9499
if (withAccount)
95100
(it) => it.isA<MaterialAccountWidgetRoute>()
96-
..accountId.equals(account!.id)
101+
..accountId.equals(accountId!)
97102
..page.isA<HomePage>()
98103
else
99104
(it) => it.isA<WidgetRoute>().page.isA<ChooseAccountPage>(),
@@ -102,10 +107,14 @@ void main() {
102107
pushedRoutes.removeRange(0, expected.length);
103108
}
104109

105-
Future<void> prepare(WidgetTester tester,
106-
{bool early = false, List<Account>? accounts}) async {
110+
Future<void> prepare(WidgetTester tester, {
111+
bool early = false,
112+
List<Account>? accounts,
113+
int? lastVisitedAccountId,
114+
}) async {
107115
accounts ??= [eg.selfAccount];
108-
await init(accounts: accounts);
116+
await init(accounts: accounts,
117+
lastVisitedAccountId: lastVisitedAccountId ?? accounts.firstOrNull?.id);
109118
pushedRoutes = [];
110119
final testNavObserver = TestNavigatorObserver()
111120
..onPushed = (route, prevRoute) => pushedRoutes.add(route);
@@ -201,6 +210,18 @@ void main() {
201210
eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]));
202211
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
203212

213+
testWidgets('changes last visited account', (tester) async {
214+
addTearDown(testBinding.reset);
215+
await prepare(tester, accounts: [eg.selfAccount, eg.otherAccount],
216+
lastVisitedAccountId: eg.selfAccount.id);
217+
check(testBinding.globalStore.settings)
218+
.getInt(IntGlobalSetting.lastVisitedAccountId).equals(eg.selfAccount.id);
219+
220+
await checkOpenNotification(tester, eg.otherAccount, eg.streamMessage());
221+
check(testBinding.globalStore.settings)
222+
.getInt(IntGlobalSetting.lastVisitedAccountId).equals(eg.otherAccount.id);
223+
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
224+
204225
testWidgets('account queried by realmUrl origin component', (tester) async {
205226
addTearDown(testBinding.reset);
206227
await prepare(tester, accounts: [eg.selfAccount.copyWith(realmUrl: Uri.parse('http://chat.example'))]);

test/widgets/app_test.dart

Lines changed: 86 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 last account visited, go to home page for last account', (tester) async {
49+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
50+
await testBinding.globalStore.add(eg.otherAccount, eg.initialSnapshot());
51+
await testBinding.globalStore.settings
52+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.otherAccount.id);
53+
await prepare(tester);
54+
55+
check(pushedRoutes).deepEquals(<Condition<Object?>>[
56+
(it) => it.isA<MaterialAccountWidgetRoute>()
57+
..accountId.equals(eg.otherAccount.id)
58+
..page.isA<HomePage>(),
59+
]);
60+
});
5261

53-
check(pushedRoutes).deepEquals(<Condition<Object?>>[
54-
(it) => it.isA<MaterialAccountWidgetRoute>()
55-
..accountId.equals(eg.selfAccount.id)
56-
..page.isA<HomePage>(),
57-
]);
62+
testWidgets('with no last account visited, go to choose account', (tester) async {
63+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
64+
check(testBinding.globalStore.settings)
65+
.getInt(IntGlobalSetting.lastVisitedAccountId).isNull();
66+
await prepare(tester);
67+
68+
check(pushedRoutes).deepEquals(<Condition<Object?>>[
69+
(it) => it.isA<WidgetRoute>().page.isA<ChooseAccountPage>(),
70+
]);
71+
});
72+
73+
testWidgets('with last account id matching none of the accounts, go to choose account', (tester) async {
74+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
75+
await testBinding.globalStore.settings
76+
.setInt(IntGlobalSetting.lastVisitedAccountId, eg.otherAccount.id);
77+
check(testBinding.globalStore).getAccount(eg.otherAccount.id).isNull();
78+
await prepare(tester);
79+
80+
check(pushedRoutes).deepEquals(<Condition<Object?>>[
81+
(it) => it.isA<WidgetRoute>().page.isA<ChooseAccountPage>(),
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, {required Account account}) async {
323398
await setupChooseAccountPage(tester, accounts: [account]);

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)