Add encrypted pref variant

This commit is contained in:
Ming Ming 2024-05-25 03:35:58 +08:00
parent 88ee044e62
commit fd8251b86c
13 changed files with 300 additions and 54 deletions

View file

@ -21,6 +21,7 @@ import 'package:nc_photos/entity/local_file/data_source.dart';
import 'package:nc_photos/entity/nc_album/data_source.dart'; import 'package:nc_photos/entity/nc_album/data_source.dart';
import 'package:nc_photos/entity/nc_album/repo.dart'; import 'package:nc_photos/entity/nc_album/repo.dart';
import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/entity/pref/provider/secure_storage.dart';
import 'package:nc_photos/entity/pref/provider/shared_preferences.dart'; import 'package:nc_photos/entity/pref/provider/shared_preferences.dart';
import 'package:nc_photos/entity/pref_util.dart' as pref_util; import 'package:nc_photos/entity/pref_util.dart' as pref_util;
import 'package:nc_photos/entity/recognize_face/data_source.dart'; import 'package:nc_photos/entity/recognize_face/data_source.dart';
@ -137,6 +138,7 @@ void _initSelfSignedCertManager() {
Future<void> _initDiContainer(InitIsolateType isolateType) async { Future<void> _initDiContainer(InitIsolateType isolateType) async {
final c = DiContainer.late(); final c = DiContainer.late();
c.pref = Pref(); c.pref = Pref();
c.securePref = await _createSecurePref();
c.npDb = await _createDb(isolateType); c.npDb = await _createDb(isolateType);
c.albumRepo = AlbumRepo(AlbumCachedDataSource(c)); c.albumRepo = AlbumRepo(AlbumCachedDataSource(c));
@ -204,5 +206,11 @@ Future<NpDb> _createDb(InitIsolateType isolateType) async {
return npDb; return npDb;
} }
Future<Pref> _createSecurePref() async {
final provider = PrefSecureStorageProvider();
await provider.init();
return Pref.scoped(provider);
}
final _log = Logger("app_init"); final _log = Logger("app_init");
var _hasInitedInThisIsolate = false; var _hasInitedInThisIsolate = false;

View file

@ -6,14 +6,16 @@ import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/language_util.dart'; import 'package:nc_photos/language_util.dart';
import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/protected_page_handler.dart';
import 'package:nc_photos/size.dart'; import 'package:nc_photos/size.dart';
import 'package:np_codegen/np_codegen.dart'; import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/object_util.dart';
import 'package:np_gps_map/np_gps_map.dart'; import 'package:np_gps_map/np_gps_map.dart';
import 'package:np_string/np_string.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
part 'pref_controller.g.dart'; part 'pref_controller.g.dart';
@npLog
@npSubjectAccessor @npSubjectAccessor
class PrefController { class PrefController {
PrefController(this._c); PrefController(this._c);
@ -145,20 +147,13 @@ class PrefController {
required BehaviorSubject<T> controller, required BehaviorSubject<T> controller,
required Future<bool> Function(Pref pref, T value) setter, required Future<bool> Function(Pref pref, T value) setter,
required T value, required T value,
}) async { }) =>
final backup = controller.value; _doSet(
controller.add(value); pref: _c.pref,
try { controller: controller,
if (!await setter(_c.pref, value)) { setter: setter,
throw StateError("Unknown error"); value: value,
} );
} catch (e, stackTrace) {
_log.severe("[_set] Failed setting preference", e, stackTrace);
controller
..addError(e, stackTrace)
..add(backup);
}
}
Future<void> _setOrRemove<T>({ Future<void> _setOrRemove<T>({
required BehaviorSubject<T?> controller, required BehaviorSubject<T?> controller,
@ -166,26 +161,15 @@ class PrefController {
required Future<bool> Function(Pref pref) remover, required Future<bool> Function(Pref pref) remover,
required T? value, required T? value,
T? defaultValue, T? defaultValue,
}) async { }) =>
final backup = controller.value; _doSetOrRemove(
controller.add(value ?? defaultValue); pref: _c.pref,
try { controller: controller,
if (value == null) { setter: setter,
if (!await remover(_c.pref)) { remover: remover,
throw StateError("Unknown error"); value: value,
} defaultValue: defaultValue,
} else { );
if (!await setter(_c.pref, value)) {
throw StateError("Unknown error");
}
}
} catch (e, stackTrace) {
_log.severe("[_set] Failed setting preference", e, stackTrace);
controller
..addError(e, stackTrace)
..add(backup);
}
}
static AppLanguage _langIdToAppLanguage(int langId) { static AppLanguage _langIdToAppLanguage(int langId) {
try { try {
@ -254,3 +238,92 @@ class PrefController {
late final _secondarySeedColorController = BehaviorSubject<Color?>.seeded( late final _secondarySeedColorController = BehaviorSubject<Color?>.seeded(
_c.pref.getSecondarySeedColor()?.run(Color.new)); _c.pref.getSecondarySeedColor()?.run(Color.new));
} }
class SecurePrefController {
SecurePrefController(this._c);
// ignore: unused_element
Future<void> _set<T>({
required BehaviorSubject<T> controller,
required Future<bool> Function(Pref pref, T value) setter,
required T value,
}) =>
_doSet(
pref: _c.securePref,
controller: controller,
setter: setter,
value: value,
);
// ignore: unused_element
Future<void> _setOrRemove<T>({
required BehaviorSubject<T?> controller,
required Future<bool> Function(Pref pref, T value) setter,
required Future<bool> Function(Pref pref) remover,
required T? value,
T? defaultValue,
}) =>
_doSetOrRemove(
pref: _c.securePref,
controller: controller,
setter: setter,
remover: remover,
value: value,
defaultValue: defaultValue,
);
final DiContainer _c;
}
Future<void> _doSet<T>({
required Pref pref,
required BehaviorSubject<T> controller,
required Future<bool> Function(Pref pref, T value) setter,
required T value,
}) async {
final backup = controller.value;
controller.add(value);
try {
if (!await setter(pref, value)) {
throw StateError("Unknown error");
}
} catch (e, stackTrace) {
_$__NpLog.log.severe("[_doSet] Failed setting preference", e, stackTrace);
controller
..addError(e, stackTrace)
..add(backup);
}
}
Future<void> _doSetOrRemove<T>({
required Pref pref,
required BehaviorSubject<T?> controller,
required Future<bool> Function(Pref pref, T value) setter,
required Future<bool> Function(Pref pref) remover,
required T? value,
T? defaultValue,
}) async {
final backup = controller.value;
controller.add(value ?? defaultValue);
try {
if (value == null) {
if (!await remover(pref)) {
throw StateError("Unknown error");
}
} else {
if (!await setter(pref, value)) {
throw StateError("Unknown error");
}
}
} catch (e, stackTrace) {
_$__NpLog.log
.severe("[_doSetOrRemove] Failed setting preference", e, stackTrace);
controller
..addError(e, stackTrace)
..add(backup);
}
}
@npLog
// ignore: camel_case_types
class __ {}

View file

@ -6,11 +6,11 @@ part of 'pref_controller.dart';
// NpLogGenerator // NpLogGenerator
// ************************************************************************** // **************************************************************************
extension _$PrefControllerNpLog on PrefController { extension _$__NpLog on __ {
// ignore: unused_element // ignore: unused_element
Logger get _log => log; Logger get _log => log;
static final log = Logger("controller.pref_controller.PrefController"); static final log = Logger("controller.pref_controller.__");
} }
// ************************************************************************** // **************************************************************************

View file

@ -51,6 +51,7 @@ enum DiType {
pref, pref,
touchManager, touchManager,
npDb, npDb,
securePref,
} }
class DiContainer { class DiContainer {
@ -88,6 +89,7 @@ class DiContainer {
Pref? pref, Pref? pref,
TouchManager? touchManager, TouchManager? touchManager,
NpDb? npDb, NpDb? npDb,
Pref? securePref,
}) : _albumRepo = albumRepo, }) : _albumRepo = albumRepo,
_albumRepoRemote = albumRepoRemote, _albumRepoRemote = albumRepoRemote,
_albumRepoLocal = albumRepoLocal, _albumRepoLocal = albumRepoLocal,
@ -120,7 +122,8 @@ class DiContainer {
_recognizeFaceRepoLocal = recognizeFaceRepoLocal, _recognizeFaceRepoLocal = recognizeFaceRepoLocal,
_pref = pref, _pref = pref,
_touchManager = touchManager, _touchManager = touchManager,
_npDb = npDb; _npDb = npDb,
_securePref = securePref;
DiContainer.late(); DiContainer.late();
@ -192,6 +195,8 @@ class DiContainer {
return contianer._touchManager != null; return contianer._touchManager != null;
case DiType.npDb: case DiType.npDb:
return contianer._npDb != null; return contianer._npDb != null;
case DiType.securePref:
return contianer._securePref != null;
} }
} }
@ -213,6 +218,7 @@ class DiContainer {
OrNull<Pref>? pref, OrNull<Pref>? pref,
OrNull<TouchManager>? touchManager, OrNull<TouchManager>? touchManager,
OrNull<NpDb>? npDb, OrNull<NpDb>? npDb,
OrNull<Pref>? securePref,
}) { }) {
return DiContainer( return DiContainer(
albumRepo: albumRepo == null ? _albumRepo : albumRepo.obj, albumRepo: albumRepo == null ? _albumRepo : albumRepo.obj,
@ -237,6 +243,7 @@ class DiContainer {
pref: pref == null ? _pref : pref.obj, pref: pref == null ? _pref : pref.obj,
touchManager: touchManager == null ? _touchManager : touchManager.obj, touchManager: touchManager == null ? _touchManager : touchManager.obj,
npDb: npDb == null ? _npDb : npDb.obj, npDb: npDb == null ? _npDb : npDb.obj,
securePref: securePref == null ? _securePref : securePref.obj,
); );
} }
@ -277,6 +284,7 @@ class DiContainer {
Pref get pref => _pref!; Pref get pref => _pref!;
TouchManager get touchManager => _touchManager!; TouchManager get touchManager => _touchManager!;
NpDb get npDb => _npDb!; NpDb get npDb => _npDb!;
Pref get securePref => _securePref!;
set albumRepo(AlbumRepo v) { set albumRepo(AlbumRepo v) {
assert(_albumRepo == null); assert(_albumRepo == null);
@ -443,6 +451,11 @@ class DiContainer {
_npDb = v; _npDb = v;
} }
set securePref(Pref v) {
assert(_securePref == null);
_securePref = v;
}
AlbumRepo? _albumRepo; AlbumRepo? _albumRepo;
AlbumRepo? _albumRepoRemote; AlbumRepo? _albumRepoRemote;
// Explicitly request a AlbumRepo backed by local source // Explicitly request a AlbumRepo backed by local source
@ -480,6 +493,7 @@ class DiContainer {
Pref? _pref; Pref? _pref;
TouchManager? _touchManager; TouchManager? _touchManager;
NpDb? _npDb; NpDb? _npDb;
Pref? _securePref;
} }
extension DiContainerExtension on DiContainer { extension DiContainerExtension on DiContainer {

View file

@ -6,7 +6,6 @@ import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart'; import 'package:nc_photos/account.dart';
import 'package:nc_photos/entity/pref/provider/memory.dart'; import 'package:nc_photos/entity/pref/provider/memory.dart';
import 'package:np_codegen/np_codegen.dart'; import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/type.dart';
part 'pref.g.dart'; part 'pref.g.dart';
part 'pref/extension.dart'; part 'pref/extension.dart';
@ -57,8 +56,6 @@ class AccountPref {
} }
} }
Future<JsonObj> toJson() => provider.toJson();
Future<bool> _set<T>(AccountPrefKey key, T value, Future<bool> _set<T>(AccountPrefKey key, T value,
Future<bool> Function(AccountPrefKey key, T value) setFn) => Future<bool> Function(AccountPrefKey key, T value) setFn) =>
setFn(key, value); setFn(key, value);
@ -241,6 +238,4 @@ abstract class PrefProvider {
Future<bool> remove(PrefKeyInterface key); Future<bool> remove(PrefKeyInterface key);
Future<bool> clear(); Future<bool> clear();
Future<JsonObj> toJson();
} }

