mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-02 06:46:22 +01:00
Move account settings out of pref
This commit is contained in:
parent
617f121810
commit
dbd9679cba
17 changed files with 492 additions and 256 deletions
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:nc_photos/ci_string.dart';
|
||||
|
@ -7,6 +9,7 @@ import 'package:nc_photos/type.dart';
|
|||
/// Details of a remote Nextcloud server account
|
||||
class Account with EquatableMixin {
|
||||
Account(
|
||||
this.id,
|
||||
this.scheme,
|
||||
String address,
|
||||
this.username,
|
||||
|
@ -20,6 +23,7 @@ class Account with EquatableMixin {
|
|||
}
|
||||
|
||||
Account copyWith({
|
||||
String? id,
|
||||
String? scheme,
|
||||
String? address,
|
||||
CiString? username,
|
||||
|
@ -27,6 +31,7 @@ class Account with EquatableMixin {
|
|||
List<String>? roots,
|
||||
}) {
|
||||
return Account(
|
||||
id ?? this.id,
|
||||
scheme ?? this.scheme,
|
||||
address ?? this.address,
|
||||
username ?? this.username,
|
||||
|
@ -35,9 +40,16 @@ class Account with EquatableMixin {
|
|||
);
|
||||
}
|
||||
|
||||
static String newId() {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final random = Random().nextInt(0xFFFFFF);
|
||||
return "${timestamp.toRadixString(16)}-${random.toRadixString(16).padLeft(6, '0')}";
|
||||
}
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"id: '$id', "
|
||||
"scheme: '$scheme', "
|
||||
"address: '${kDebugMode ? address : "***"}', "
|
||||
"username: '${kDebugMode ? username : "***"}', "
|
||||
|
@ -47,13 +59,15 @@ class Account with EquatableMixin {
|
|||
}
|
||||
|
||||
Account.fromJson(JsonObj json)
|
||||
: scheme = json["scheme"],
|
||||
: id = json["id"],
|
||||
scheme = json["scheme"],
|
||||
address = json["address"],
|
||||
username = CiString(json["username"]),
|
||||
password = json["password"],
|
||||
_roots = json["roots"].cast<String>();
|
||||
|
||||
JsonObj toJson() => {
|
||||
"id": id,
|
||||
"scheme": scheme,
|
||||
"address": address,
|
||||
"username": username.toString(),
|
||||
|
@ -62,10 +76,11 @@ class Account with EquatableMixin {
|
|||
};
|
||||
|
||||
@override
|
||||
List<Object> get props => [scheme, address, username, password, _roots];
|
||||
List<Object> get props => [id, scheme, address, username, password, _roots];
|
||||
|
||||
List<String> get roots => _roots;
|
||||
|
||||
final String id;
|
||||
final String scheme;
|
||||
final String address;
|
||||
final CiString username;
|
||||
|
@ -73,58 +88,6 @@ class Account with EquatableMixin {
|
|||
final List<String> _roots;
|
||||
}
|
||||
|
||||
class AccountSettings with EquatableMixin {
|
||||
const AccountSettings({
|
||||
this.isEnableFaceRecognitionApp = true,
|
||||
this.shareFolder = "",
|
||||
});
|
||||
|
||||
factory AccountSettings.fromJson(JsonObj json) {
|
||||
return AccountSettings(
|
||||
isEnableFaceRecognitionApp: json["isEnableFaceRecognitionApp"] ?? true,
|
||||
shareFolder: json["shareFolder"] ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
JsonObj toJson() => {
|
||||
"isEnableFaceRecognitionApp": isEnableFaceRecognitionApp,
|
||||
"shareFolder": shareFolder,
|
||||
};
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"isEnableFaceRecognitionApp: $isEnableFaceRecognitionApp, "
|
||||
"shareFolder: $shareFolder, "
|
||||
"}";
|
||||
}
|
||||
|
||||
AccountSettings copyWith({
|
||||
bool? isEnableFaceRecognitionApp,
|
||||
String? shareFolder,
|
||||
}) {
|
||||
return AccountSettings(
|
||||
isEnableFaceRecognitionApp:
|
||||
isEnableFaceRecognitionApp ?? this.isEnableFaceRecognitionApp,
|
||||
shareFolder: shareFolder ?? this.shareFolder,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
get props => [
|
||||
isEnableFaceRecognitionApp,
|
||||
shareFolder,
|
||||
];
|
||||
|
||||
final bool isEnableFaceRecognitionApp;
|
||||
|
||||
/// Path of the share folder
|
||||
///
|
||||
/// Share folder is where files shared with you are initially placed. Must
|
||||
/// match the value of share_folder in config.php
|
||||
final String shareFolder;
|
||||
}
|
||||
|
||||
extension AccountExtension on Account {
|
||||
String get url => "$scheme://$address";
|
||||
|
||||
|
|
|
@ -274,7 +274,7 @@ class ScanAccountDirBloc
|
|||
// no data in this bloc, ignore
|
||||
return;
|
||||
}
|
||||
if (ev.key == PrefKey.accounts2) {
|
||||
if (ev.key == PrefKey.accounts3) {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
|
@ -301,9 +301,9 @@ class ScanAccountDirBloc
|
|||
try {
|
||||
final fileRepo = FileRepo(dataSrc);
|
||||
// include files shared with this account
|
||||
final settings = Pref().getAccountSettings(account);
|
||||
final shareDir =
|
||||
File(path: file_util.unstripPath(account, settings.shareFolder));
|
||||
final settings = AccountPref.of(account);
|
||||
final shareDir = File(
|
||||
path: file_util.unstripPath(account, settings.getShareFolderOr()));
|
||||
bool isShareDirIncluded = false;
|
||||
|
||||
for (final r in account.roots) {
|
||||
|
@ -320,8 +320,12 @@ class ScanAccountDirBloc
|
|||
}
|
||||
|
||||
if (!isShareDirIncluded) {
|
||||
final files = await Ls(fileRepo)(account,
|
||||
File(path: file_util.unstripPath(account, settings.shareFolder)));
|
||||
final files = await Ls(fileRepo)(
|
||||
account,
|
||||
File(
|
||||
path: file_util.unstripPath(account, settings.getShareFolderOr()),
|
||||
),
|
||||
);
|
||||
final sharedFiles =
|
||||
files.where((f) => !f.isOwned(account.username)).toList();
|
||||
yield ScanAccountDirBlocSuccess(getState().files + sharedFiles);
|
||||
|
@ -347,9 +351,9 @@ class ScanAccountDirBloc
|
|||
}
|
||||
}
|
||||
|
||||
final settings = Pref().getAccountSettings(account);
|
||||
final settings = AccountPref.of(account);
|
||||
final shareDir =
|
||||
File(path: file_util.unstripPath(account, settings.shareFolder));
|
||||
File(path: file_util.unstripPath(account, settings.getShareFolderOr()));
|
||||
if (file_util.isUnderDir(file, shareDir)) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
|||
import 'package:nc_photos/platform/features.dart' as features;
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/pref_util.dart' as pref_util;
|
||||
import 'package:nc_photos/widget/my_app.dart';
|
||||
|
||||
void main() async {
|
||||
|
@ -34,6 +35,7 @@ void main() async {
|
|||
_initLog();
|
||||
_initKiwi();
|
||||
await _initPref();
|
||||
await _initAccountPrefs();
|
||||
await _initDeviceInfo();
|
||||
_initBloc();
|
||||
_initEquatable();
|
||||
|
@ -105,6 +107,17 @@ Future<void> _initPref() async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _initAccountPrefs() async {
|
||||
for (final a in Pref().getAccounts3Or([])) {
|
||||
try {
|
||||
AccountPref.setGlobalInstance(a, await pref_util.loadAccountPref(a));
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout("[_initAccountPrefs] Failed reading pref for account: $a", e,
|
||||
stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initDeviceInfo() async {
|
||||
if (platform_k.isAndroid) {
|
||||
await AndroidInfo.init();
|
||||
|
@ -150,3 +163,5 @@ class _BlocObserver extends BlocObserver {
|
|||
|
||||
static final _log = Logger("main._BlocObserver");
|
||||
}
|
||||
|
||||
final _log = Logger("main");
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Store simple contents across different platforms
|
||||
///
|
||||
/// On mobile, the contents will be persisted as a file. On web, the contents
|
||||
|
@ -19,3 +21,30 @@ abstract class UniversalStorage {
|
|||
|
||||
Future<void> remove(String name);
|
||||
}
|
||||
|
||||
/// UniversalStorage backed by memory, useful in unit tests
|
||||
@visibleForTesting
|
||||
class UniversalMemoryStorage implements UniversalStorage {
|
||||
@override
|
||||
putBinary(String name, Uint8List content) async {
|
||||
data[name] = content;
|
||||
}
|
||||
|
||||
@override
|
||||
getBinary(String name) async => data[name];
|
||||
|
||||
@override
|
||||
putString(String name, String content) async {
|
||||
data[name] = content;
|
||||
}
|
||||
|
||||
@override
|
||||
getString(String name) async => data[name];
|
||||
|
||||
@override
|
||||
remove(String name) async {
|
||||
data.remove(name);
|
||||
}
|
||||
|
||||
final data = <String, dynamic>{};
|
||||
}
|
||||
|
|
247
lib/pref.dart
247
lib/pref.dart
|
@ -4,8 +4,9 @@ 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:nc_photos/mobile/platform.dart'
|
||||
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
||||
import 'package:nc_photos/use_case/compat/v34.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class Pref {
|
||||
|
@ -22,16 +23,15 @@ class Pref {
|
|||
_inst = pref;
|
||||
}
|
||||
|
||||
List<PrefAccount>? getAccounts2() {
|
||||
final jsonObjs = provider.getStringList(PrefKey.accounts2);
|
||||
return jsonObjs?.map((e) => PrefAccount.fromJson(jsonDecode(e))).toList();
|
||||
List<Account>? getAccounts3() {
|
||||
final jsonObjs = provider.getStringList(PrefKey.accounts3);
|
||||
return jsonObjs?.map((e) => Account.fromJson(jsonDecode(e))).toList();
|
||||
}
|
||||
|
||||
List<PrefAccount> getAccounts2Or(List<PrefAccount> def) =>
|
||||
getAccounts2() ?? def;
|
||||
Future<bool> setAccounts2(List<PrefAccount> value) {
|
||||
List<Account> getAccounts3Or(List<Account> def) => getAccounts3() ?? def;
|
||||
Future<bool> setAccounts3(List<Account> value) {
|
||||
final jsons = value.map((e) => jsonEncode(e.toJson())).toList();
|
||||
return provider.setStringList(PrefKey.accounts2, jsons);
|
||||
return provider.setStringList(PrefKey.accounts3, jsons);
|
||||
}
|
||||
|
||||
int? getCurrentAccountIndex() => provider.getInt(PrefKey.currentAccountIndex);
|
||||
|
@ -155,6 +155,43 @@ class Pref {
|
|||
static Pref? _inst;
|
||||
}
|
||||
|
||||
class AccountPref {
|
||||
AccountPref.scoped(this.provider);
|
||||
|
||||
static AccountPref of(Account account) {
|
||||
_insts.putIfAbsent(
|
||||
account.id, () => AccountPref.scoped(PrefMemoryProvider()));
|
||||
return _insts[account.id]!;
|
||||
}
|
||||
|
||||
/// Set the global [AccountPref] instance returned by the default constructor
|
||||
static void setGlobalInstance(Account account, AccountPref? pref) {
|
||||
if (pref != null) {
|
||||
assert(!_insts.containsKey(account.id));
|
||||
_insts[account.id] = pref;
|
||||
} else {
|
||||
assert(_insts.containsKey(account.id));
|
||||
_insts.remove(account.id);
|
||||
}
|
||||
}
|
||||
|
||||
bool? isEnableFaceRecognitionApp() =>
|
||||
provider.getBool(PrefKey.isEnableFaceRecognitionApp);
|
||||
bool isEnableFaceRecognitionAppOr([bool def = true]) =>
|
||||
isEnableFaceRecognitionApp() ?? def;
|
||||
Future<bool> setEnableFaceRecognitionApp(bool value) =>
|
||||
provider.setBool(PrefKey.isEnableFaceRecognitionApp, value);
|
||||
|
||||
String? getShareFolder() => provider.getString(PrefKey.shareFolder);
|
||||
String getShareFolderOr([String def = ""]) => getShareFolder() ?? def;
|
||||
Future<bool> setShareFolder(String value) =>
|
||||
provider.setString(PrefKey.shareFolder, value);
|
||||
|
||||
final PrefProvider provider;
|
||||
|
||||
static final _insts = <String, AccountPref>{};
|
||||
}
|
||||
|
||||
/// Provide the data for [Pref]
|
||||
abstract class PrefProvider {
|
||||
bool? getBool(PrefKey key);
|
||||
|
@ -163,9 +200,14 @@ abstract class PrefProvider {
|
|||
int? getInt(PrefKey key);
|
||||
Future<bool> setInt(PrefKey key, int value);
|
||||
|
||||
String? getString(PrefKey key);
|
||||
Future<bool> setString(PrefKey key, String value);
|
||||
|
||||
List<String>? getStringList(PrefKey key);
|
||||
Future<bool> setStringList(PrefKey key, List<String> value);
|
||||
|
||||
Future<bool> clear();
|
||||
|
||||
bool _onPostSet(bool result, PrefKey key, dynamic value) {
|
||||
if (result) {
|
||||
KiwiContainer().resolve<EventBus>().fire(PrefUpdatedEvent(key, value));
|
||||
|
@ -179,8 +221,12 @@ abstract class PrefProvider {
|
|||
/// [Pref] stored with [SharedPreferences] lib
|
||||
class PrefSharedPreferencesProvider extends PrefProvider {
|
||||
Future<void> init() async {
|
||||
if (await CompatV32.isPrefNeedMigration()) {
|
||||
await CompatV32.migratePref();
|
||||
// Obsolete, CompatV34 is compatible with pre v32 versions
|
||||
// if (await CompatV32.isPrefNeedMigration()) {
|
||||
// await CompatV32.migratePref();
|
||||
// }
|
||||
if (await CompatV34.isPrefNeedMigration()) {
|
||||
await CompatV34.migratePref(platform.UniversalStorage());
|
||||
}
|
||||
return SharedPreferences.getInstance().then((pref) {
|
||||
_pref = pref;
|
||||
|
@ -204,6 +250,15 @@ class PrefSharedPreferencesProvider extends PrefProvider {
|
|||
return _onPostSet(await _pref.setInt(key.toStringKey(), value), key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
getString(PrefKey key) => _pref.getString(key.toStringKey());
|
||||
|
||||
@override
|
||||
setString(PrefKey key, String value) async {
|
||||
return _onPostSet(
|
||||
await _pref.setString(key.toStringKey(), value), key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
getStringList(PrefKey key) => _pref.getStringList(key.toStringKey());
|
||||
|
||||
|
@ -213,42 +268,103 @@ class PrefSharedPreferencesProvider extends PrefProvider {
|
|||
await _pref.setStringList(key.toStringKey(), value), key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
clear() => _pref.clear();
|
||||
|
||||
late SharedPreferences _pref;
|
||||
}
|
||||
|
||||
/// [Pref] stored in memory
|
||||
/// [Pref] backed by [UniversalStorage]
|
||||
class PrefUniversalStorageProvider extends PrefProvider {
|
||||
PrefUniversalStorageProvider(this.name);
|
||||
|
||||
Future<void> init() async {
|
||||
final prefStr = await platform.UniversalStorage().getString(name) ?? "{}";
|
||||
_data
|
||||
..clear()
|
||||
..addAll(jsonDecode(prefStr));
|
||||
}
|
||||
|
||||
@override
|
||||
getBool(PrefKey key) => _get<bool>(key);
|
||||
@override
|
||||
setBool(PrefKey key, bool value) => _set(key, value);
|
||||
|
||||
@override
|
||||
getInt(PrefKey key) => _get<int>(key);
|
||||
@override
|
||||
setInt(PrefKey key, int value) => _set(key, value);
|
||||
|
||||
@override
|
||||
getString(PrefKey key) => _get<String>(key);
|
||||
@override
|
||||
setString(PrefKey key, String value) => _set(key, value);
|
||||
|
||||
@override
|
||||
getStringList(PrefKey key) => _get<List<String>>(key);
|
||||
@override
|
||||
setStringList(PrefKey key, List<String> value) => _set(key, value);
|
||||
|
||||
@override
|
||||
clear() async {
|
||||
await platform.UniversalStorage().remove(name);
|
||||
_data.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
T? _get<T>(PrefKey key) => _data[key.toStringKey()];
|
||||
|
||||
Future<bool> _set<T>(PrefKey key, T value) async {
|
||||
return _onPostSet(await _update(key, value), key, value);
|
||||
}
|
||||
|
||||
Future<bool> _update<T>(PrefKey key, T value) async {
|
||||
final newData = Map.of(_data)
|
||||
..addEntries([MapEntry(key.toStringKey(), value)]);
|
||||
await platform.UniversalStorage().putString(name, jsonEncode(newData));
|
||||
_data[key.toStringKey()] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
final String name;
|
||||
final _data = <String, dynamic>{};
|
||||
}
|
||||
|
||||
/// [Pref] stored in memory, useful in unit tests
|
||||
class PrefMemoryProvider extends PrefProvider {
|
||||
PrefMemoryProvider([
|
||||
Map<String, dynamic> initialData = const <String, dynamic>{},
|
||||
]) : _data = Map.of(initialData);
|
||||
|
||||
@override
|
||||
getBool(PrefKey key) => _data[key.toStringKey()];
|
||||
getBool(PrefKey key) => _get<bool>(key);
|
||||
@override
|
||||
setBool(PrefKey key, bool value) => _set(key, value);
|
||||
|
||||
@override
|
||||
setBool(PrefKey key, bool value) async {
|
||||
return _onPostSet(() {
|
||||
_data[key.toStringKey()] = value;
|
||||
return true;
|
||||
}(), key, value);
|
||||
getInt(PrefKey key) => _get<int>(key);
|
||||
@override
|
||||
setInt(PrefKey key, int value) => _set(key, value);
|
||||
|
||||
@override
|
||||
getString(PrefKey key) => _get<String>(key);
|
||||
@override
|
||||
setString(PrefKey key, String value) => _set(key, value);
|
||||
|
||||
@override
|
||||
getStringList(PrefKey key) => _get<List<String>>(key);
|
||||
@override
|
||||
setStringList(PrefKey key, List<String> value) => _set(key, value);
|
||||
|
||||
@override
|
||||
clear() async {
|
||||
_data.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
getInt(PrefKey key) => _data[key.toStringKey()];
|
||||
T? _get<T>(PrefKey key) => _data[key.toStringKey()];
|
||||
|
||||
@override
|
||||
setInt(PrefKey key, int value) async {
|
||||
return _onPostSet(() {
|
||||
_data[key.toStringKey()] = value;
|
||||
return true;
|
||||
}(), key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
getStringList(PrefKey key) => _data[key.toStringKey()];
|
||||
|
||||
@override
|
||||
setStringList(PrefKey key, List<String> value) async {
|
||||
Future<bool> _set<T>(PrefKey key, T value) async {
|
||||
return _onPostSet(() {
|
||||
_data[key.toStringKey()] = value;
|
||||
return true;
|
||||
|
@ -258,48 +374,8 @@ class PrefMemoryProvider extends PrefProvider {
|
|||
final Map<String, dynamic> _data;
|
||||
}
|
||||
|
||||
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 {
|
||||
accounts2,
|
||||
accounts3,
|
||||
currentAccountIndex,
|
||||
homePhotosZoomLevel,
|
||||
albumBrowserZoomLevel,
|
||||
|
@ -321,13 +397,17 @@ enum PrefKey {
|
|||
isAlbumBrowserShowDate,
|
||||
gpsMapProvider,
|
||||
hasShownSharedAlbumInfo,
|
||||
|
||||
// account pref
|
||||
isEnableFaceRecognitionApp,
|
||||
shareFolder,
|
||||
}
|
||||
|
||||
extension on PrefKey {
|
||||
String toStringKey() {
|
||||
switch (this) {
|
||||
case PrefKey.accounts2:
|
||||
return "accounts2";
|
||||
case PrefKey.accounts3:
|
||||
return "accounts3";
|
||||
case PrefKey.currentAccountIndex:
|
||||
return "currentAccountIndex";
|
||||
case PrefKey.homePhotosZoomLevel:
|
||||
|
@ -370,6 +450,12 @@ extension on PrefKey {
|
|||
return "gpsMapProvider";
|
||||
case PrefKey.hasShownSharedAlbumInfo:
|
||||
return "hasShownSharedAlbumInfo";
|
||||
|
||||
// account pref
|
||||
case PrefKey.isEnableFaceRecognitionApp:
|
||||
return "isEnableFaceRecognitionApp";
|
||||
case PrefKey.shareFolder:
|
||||
return "shareFolder";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -377,16 +463,9 @@ extension on PrefKey {
|
|||
extension PrefExtension on Pref {
|
||||
Account? getCurrentAccount() {
|
||||
try {
|
||||
return Pref().getAccounts2()![Pref().getCurrentAccountIndex()!].account;
|
||||
return Pref().getAccounts3()![Pref().getCurrentAccountIndex()!];
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
AccountSettings getAccountSettings(Account account) {
|
||||
return Pref()
|
||||
.getAccounts2()!
|
||||
.firstWhere((element) => element.account == account)
|
||||
.settings;
|
||||
}
|
||||
}
|
||||
|
|
8
lib/pref_util.dart
Normal file
8
lib/pref_util.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
|
||||
Future<AccountPref> loadAccountPref(Account account) async {
|
||||
final provider = PrefUniversalStorageProvider("accounts/${account.id}/pref");
|
||||
await provider.init();
|
||||
return AccountPref.scoped(provider);
|
||||
}
|
75
lib/use_case/compat/v34.dart
Normal file
75
lib/use_case/compat/v34.dart
Normal file
|
@ -0,0 +1,75 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/platform/universal_storage.dart';
|
||||
import 'package:nc_photos/type.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// Compatibility helper for v34
|
||||
class CompatV34 {
|
||||
static Future<bool> isPrefNeedMigration() async {
|
||||
final pref = await SharedPreferences.getInstance();
|
||||
return pref.containsKey("accounts2") || pref.containsKey("accounts");
|
||||
}
|
||||
|
||||
static Future<void> migratePref(UniversalStorage storage) async {
|
||||
final pref = await SharedPreferences.getInstance();
|
||||
if (pref.containsKey("accounts2")) {
|
||||
return _migratePrefV2(pref, storage);
|
||||
} else {
|
||||
return _migratePrefV1(pref, storage);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _migratePrefV2(
|
||||
SharedPreferences pref, UniversalStorage storage) async {
|
||||
final jsons = pref.getStringList("accounts2");
|
||||
if (jsons == null) {
|
||||
return;
|
||||
}
|
||||
_log.info("[migratePref] Migrate Pref.accounts2");
|
||||
final newJsons = <JsonObj>[];
|
||||
for (final j in jsons) {
|
||||
final JsonObj account2 = jsonDecode(j).cast<String, dynamic>();
|
||||
final id = Account.newId();
|
||||
account2["account"]["id"] = id;
|
||||
newJsons.add(account2["account"]);
|
||||
await storage.putString("accounts/$id/pref", jsonEncode(account2["settings"]));
|
||||
}
|
||||
if (await pref.setStringList(
|
||||
"accounts3", newJsons.map((e) => jsonEncode(e)).toList())) {
|
||||
_log.info("[migratePref] Migrated ${newJsons.length} accounts2");
|
||||
await pref.remove("accounts2");
|
||||
} else {
|
||||
_log.severe("[migratePref] Failed while writing pref");
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _migratePrefV1(
|
||||
SharedPreferences pref, UniversalStorage storage) async {
|
||||
final jsons = pref.getStringList("accounts");
|
||||
if (jsons == null) {
|
||||
return;
|
||||
}
|
||||
_log.info("[migratePref] Migrate Pref.accounts");
|
||||
final newJsons = <JsonObj>[];
|
||||
for (final j in jsons) {
|
||||
final JsonObj account = jsonDecode(j).cast<String, dynamic>();
|
||||
final id = Account.newId();
|
||||
account["id"] = id;
|
||||
newJsons.add(account);
|
||||
await storage.putString("accounts/$id/pref",
|
||||
"""{"isEnableFaceRecognitionApp":true,"shareFolder":""}""");
|
||||
}
|
||||
if (await pref.setStringList(
|
||||
"accounts3", newJsons.map((e) => jsonEncode(e)).toList())) {
|
||||
_log.info("[migratePref] Migrated ${newJsons.length} accounts");
|
||||
await pref.remove("accounts");
|
||||
} else {
|
||||
_log.severe("[migratePref] Failed while writing pref");
|
||||
}
|
||||
}
|
||||
|
||||
static final _log = Logger("use_case.compat.v34.CompatV34");
|
||||
}
|
|
@ -2,6 +2,7 @@ import 'package:logging/logging.dart';
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||
import 'package:nc_photos/use_case/list_potential_shared_album.dart';
|
||||
import 'package:nc_photos/use_case/move.dart';
|
||||
|
@ -10,10 +11,11 @@ import 'package:nc_photos/use_case/move.dart';
|
|||
class ImportPotentialSharedAlbum {
|
||||
ImportPotentialSharedAlbum(this.fileRepo, this.albumRepo);
|
||||
|
||||
Future<List<Album>> call(Account account, AccountSettings settings) async {
|
||||
Future<List<Album>> call(Account account, AccountPref accountPref) async {
|
||||
_log.info("[call] $account");
|
||||
final products = <Album>[];
|
||||
final files = await ListPotentialSharedAlbum(fileRepo)(account, settings);
|
||||
final files =
|
||||
await ListPotentialSharedAlbum(fileRepo)(account, accountPref);
|
||||
for (final f in files) {
|
||||
// check if the file is actually an album
|
||||
try {
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:logging/logging.dart';
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/use_case/ls.dart';
|
||||
|
||||
/// List all shared files that are potentially albums
|
||||
|
@ -10,11 +11,13 @@ import 'package:nc_photos/use_case/ls.dart';
|
|||
class ListPotentialSharedAlbum {
|
||||
ListPotentialSharedAlbum(this.fileRepo);
|
||||
|
||||
Future<List<File>> call(Account account, AccountSettings settings) async {
|
||||
Future<List<File>> call(Account account, AccountPref accountPref) async {
|
||||
final results = <File>[];
|
||||
final ls = await Ls(fileRepo)(
|
||||
account,
|
||||
File(path: file_util.unstripPath(account, settings.shareFolder)),
|
||||
File(
|
||||
path: file_util.unstripPath(account, accountPref.getShareFolderOr()),
|
||||
),
|
||||
);
|
||||
for (final f in ls) {
|
||||
// check owner
|
||||
|
|
|
@ -29,20 +29,20 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
|
|||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_accounts = Pref().getAccounts2Or([]);
|
||||
_accounts = Pref().getAccounts3Or([]);
|
||||
}
|
||||
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
final otherAccountOptions = _accounts
|
||||
.where((a) => a.account != widget.account)
|
||||
.where((a) => a != widget.account)
|
||||
.map((a) => SimpleDialogOption(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
onPressed: () => _onItemPressed(a),
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
title: Text(a.account.url),
|
||||
subtitle: Text(a.account.username.toString()),
|
||||
title: Text(a.url),
|
||||
subtitle: Text(a.username.toString()),
|
||||
trailing: IconButton(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
|
@ -101,21 +101,21 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
void _onItemPressed(PrefAccount account) {
|
||||
void _onItemPressed(Account account) {
|
||||
Pref().setCurrentAccountIndex(_accounts.indexOf(account));
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Home.routeName, (_) => false,
|
||||
arguments: HomeArguments(account.account));
|
||||
arguments: HomeArguments(account));
|
||||
}
|
||||
|
||||
void _onRemoveItemPressed(PrefAccount account) {
|
||||
Future<void> _onRemoveItemPressed(Account account) async {
|
||||
try {
|
||||
_removeAccount(account);
|
||||
await _removeAccount(account);
|
||||
setState(() {
|
||||
_accounts = Pref().getAccounts2()!;
|
||||
_accounts = Pref().getAccounts3()!;
|
||||
});
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
L10n.global().removeServerSuccessNotification(account.account.url)),
|
||||
content:
|
||||
Text(L10n.global().removeServerSuccessNotification(account.url)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
} catch (e) {
|
||||
|
@ -133,24 +133,27 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
|
|||
arguments: AccountSettingsWidgetArguments(widget.account));
|
||||
}
|
||||
|
||||
void _removeAccount(PrefAccount account) {
|
||||
_log.info("[_removeAccount] Remove account: ${account.account}");
|
||||
final currentAccounts = Pref().getAccounts2()!;
|
||||
final currentAccount =
|
||||
currentAccounts[Pref().getCurrentAccountIndex()!];
|
||||
final newAccounts = currentAccounts
|
||||
.where((element) => element.account != account.account)
|
||||
.toList();
|
||||
final newAccountIndex = newAccounts.indexOf(currentAccount);
|
||||
Future<void> _removeAccount(Account account) async {
|
||||
_log.info("[_removeAccount] Remove account: $account");
|
||||
final accounts = Pref().getAccounts3()!;
|
||||
final currentAccount = accounts[Pref().getCurrentAccountIndex()!];
|
||||
accounts.remove(account);
|
||||
final newAccountIndex = accounts.indexOf(currentAccount);
|
||||
if (newAccountIndex == -1) {
|
||||
throw StateError("Active account not found in resulting account list");
|
||||
}
|
||||
try {
|
||||
await AccountPref.of(account).provider.clear();
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_removeAccount] Failed while removing account pref", e, stackTrace);
|
||||
}
|
||||
Pref()
|
||||
..setAccounts2(newAccounts)
|
||||
..setAccounts3(accounts)
|
||||
..setCurrentAccountIndex(newAccountIndex);
|
||||
}
|
||||
|
||||
late List<PrefAccount> _accounts;
|
||||
late List<Account> _accounts;
|
||||
|
||||
static final _log =
|
||||
Logger("widget.account_picker_dialog._AccountPickerDialogState");
|
||||
|
|
|
@ -131,7 +131,7 @@ class _HomeState extends State<Home> {
|
|||
final albumRepo = AlbumRepo(AlbumRemoteDataSource());
|
||||
try {
|
||||
return await ImportPotentialSharedAlbum(fileRepo, albumRepo)(
|
||||
widget.account, Pref().getAccountSettings(widget.account));
|
||||
widget.account, AccountPref.of(widget.account));
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
"[_importPotentialSharedAlbum] Failed while ImportPotentialSharedAlbum",
|
||||
|
|
|
@ -459,10 +459,7 @@ class _HomeAlbumsState extends State<HomeAlbums>
|
|||
}
|
||||
}).map((e) => e.item2);
|
||||
itemStreamListItems = [
|
||||
if (Pref()
|
||||
.getAccountSettings(widget.account)
|
||||
.isEnableFaceRecognitionApp ==
|
||||
true)
|
||||
if (AccountPref.of(widget.account).isEnableFaceRecognitionAppOr())
|
||||
_buildPersonItem(context),
|
||||
_buildSharingItem(context),
|
||||
_buildArchiveItem(context),
|
||||
|
|
|
@ -408,9 +408,9 @@ class _AccountSettingsState extends State<AccountSettingsWidget> {
|
|||
super.initState();
|
||||
_account = widget.account;
|
||||
|
||||
final settings = Pref().getAccountSettings(_account);
|
||||
_isEnableFaceRecognitionApp = settings.isEnableFaceRecognitionApp;
|
||||
_shareFolder = settings.shareFolder;
|
||||
final settings = AccountPref.of(_account);
|
||||
_isEnableFaceRecognitionApp = settings.isEnableFaceRecognitionAppOr();
|
||||
_shareFolder = settings.getShareFolderOr();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -491,8 +491,8 @@ class _AccountSettingsState extends State<AccountSettingsWidget> {
|
|||
_log.fine("[_onIncludedFoldersPressed] No changes");
|
||||
return;
|
||||
}
|
||||
final accounts = Pref().getAccounts2()!;
|
||||
if (accounts.any((element) => element.account == result)) {
|
||||
final accounts = Pref().getAccounts3()!;
|
||||
if (accounts.contains(result)) {
|
||||
// conflict with another account. This normally won't happen because
|
||||
// the app passwords are unique to each entry, but just in case
|
||||
Navigator.of(context).pop();
|
||||
|
@ -503,7 +503,7 @@ class _AccountSettingsState extends State<AccountSettingsWidget> {
|
|||
return;
|
||||
}
|
||||
|
||||
final index = _findAccount(_account, accounts);
|
||||
final index = accounts.indexOf(_account);
|
||||
if (index < 0) {
|
||||
_log.shout("[_onIncludedFoldersPressed] Account not found: $_account");
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
|
@ -513,11 +513,8 @@ class _AccountSettingsState extends State<AccountSettingsWidget> {
|
|||
return;
|
||||
}
|
||||
|
||||
final newAccount = accounts[index].copyWith(
|
||||
account: result,
|
||||
);
|
||||
accounts[index] = newAccount;
|
||||
if (!await Pref().setAccounts2(accounts)) {
|
||||
accounts[index] = result;
|
||||
if (!await Pref().setAccounts3(accounts)) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().writePreferenceFailureNotification),
|
||||
duration: k.snackBarDurationNormal,
|
||||
|
@ -557,10 +554,7 @@ class _AccountSettingsState extends State<AccountSettingsWidget> {
|
|||
setState(() {
|
||||
_isEnableFaceRecognitionApp = value;
|
||||
});
|
||||
if (!await _modifyAccountSettings(
|
||||
_account,
|
||||
isEnableFaceRecognitionApp: value,
|
||||
)) {
|
||||
if (!await AccountPref.of(_account).setEnableFaceRecognitionApp(value)) {
|
||||
_log.severe("[_onEnableFaceRecognitionAppChanged] Failed writing pref");
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().writePreferenceFailureNotification),
|
||||
|
@ -582,10 +576,7 @@ class _AccountSettingsState extends State<AccountSettingsWidget> {
|
|||
setState(() {
|
||||
_shareFolder = value;
|
||||
});
|
||||
if (!await _modifyAccountSettings(
|
||||
_account,
|
||||
shareFolder: value,
|
||||
)) {
|
||||
if (!await AccountPref.of(_account).setShareFolder(value)) {
|
||||
_log.severe("[_setShareFolder] Failed writing pref");
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().writePreferenceFailureNotification),
|
||||
|
@ -601,36 +592,6 @@ class _AccountSettingsState extends State<AccountSettingsWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
static Future<bool> _modifyAccountSettings(
|
||||
Account account, {
|
||||
bool? isEnableFaceRecognitionApp,
|
||||
String? shareFolder,
|
||||
}) {
|
||||
try {
|
||||
final accounts = Pref().getAccounts2()!;
|
||||
final index = _findAccount(account, accounts);
|
||||
accounts[index] = accounts[index].copyWith(
|
||||
settings: accounts[index].settings.copyWith(
|
||||
isEnableFaceRecognitionApp: isEnableFaceRecognitionApp,
|
||||
shareFolder: shareFolder,
|
||||
),
|
||||
);
|
||||
return Pref().setAccounts2(accounts);
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe(
|
||||
"[_modifyAccountSettings] Failed while setting account settings",
|
||||
e,
|
||||
stackTrace);
|
||||
return Future.value(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the index of [account] in [Pref.getAccounts2]
|
||||
static int _findAccount(Account account, [List<PrefAccount>? accounts]) {
|
||||
final from = accounts ?? Pref().getAccounts2Or([]);
|
||||
return from.indexWhere((element) => element.account == account);
|
||||
}
|
||||
|
||||
bool _hasModified = false;
|
||||
late Account _account;
|
||||
late bool _isEnableFaceRecognitionApp;
|
||||
|
|
|
@ -325,7 +325,7 @@ class _SharingBrowserState extends State<SharingBrowser> {
|
|||
final albumRepo = AlbumRepo(AlbumRemoteDataSource());
|
||||
try {
|
||||
return await ImportPotentialSharedAlbum(fileRepo, albumRepo)(
|
||||
widget.account, Pref().getAccountSettings(widget.account));
|
||||
widget.account, AccountPref.of(widget.account));
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout(
|
||||
"[_importPotentialSharedAlbum] Failed while ImportPotentialSharedAlbum",
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:nc_photos/help_utils.dart' as help_utils;
|
|||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/pref_util.dart' as pref_util;
|
||||
import 'package:nc_photos/string_extension.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/widget/connect.dart';
|
||||
|
@ -229,36 +230,44 @@ class _SignInState extends State<SignIn> {
|
|||
);
|
||||
}
|
||||
|
||||
void _connect() {
|
||||
Future<void> _connect() async {
|
||||
_formKey.currentState!.save();
|
||||
final account = Account(_formValue.scheme, _formValue.address,
|
||||
_formValue.username.toCi(), _formValue.password, [""]);
|
||||
Account? account = Account(
|
||||
Account.newId(),
|
||||
_formValue.scheme,
|
||||
_formValue.address,
|
||||
_formValue.username.toCi(),
|
||||
_formValue.password,
|
||||
[""],
|
||||
);
|
||||
_log.info("[_connect] Try connecting with account: $account");
|
||||
Navigator.pushNamed<Account>(context, Connect.routeName,
|
||||
arguments: ConnectArguments(account))
|
||||
.then<Account?>((result) {
|
||||
return result != null
|
||||
? Navigator.pushNamed(context, RootPicker.routeName,
|
||||
arguments: RootPickerArguments(result))
|
||||
: Future.value(null);
|
||||
}).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().getAccounts2Or([])..add(pa)).distinctIf(
|
||||
(a, b) => a.account == b.account,
|
||||
(a) => a.account.hashCode,
|
||||
);
|
||||
Pref()
|
||||
..setAccounts2(accounts)
|
||||
..setCurrentAccountIndex(
|
||||
accounts.indexWhere((element) => element.account == result));
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context, Home.routeName, (route) => false,
|
||||
arguments: HomeArguments(pa.account));
|
||||
}
|
||||
});
|
||||
account = await Navigator.pushNamed<Account>(context, Connect.routeName,
|
||||
arguments: ConnectArguments(account));
|
||||
if (account == null) {
|
||||
// connection failed
|
||||
return;
|
||||
}
|
||||
account = await Navigator.pushNamed<Account>(context, RootPicker.routeName,
|
||||
arguments: RootPickerArguments(account));
|
||||
if (account == null) {
|
||||
// ???
|
||||
return;
|
||||
}
|
||||
// we've got a good account
|
||||
// only signing in with app password would trigger distinct
|
||||
final accounts = (Pref().getAccounts3Or([])..add(account)).distinct();
|
||||
try {
|
||||
AccountPref.setGlobalInstance(
|
||||
account, await pref_util.loadAccountPref(account));
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout("[_connect] Failed reading pref for account: $account", e,
|
||||
stackTrace);
|
||||
}
|
||||
Pref()
|
||||
..setAccounts3(accounts)
|
||||
..setCurrentAccountIndex(accounts.indexOf(account));
|
||||
Navigator.pushNamedAndRemoveUntil(context, Home.routeName, (route) => false,
|
||||
arguments: HomeArguments(account));
|
||||
}
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
|
|
@ -167,13 +167,14 @@ void initLog() {
|
|||
}
|
||||
|
||||
Account buildAccount({
|
||||
String id = "123456-000000",
|
||||
String scheme = "http",
|
||||
String address = "example.com",
|
||||
String username = "admin",
|
||||
String password = "pass",
|
||||
List<String> roots = const [""],
|
||||
}) =>
|
||||
Account(scheme, address, username.toCi(), password, roots);
|
||||
Account(id, scheme, address, username.toCi(), password, roots);
|
||||
|
||||
/// Build a mock [File] pointing to a album JSON file
|
||||
///
|
||||
|
|
87
test/use_case/compat/v34_test.dart
Normal file
87
test/use_case/compat/v34_test.dart
Normal file
|
@ -0,0 +1,87 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:nc_photos/platform/universal_storage.dart';
|
||||
import 'package:nc_photos/use_case/compat/v34.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group("CompatV34", () {
|
||||
group("isPrefNeedMigration", () {
|
||||
test("w/ accounts2", () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
"accounts2": [
|
||||
"""{"account":{"scheme":"http","address":"example.com","username":"admin","password":"123456","roots":["dir","dir2"]},"settings":{"isEnableFaceRecognitionApp":true,"shareFolder":""}}""",
|
||||
],
|
||||
});
|
||||
expect(await CompatV34.isPrefNeedMigration(), true);
|
||||
});
|
||||
|
||||
test("w/ accounts", () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
"accounts": [
|
||||
"""{"scheme":"http","address":"example.com","username":"admin","password":"123456","roots":["dir","dir2"]}""",
|
||||
],
|
||||
});
|
||||
expect(await CompatV34.isPrefNeedMigration(), true);
|
||||
});
|
||||
|
||||
test("w/o accounts(2)", () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
"hello": "world",
|
||||
});
|
||||
expect(await CompatV34.isPrefNeedMigration(), false);
|
||||
});
|
||||
});
|
||||
|
||||
group("migratePref", () {
|
||||
test("from v1", () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
"accounts": [
|
||||
"""{"scheme":"http","address":"example.com","username":"admin","password":"123456","roots":["dir","dir2"]}""",
|
||||
],
|
||||
});
|
||||
final storage = UniversalMemoryStorage();
|
||||
await CompatV34.migratePref(storage);
|
||||
final pref = await SharedPreferences.getInstance();
|
||||
final result = pref.getStringList("accounts3");
|
||||
expect(result?.length, 1);
|
||||
expect(
|
||||
result![0],
|
||||
matches(RegExp(
|
||||
r"""\{"scheme":"http","address":"example.com","username":"admin","password":"123456","roots":\["dir","dir2"\],"id":"[0-9a-f]+-[0-9a-f]+"\}""")),
|
||||
);
|
||||
expect(pref.containsKey("accounts"), false);
|
||||
final id = jsonDecode(result[0])["id"];
|
||||
expect(
|
||||
await storage.getString("accounts/$id/pref"),
|
||||
"""{"isEnableFaceRecognitionApp":true,"shareFolder":""}""",
|
||||
);
|
||||
});
|
||||
|
||||
test("from v32", () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
"accounts2": [
|
||||
"""{"account":{"scheme":"http","address":"example.com","username":"admin","password":"123456","roots":["dir","dir2"]},"settings":{"isEnableFaceRecognitionApp":true,"shareFolder":""}}""",
|
||||
],
|
||||
});
|
||||
final storage = UniversalMemoryStorage();
|
||||
await CompatV34.migratePref(storage);
|
||||
final pref = await SharedPreferences.getInstance();
|
||||
final result = pref.getStringList("accounts3");
|
||||
expect(result?.length, 1);
|
||||
expect(
|
||||
result![0],
|
||||
matches(RegExp(
|
||||
r"""\{"scheme":"http","address":"example.com","username":"admin","password":"123456","roots":\["dir","dir2"\],"id":"[0-9a-f]+-[0-9a-f]+"\}""")),
|
||||
);
|
||||
expect(pref.containsKey("accounts2"), false);
|
||||
final id = jsonDecode(result[0])["id"];
|
||||
expect(
|
||||
await storage.getString("accounts/$id/pref"),
|
||||
"""{"isEnableFaceRecognitionApp":true,"shareFolder":""}""",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue