Support account specific settings

This commit is contained in:
Ming Ming 2021-10-20 03:49:41 +08:00
parent 001ed6b514
commit 5fe70c3964
5 changed files with 146 additions and 31 deletions

View file

@ -72,6 +72,29 @@ class Account with EquatableMixin {
final List<String> _roots;
}
class AccountSettings with EquatableMixin {
const AccountSettings();
factory AccountSettings.fromJson(JsonObj json) {
return AccountSettings();
}
JsonObj toJson() => {};
@override
toString() {
return "$runtimeType {"
"}";
}
AccountSettings copyWith() {
return AccountSettings();
}
@override
get props => [];
}
extension AccountExtension on Account {
String get url => "$scheme://$address";
}

View file

@ -4,10 +4,15 @@ import 'package:event_bus/event_bus.dart';
import 'package:kiwi/kiwi.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/type.dart';
import 'package:nc_photos/use_case/compat/v32.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Pref {
static Future<void> init() async {
if (await CompatV32.isPrefNeedMigration()) {
await CompatV32.migratePref();
}
return SharedPreferences.getInstance().then((pref) {
_inst._pref = pref;
});
@ -15,15 +20,16 @@ class Pref {
factory Pref.inst() => _inst;
List<Account>? getAccounts() {
final jsonObjs = _pref.getStringList(_toKey(PrefKey.accounts));
return jsonObjs?.map((e) => Account.fromJson(jsonDecode(e))).toList();
List<PrefAccount>? getAccounts2() {
final jsonObjs = _pref.getStringList(_toKey(PrefKey.accounts2));
return jsonObjs?.map((e) => PrefAccount.fromJson(jsonDecode(e))).toList();
}
List<Account> getAccountsOr(List<Account> def) => getAccounts() ?? def;
Future<bool> setAccounts(List<Account> value) {
List<PrefAccount> getAccounts2Or(List<PrefAccount> def) =>
getAccounts2() ?? def;
Future<bool> setAccounts2(List<PrefAccount> value) {
final jsons = value.map((e) => jsonEncode(e.toJson())).toList();
return _setStringList(PrefKey.accounts, jsons);
return _setStringList(PrefKey.accounts2, jsons);
}
int? getCurrentAccountIndex() =>
@ -158,8 +164,8 @@ class Pref {
String _toKey(PrefKey key) {
switch (key) {
case PrefKey.accounts:
return "accounts";
case PrefKey.accounts2:
return "accounts2";
case PrefKey.currentAccountIndex:
return "currentAccountIndex";
case PrefKey.homePhotosZoomLevel:
@ -205,8 +211,48 @@ class Pref {
late SharedPreferences _pref;
}
class PrefAccount {
const PrefAccount(
this.account, [
this.settings = const AccountSettings(),
]);
factory PrefAccount.fromJson(JsonObj json) {
return PrefAccount(
Account.fromJson(json["account"].cast<String, dynamic>()),
AccountSettings.fromJson(json["settings"].cast<String, dynamic>()),
);
}
JsonObj toJson() => {
"account": account.toJson(),
"settings": settings.toJson(),
};
PrefAccount copyWith({
Account? account,
AccountSettings? settings,
}) {
return PrefAccount(
account ?? this.account,
settings ?? this.settings,
);
}
@override
toString() {
return "$runtimeType {"
"account: $account, "
"settings: $settings, "
"}";
}
final Account account;
final AccountSettings settings;
}
enum PrefKey {
accounts,
accounts2,
currentAccountIndex,
homePhotosZoomLevel,
albumBrowserZoomLevel,
@ -231,7 +277,9 @@ enum PrefKey {
extension PrefExtension on Pref {
Account? getCurrentAccount() {
try {
return Pref.inst().getAccounts()![Pref.inst().getCurrentAccountIndex()!];
return Pref.inst()
.getAccounts2()![Pref.inst().getCurrentAccountIndex()!]
.account;
} catch (_) {
return null;
}

View file

@ -0,0 +1,38 @@
import 'dart:convert';
import 'package:logging/logging.dart';
import 'package:nc_photos/type.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Compatibility helper for v32
class CompatV32 {
static Future<bool> isPrefNeedMigration() async {
final pref = await SharedPreferences.getInstance();
return pref.containsKey("accounts");
}
static Future<void> migratePref() async {
final pref = await SharedPreferences.getInstance();
final jsons = pref.getStringList("accounts");
if (jsons == null) {
return;
}
_log.info("[call] Migrate Pref.accounts");
final newJsons = <JsonObj>[];
for (final j in jsons) {
newJsons.add(<String, dynamic>{
"account": jsonDecode(j),
"settings": <String, dynamic>{},
});
}
if (await pref.setStringList(
"accounts2", newJsons.map((e) => jsonEncode(e)).toList())) {
_log.info("[call] Migrated ${newJsons.length} accounts");
await pref.remove("accounts");
} else {
_log.severe("[call] Failed while writing pref");
}
}
static final _log = Logger("use_case.compat.v32.CompatV32");
}

View file

@ -29,20 +29,20 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
@override
initState() {
super.initState();
_accounts = Pref.inst().getAccountsOr([]);
_accounts = Pref.inst().getAccounts2Or([]);
}
@override
build(BuildContext context) {
final otherAccountOptions = _accounts
.where((a) => a != widget.account)
.where((a) => a.account != widget.account)
.map((a) => SimpleDialogOption(
padding: const EdgeInsets.symmetric(horizontal: 8),
onPressed: () => _onItemPressed(a),
child: ListTile(
dense: true,
title: Text(a.url),
subtitle: Text(a.username),
title: Text(a.account.url),
subtitle: Text(a.account.username),
trailing: IconButton(
icon: Icon(
Icons.close,
@ -101,21 +101,21 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
);
}
void _onItemPressed(Account account) {
void _onItemPressed(PrefAccount account) {
Pref.inst().setCurrentAccountIndex(_accounts.indexOf(account));
Navigator.of(context).pushNamedAndRemoveUntil(Home.routeName, (_) => false,
arguments: HomeArguments(account));
arguments: HomeArguments(account.account));
}
void _onRemoveItemPressed(Account account) {
void _onRemoveItemPressed(PrefAccount account) {
try {
_removeAccount(account);
setState(() {
_accounts = Pref.inst().getAccounts()!;
_accounts = Pref.inst().getAccounts2()!;
});
SnackBarManager().showSnackBar(SnackBar(
content:
Text(L10n.global().removeServerSuccessNotification(account.url)),
content: Text(
L10n.global().removeServerSuccessNotification(account.account.url)),
duration: k.snackBarDurationNormal,
));
} catch (e) {
@ -166,22 +166,24 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
}
}
void _removeAccount(Account account) {
final currentAccounts = Pref.inst().getAccounts()!;
void _removeAccount(PrefAccount account) {
_log.info("[_removeAccount] Remove account: ${account.account}");
final currentAccounts = Pref.inst().getAccounts2()!;
final currentAccount =
currentAccounts[Pref.inst().getCurrentAccountIndex()!];
final newAccounts =
currentAccounts.where((element) => element != account).toList();
final newAccounts = currentAccounts
.where((element) => element.account != account.account)
.toList();
final newAccountIndex = newAccounts.indexOf(currentAccount);
if (newAccountIndex == -1) {
throw StateError("Active account not found in resulting account list");
}
Pref.inst()
..setAccounts(newAccounts)
..setAccounts2(newAccounts)
..setCurrentAccountIndex(newAccountIndex);
}
late List<Account> _accounts;
late List<PrefAccount> _accounts;
static final _log =
Logger("widget.account_picker_dialog._AccountPickerDialogState");

View file

@ -226,15 +226,19 @@ class _SignInState extends State<SignIn> {
}).then((result) {
if (result != null) {
// we've got a good account
final pa = PrefAccount(result);
// only signing in with app password would trigger distinct
final accounts =
(Pref.inst().getAccountsOr([])..add(result)).distinct();
final accounts = (Pref.inst().getAccounts2Or([])..add(pa)).distinctIf(
(a, b) => a.account == b.account,
(a) => a.account.hashCode,
);
Pref.inst()
..setAccounts(accounts)
..setCurrentAccountIndex(accounts.indexOf(result));
..setAccounts2(accounts)
..setCurrentAccountIndex(
accounts.indexWhere((element) => element.account == result));
Navigator.pushNamedAndRemoveUntil(
context, Home.routeName, (route) => false,
arguments: HomeArguments(result));
arguments: HomeArguments(pa.account));
}
});
}