View file

@ -42,8 +42,6 @@ class PrefMemoryProvider extends PrefProvider {
_data.clear(); _data.clear();
return true; return true;
} }
@override
Future<JsonObj> toJson() async => Map.of(_data);
T? _get<T>(PrefKeyInterface key) => _data[key.toStringKey()]; T? _get<T>(PrefKeyInterface key) => _data[key.toStringKey()];

View file

@ -0,0 +1,100 @@
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/entity/pref.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/object_util.dart';
part 'secure_storage.g.dart';
@npLog
class PrefSecureStorageProvider implements PrefProvider {
Future<void> init() async {
_storage = const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
preferencesKeyPrefix: "com.nkming.nc_photos",
sharedPreferencesName: "secure_pref",
),
iOptions: IOSOptions(
accountName: "com.nkming.nc_photos",
),
);
_rawData = await _storage.readAll();
}
@override
bool? getBool(PrefKeyInterface key) {
final value = _rawData[key.toStringKey()];
return value?.let((e) => bool.tryParse(e, caseSensitive: false));
}
@override
Future<bool> setBool(PrefKeyInterface key, bool value) =>
setString(key, value.toString());
@override
int? getInt(PrefKeyInterface key) {
final value = _rawData[key.toStringKey()];
return value?.let(int.tryParse);
}
@override
Future<bool> setInt(PrefKeyInterface key, int value) =>
setString(key, value.toString());
@override
String? getString(PrefKeyInterface key) {
return _rawData[key.toStringKey()];
}
@override
Future<bool> setString(PrefKeyInterface key, String value) async {
try {
await _storage.write(key: key.toStringKey(), value: value);
_rawData[key.toStringKey()] = value;
return true;
} catch (e, stackTrace) {
_log.severe("[setString] Failed while write", e, stackTrace);
return false;
}
}
@override
List<String>? getStringList(PrefKeyInterface key) {
final value = _rawData[key.toStringKey()];
return (value?.let(jsonDecode) as List).cast<String>();
}
@override
Future<bool> setStringList(PrefKeyInterface key, List<String> value) =>
setString(key, jsonEncode(value));
@override
Future<bool> remove(PrefKeyInterface key) async {
try {
await _storage.delete(key: key.toStringKey());
_rawData.remove(key.toStringKey());
return true;
} catch (e, stackTrace) {
_log.severe("[remove] Failed while write", e, stackTrace);
return false;
}
}
@override
Future<bool> clear() async {
try {
await _storage.deleteAll();
_rawData.clear();
return true;
} catch (e, stackTrace) {
_log.severe("[clear] Failed while write", e, stackTrace);
return false;
}
}
late FlutterSecureStorage _storage;
late Map<String, String> _rawData;
}

