Skip to content

Commit c569596

Browse files
committed
settings: Add IntGlobalSetting.lastVisitedAccountId, and keep updated
1 parent f1badd4 commit c569596

File tree

13 files changed

+1465
-3
lines changed

13 files changed

+1465
-3
lines changed

lib/model/database.dart

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class AppDatabase extends _$AppDatabase {
164164
// information on using the build_runner.
165165
// * Write a migration in `_migrationSteps` below.
166166
// * Write tests.
167-
static const int latestSchemaVersion = 10; // See note.
167+
static const int latestSchemaVersion = 11; // See note.
168168

169169
@override
170170
int get schemaVersion => latestSchemaVersion;
@@ -239,6 +239,26 @@ class AppDatabase extends _$AppDatabase {
239239
from9To10: (m, schema) async {
240240
await m.createTable(schema.intGlobalSettings);
241241
},
242+
from10To11: (Migrator m, Schema11 schema) async {
243+
// To provide a smooth experience for users when they first install a new
244+
// version of the app with support for the "last visited account" feature,
245+
// we set the first available account as the last visited one. This way,
246+
// the user is still taken straight to the first account, just as they
247+
// were used to before, instead of being shown the "choose account" page.
248+
final firstAccountId = await (m.database.selectOnly(schema.accounts)
249+
..addColumns([schema.accounts.id])
250+
..limit(1)
251+
).map((row) => row.read(schema.accounts.id)).getSingleOrNull();
252+
if (firstAccountId == null) return;
253+
254+
// Like `globalStore.setLastVisitedAccount(firstAccountId)`,
255+
// as of the schema at the time of this migration.
256+
await m.database.into(schema.intGlobalSettings).insert(
257+
RawValuesInsertable({
258+
'name': Variable('lastVisitedAccountId'),
259+
'value': Variable(firstAccountId),
260+
}));
261+
},
242262
);
243263

244264
Future<void> _createLatestSchema(Migrator m) async {

lib/model/schema_versions.g.dart

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,75 @@ i1.GeneratedColumn<int> _column_16(String aliasedName) =>
679679
false,
680680
type: i1.DriftSqlType.int,
681681
);
682+
683+
final class Schema11 extends i0.VersionedSchema {
684+
Schema11({required super.database}) : super(version: 11);
685+
@override
686+
late final List<i1.DatabaseSchemaEntity> entities = [
687+
globalSettings,
688+
boolGlobalSettings,
689+
intGlobalSettings,
690+
accounts,
691+
];
692+
late final Shape6 globalSettings = Shape6(
693+
source: i0.VersionedTable(
694+
entityName: 'global_settings',
695+
withoutRowId: false,
696+
isStrict: false,
697+
tableConstraints: [],
698+
columns: [_column_9, _column_10, _column_13, _column_14, _column_15],
699+
attachedDatabase: database,
700+
),
701+
alias: null,
702+
);
703+
late final Shape3 boolGlobalSettings = Shape3(
704+
source: i0.VersionedTable(
705+
entityName: 'bool_global_settings',
706+
withoutRowId: false,
707+
isStrict: false,
708+
tableConstraints: ['PRIMARY KEY(name)'],
709+
columns: [_column_11, _column_12],
710+
attachedDatabase: database,
711+
),
712+
alias: null,
713+
);
714+
late final Shape7 intGlobalSettings = Shape7(
715+
source: i0.VersionedTable(
716+
entityName: 'int_global_settings',
717+
withoutRowId: false,
718+
isStrict: false,
719+
tableConstraints: ['PRIMARY KEY(name)'],
720+
columns: [_column_11, _column_16],
721+
attachedDatabase: database,
722+
),
723+
alias: null,
724+
);
725+
late final Shape0 accounts = Shape0(
726+
source: i0.VersionedTable(
727+
entityName: 'accounts',
728+
withoutRowId: false,
729+
isStrict: false,
730+
tableConstraints: [
731+
'UNIQUE(realm_url, user_id)',
732+
'UNIQUE(realm_url, email)',
733+
],
734+
columns: [
735+
_column_0,
736+
_column_1,
737+
_column_2,
738+
_column_3,
739+
_column_4,
740+
_column_5,
741+
_column_6,
742+
_column_7,
743+
_column_8,
744+
],
745+
attachedDatabase: database,
746+
),
747+
alias: null,
748+
);
749+
}
750+
682751
i0.MigrationStepWithVersion migrationSteps({
683752
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
684753
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@@ -689,6 +758,7 @@ i0.MigrationStepWithVersion migrationSteps({
689758
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
690759
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
691760
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
761+
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
692762
}) {
693763
return (currentVersion, database) async {
694764
switch (currentVersion) {
@@ -737,6 +807,11 @@ i0.MigrationStepWithVersion migrationSteps({
737807
final migrator = i1.Migrator(database, schema);
738808
await from9To10(migrator, schema);
739809
return 10;
810+
case 10:
811+
final schema = Schema11(database: database);
812+
final migrator = i1.Migrator(database, schema);
813+
await from10To11(migrator, schema);
814+
return 11;
740815
default:
741816
throw ArgumentError.value('Unknown migration from $currentVersion');
742817
}
@@ -753,6 +828,7 @@ i1.OnUpgrade stepByStep({
753828
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
754829
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
755830
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
831+
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
756832
}) => i0.VersionedSchema.stepByStepHelper(
757833
step: migrationSteps(
758834
from1To2: from1To2,
@@ -764,5 +840,6 @@ i1.OnUpgrade stepByStep({
764840
from7To8: from7To8,
765841
from8To9: from8To9,
766842
from9To10: from9To10,
843+
from10To11: from10To11,
767844
),
768845
);

lib/model/settings.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,16 @@ enum IntGlobalSetting {
229229
/// (This is also handy to use in tests.)
230230
placeholderIgnore,
231231

232+
/// A pseudo-setting recording the id of the account the user has visited most
233+
/// recently, from the list of all the available accounts on the device.
234+
///
235+
/// In some cases, this may point to an account that doesn't actually exist on
236+
/// the device, for example, when the last visited account is logged out and
237+
/// another account is not visited during the same running session. For cases
238+
/// like these, it's the responsibility of the code that reads this value to
239+
/// check for the availability of the account that corresponds to this id.
240+
lastVisitedAccountId,
241+
232242
// Former settings which might exist in the database,
233243
// whose names should therefore not be reused:
234244
// (this list is empty so far)

lib/model/store.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,18 @@ abstract class GlobalStore extends ChangeNotifier {
269269

270270
Account? getAccount(int id) => _accounts[id];
271271

272+
Account? get lastVisitedAccount {
273+
final id = settings.getInt(IntGlobalSetting.lastVisitedAccountId);
274+
if (id == null) return null; // No account has been visited yet.
275+
276+
// (Will be null if `id` refers to an account that has been logged out.)
277+
return getAccount(id);
278+
}
279+
280+
Future<void> setLastVisitedAccount(int accountId) {
281+
return settings.setInt(IntGlobalSetting.lastVisitedAccountId, accountId);
282+
}
283+
272284
/// Add an account to the store, returning its assigned account ID.
273285
Future<int> insertAccount(AccountsCompanion data) async {
274286
final account = await doInsertAccount(data);

lib/widgets/app.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
254254
if (widget.navigatorObservers != null)
255255
...widget.navigatorObservers!,
256256
_PreventEmptyStack(),
257+
_UpdateLastVisitedAccount(GlobalStoreWidget.of(context)),
257258
],
258259
builder: (BuildContext context, Widget? child) {
259260
if (!ZulipApp.ready.value) {
@@ -305,6 +306,19 @@ class _PreventEmptyStack extends NavigatorObserver {
305306
}
306307
}
307308

309+
class _UpdateLastVisitedAccount extends NavigatorObserver {
310+
_UpdateLastVisitedAccount(this.globalStore);
311+
312+
final GlobalStore globalStore;
313+
314+
@override
315+
void didChangeTop(Route<void> topRoute, _) {
316+
if (topRoute case AccountPageRouteMixin(:var accountId)) {
317+
globalStore.setLastVisitedAccount(accountId);
318+
}
319+
}
320+
}
321+
308322
class ChooseAccountPage extends StatelessWidget {
309323
const ChooseAccountPage({super.key});
310324

test/model/database_test.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import 'schemas/schema_v2.dart' as v2;
1313
import 'schemas/schema_v3.dart' as v3;
1414
import 'schemas/schema_v4.dart' as v4;
1515
import 'schemas/schema_v5.dart' as v5;
16+
import 'schemas/schema_v10.dart' as v10;
17+
import 'schemas/schema_v11.dart' as v11;
1618
import 'store_checks.dart';
1719

1820
void main() {
@@ -400,6 +402,58 @@ void main() {
400402
});
401403

402404
// TODO(#1593) test upgrade to v9: legacyUpgradeState set to noLegacy
405+
406+
test('upgrade to v11: with accounts available, '
407+
'insert first account ID as the last-visited account ID', () async {
408+
final schema = await verifier.schemaAt(10);
409+
final before = v10.DatabaseAtV10(schema.newConnection());
410+
final firstAccountId = await before.into(before.accounts).insert(
411+
v10.AccountsCompanion.insert(
412+
realmUrl: 'https://chat.example/',
413+
userId: 1,
414+
415+
apiKey: '1234',
416+
zulipVersion: '10.0',
417+
zulipMergeBase: const Value('10.0'),
418+
zulipFeatureLevel: 370,
419+
));
420+
await before.into(before.accounts).insert(
421+
v10.AccountsCompanion.insert(
422+
realmUrl: 'https://example.com/',
423+
userId: 2,
424+
425+
apiKey: '4321',
426+
zulipVersion: '11.0',
427+
zulipMergeBase: const Value('11.0'),
428+
zulipFeatureLevel: 420,
429+
));
430+
await before.close();
431+
432+
final db = AppDatabase(schema.newConnection());
433+
await verifier.migrateAndValidate(db, 11);
434+
await db.close();
435+
436+
final after = v11.DatabaseAtV11(schema.newConnection());
437+
final intGlobalSettings = await after.select(after.intGlobalSettings).getSingle();
438+
check(intGlobalSettings.name).equals(IntGlobalSetting.lastVisitedAccountId.name);
439+
check(intGlobalSettings.value).equals(firstAccountId);
440+
await after.close();
441+
});
442+
443+
test("upgrade to v11: with no accounts available, don't set last-visited account ID", () async {
444+
final schema = await verifier.schemaAt(10);
445+
final before = v10.DatabaseAtV10(schema.newConnection());
446+
check(await before.select(before.accounts).get()).isEmpty();
447+
await before.close();
448+
449+
final db = AppDatabase(schema.newConnection());
450+
await verifier.migrateAndValidate(db, 11);
451+
await db.close();
452+
453+
final after = v11.DatabaseAtV11(schema.newConnection());
454+
check(await after.select(after.intGlobalSettings).get()).isEmpty();
455+
await after.close();
456+
});
403457
});
404458
}
405459

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"global_settings","was_declared_in_moor":false,"columns":[{"name":"theme_setting","getter_name":"themeSetting","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter<ThemeSetting>(ThemeSetting.values)","dart_type_name":"ThemeSetting"}},{"name":"browser_preference","getter_name":"browserPreference","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter<BrowserPreference>(BrowserPreference.values)","dart_type_name":"BrowserPreference"}},{"name":"visit_first_unread","getter_name":"visitFirstUnread","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter<VisitFirstUnreadSetting>(VisitFirstUnreadSetting.values)","dart_type_name":"VisitFirstUnreadSetting"}},{"name":"mark_read_on_scroll","getter_name":"markReadOnScroll","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter<MarkReadOnScrollSetting>(MarkReadOnScrollSetting.values)","dart_type_name":"MarkReadOnScrollSetting"}},{"name":"legacy_upgrade_state","getter_name":"legacyUpgradeState","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter<LegacyUpgradeState>(LegacyUpgradeState.values)","dart_type_name":"LegacyUpgradeState"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"bool_global_settings","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"value","getter_name":"value","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"value\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"value\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["name"]}},{"id":2,"references":[],"type":"table","data":{"name":"int_global_settings","was_declared_in_moor":false,"columns":[{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"value","getter_name":"value","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["name"]}},{"id":3,"references":[],"type":"table","data":{"name":"accounts","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"realm_url","getter_name":"realmUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const UriConverter()","dart_type_name":"Uri"}},{"name":"user_id","getter_name":"userId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"api_key","getter_name":"apiKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_version","getter_name":"zulipVersion","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_merge_base","getter_name":"zulipMergeBase","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_feature_level","getter_name":"zulipFeatureLevel","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"acked_push_token","getter_name":"ackedPushToken","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"unique_keys":[["realm_url","user_id"],["realm_url","email"]]}}]}

test/model/schemas/schema.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'schema_v7.dart' as v7;
1313
import 'schema_v8.dart' as v8;
1414
import 'schema_v9.dart' as v9;
1515
import 'schema_v10.dart' as v10;
16+
import 'schema_v11.dart' as v11;
1617

1718
class GeneratedHelper implements SchemaInstantiationHelper {
1819
@override
@@ -38,10 +39,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
3839
return v9.DatabaseAtV9(db);
3940
case 10:
4041
return v10.DatabaseAtV10(db);
42+
case 11:
43+
return v11.DatabaseAtV11(db);
4144
default:
4245
throw MissingSchemaException(version, versions);
4346
}
4447
}
4548

46-
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
49+
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
4750
}

0 commit comments

Comments
 (0)