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; 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 { extension AccountExtension on Account {
String get url => "$scheme://$address"; 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:kiwi/kiwi.dart';
import 'package:nc_photos/account.dart'; import 'package:nc_photos/account.dart';
import 'package:nc_photos/event/event.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'; import 'package:shared_preferences/shared_preferences.dart';
class Pref { class Pref {
static Future<void> init() async { static Future<void> init() async {
if (await CompatV32.isPrefNeedMigration()) {
await CompatV32.migratePref();
}
return SharedPreferences.getInstance().then((pref) { return SharedPreferences.getInstance().then((pref) {
_inst._pref = pref; _inst._pref = pref;
}); });
@ -15,15 +20,16 @@ class Pref {
factory Pref.inst() => _inst; factory Pref.inst() => _inst;
List<Account>? getAccounts() { List<PrefAccount>? getAccounts2() {
final jsonObjs = _pref.getStringList(_toKey(PrefKey.accounts)); final jsonObjs = _pref.getStringList(_toKey(PrefKey.accounts2));
return jsonObjs?.map((e) => Account.fromJson(jsonDecode(e))).toList(); return jsonObjs?.map((e) => PrefAccount.fromJson(jsonDecode(e))).toList();
} }
List<Account> getAccountsOr(List<Account> def) => getAccounts() ?? def; List<PrefAccount> getAccounts2Or(List<PrefAccount> def) =>
Future<bool> setAccounts(List<Account> value) { getAccounts2() ?? def;
Future<bool> setAccounts2(List<PrefAccount> value) {
final jsons = value.map((e) => jsonEncode(e.toJson())).toList(); final jsons = value.map((e) => jsonEncode(e.toJson())).toList();
return _setStringList(PrefKey.accounts, jsons); return _setStringList(PrefKey.accounts2, jsons);
} }
int? getCurrentAccountIndex() => int? getCurrentAccountIndex() =>
@ -158,8 +164,8 @@ class Pref {
String _toKey(PrefKey key) { String _toKey(PrefKey key) {
switch (key) { switch (key) {
case PrefKey.accounts: case PrefKey.accounts2:
return "accounts"; return "accounts2";
case PrefKey.currentAccountIndex: case PrefKey.currentAccountIndex:
return "currentAccountIndex"; return "currentAccountIndex";
case PrefKey.homePhotosZoomLevel: case PrefKey.homePhotosZoomLevel:
@ -205,8 +211,48 @@ class Pref {
late SharedPreferences _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 { enum PrefKey {
accounts, accounts2,
currentAccountIndex, currentAccountIndex,
homePhotosZoomLevel, homePhotosZoomLevel,
albumBrowserZoomLevel, albumBrowserZoomLevel,
@ -231,7 +277,9 @@ enum PrefKey {
extension PrefExtension on Pref { extension PrefExtension on Pref {
Account? getCurrentAccount() { Account? getCurrentAccount() {
try { try {
return Pref.inst().getAccounts()![Pref.inst().getCurrentAccountIndex()!]; return Pref.inst()
.getAccounts2()![Pref.inst().getCurrentAccountIndex()!]
.account;
} catch (_) { } catch (_) {
return null; 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 @override
initState() { initState() {
super.initState(); super.initState();
_accounts = Pref.inst().getAccountsOr([]); _accounts = Pref.inst().getAccounts2Or([]);
} }
@override @override
build(BuildContext context) { build(BuildContext context) {
final otherAccountOptions = _accounts final otherAccountOptions = _accounts
.where((a) => a != widget.account) .where((a) => a.account != widget.account)
.map((a) => SimpleDialogOption( .map((a) => SimpleDialogOption(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
onPressed: () => _onItemPressed(a), onPressed: () => _onItemPressed(a),
child: ListTile( child: ListTile(
dense: true, dense: true,
title: Text(a.url), title: Text(a.account.url),
subtitle: Text(a.username), subtitle: Text(a.account.username),
trailing: IconButton( trailing: IconButton(
icon: Icon( icon: Icon(
Icons.close, 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)); Pref.inst().setCurrentAccountIndex(_accounts.indexOf(account));
Navigator.of(context).pushNamedAndRemoveUntil(Home.routeName, (_) => false, Navigator.of(context).pushNamedAndRemoveUntil(Home.routeName, (_) => false,
arguments: HomeArguments(account)); arguments: HomeArguments(account.account));
} }
void _onRemoveItemPressed(Account account) { void _onRemoveItemPressed(PrefAccount account) {
try { try {
_removeAccount(account); _removeAccount(account);
setState(() { setState(() {
_accounts = Pref.inst().getAccounts()!; _accounts = Pref.inst().getAccounts2()!;
}); });
SnackBarManager().showSnackBar(SnackBar( SnackBarManager().showSnackBar(SnackBar(
content: content: Text(
Text(L10n.global().removeServerSuccessNotification(account.url)), L10n.global().removeServerSuccessNotification(account.account.url)),
duration: k.snackBarDurationNormal, duration: k.snackBarDurationNormal,
)); ));
} catch (e) { } catch (e) {
@ -166,22 +166,24 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
} }
} }
void _removeAccount(Account account) { void _removeAccount(PrefAccount account) {
final currentAccounts = Pref.inst().getAccounts()!; _log.info("[_removeAccount] Remove account: ${account.account}");
final currentAccounts = Pref.inst().getAccounts2()!;
final currentAccount = final currentAccount =
currentAccounts[Pref.inst().getCurrentAccountIndex()!]; currentAccounts[Pref.inst().getCurrentAccountIndex()!];
final newAccounts = final newAccounts = currentAccounts
currentAccounts.where((element) => element != account).toList(); .where((element) => element.account != account.account)
.toList();
final newAccountIndex = newAccounts.indexOf(currentAccount); final newAccountIndex = newAccounts.indexOf(currentAccount);
if (newAccountIndex == -1) { if (newAccountIndex == -1) {
throw StateError("Active account not found in resulting account list"); throw StateError("Active account not found in resulting account list");
} }
Pref.inst() Pref.inst()
..setAccounts(newAccounts) ..setAccounts2(newAccounts)
..setCurrentAccountIndex(newAccountIndex); ..setCurrentAccountIndex(newAccountIndex);
} }
late List<Account> _accounts; late List<PrefAccount> _accounts;
static final _log = static final _log =
Logger("widget.account_picker_dialog._AccountPickerDialogState"); Logger("widget.account_picker_dialog._AccountPickerDialogState");

View file

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