View file

@ -0,0 +1,15 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'secure_storage.dart';
// **************************************************************************
// NpLogGenerator
// **************************************************************************
extension _$PrefSecureStorageProviderNpLog on PrefSecureStorageProvider {
// ignore: unused_element
Logger get _log => log;
static final log =
Logger("entity.pref.provider.secure_storage.PrefSecureStorageProvider");
}

View file

@ -1,9 +1,7 @@
import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/use_case/compat/v34.dart'; import 'package:nc_photos/use_case/compat/v34.dart';
import 'package:np_common/type.dart';
import 'package:np_universal_storage/np_universal_storage.dart'; import 'package:np_universal_storage/np_universal_storage.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
/// [Pref] stored with [SharedPreferences] lib /// [Pref] stored with [SharedPreferences] lib
class PrefSharedPreferencesProvider extends PrefProvider { class PrefSharedPreferencesProvider extends PrefProvider {
@ -55,8 +53,5 @@ class PrefSharedPreferencesProvider extends PrefProvider {
@override @override
Future<bool> clear() => _pref.clear(); Future<bool> clear() => _pref.clear();
@override
Future<JsonObj> toJson() => SharedPreferencesStorePlatform.instance.getAll();
late SharedPreferences _pref; late SharedPreferences _pref;
} }

View file

@ -1,7 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/entity/pref.dart';
import 'package:np_common/type.dart';
import 'package:np_universal_storage/np_universal_storage.dart'; import 'package:np_universal_storage/np_universal_storage.dart';
/// [Pref] backed by [UniversalStorage] /// [Pref] backed by [UniversalStorage]
@ -52,9 +51,6 @@ class PrefUniversalStorageProvider extends PrefProvider {
return true; return true;
} }
@override
Future<JsonObj> toJson() async => Map.of(_data);
T? _get<T>(PrefKeyInterface key) => _data[key.toStringKey()]; T? _get<T>(PrefKeyInterface key) => _data[key.toStringKey()];
Future<bool> _set<T>(PrefKeyInterface key, T value) async { Future<bool> _set<T>(PrefKeyInterface key, T value) async {

View file

@ -72,6 +72,9 @@ class MyApp extends StatelessWidget {
RepositoryProvider( RepositoryProvider(
create: (_) => PrefController(_c), create: (_) => PrefController(_c),
), ),
RepositoryProvider(
create: (_) => SecurePrefController(_c),
),
RepositoryProvider( RepositoryProvider(
create: (context) => AccountController( create: (context) => AccountController(
prefController: context.read(), prefController: context.read(),

View file

@ -553,6 +553,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.15" version: "2.0.15"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0"
url: "https://pub.dev"
source: hosted
version: "9.2.2"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
url: "https://pub.dev"
source: hosted
version: "1.2.1"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_staggered_grid_view: flutter_staggered_grid_view:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -69,6 +69,7 @@ dependencies:
flutter_cache_manager: any flutter_cache_manager: any
flutter_colorpicker: ^1.0.3 flutter_colorpicker: ^1.0.3
flutter_isolate: ^2.0.4 flutter_isolate: ^2.0.4
flutter_secure_storage: ^9.2.2
flutter_staggered_grid_view: flutter_staggered_grid_view:
git: git:
url: https://gitlab.com/nc-photos/flutter_staggered_grid_view url: https://gitlab.com/nc-photos/flutter_staggered_grid_view