import 'dart:async'; import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; import 'package:spotube/utils/primitive_utils.dart'; const secureStorage = FlutterSecureStorage( aOptions: AndroidOptions( encryptedSharedPreferences: true, ), ); const kKeyBoxName = "spotube_box_name"; String getBoxKey(String boxName) => "spotube_box_$boxName"; abstract class PersistedStateNotifier extends StateNotifier { final String cacheKey; final bool encrypted; FutureOr onInit() {} PersistedStateNotifier( super.state, this.cacheKey, { this.encrypted = false, }) { _load().then((_) => onInit()); } static late LazyBox _box; static late LazyBox _encryptedBox; static Future initializeBoxes() async { String? boxName = await secureStorage.read(key: kKeyBoxName); if (boxName == null) { boxName = "spotube-${PrimitiveUtils.uuid.v4()}"; await secureStorage.write(key: kKeyBoxName, value: boxName); } String? encryptionKey = await secureStorage.read(key: getBoxKey(boxName)); if (encryptionKey == null) { encryptionKey = base64Url.encode(Hive.generateSecureKey()); await secureStorage.write( key: getBoxKey(boxName), value: encryptionKey, ); } _encryptedBox = await Hive.openLazyBox( boxName, encryptionCipher: HiveAesCipher(base64Url.decode(encryptionKey)), ); _box = await Hive.openLazyBox("spotube_cache"); } LazyBox get box => encrypted ? _encryptedBox : _box; Future _load() async { final json = await box.get(cacheKey); if (json != null) { state = await fromJson(castNestedJson(json)); } } Map castNestedJson(Map map) { return Map.castFrom( map.map((key, value) { if (value is Map) { return MapEntry( key, castNestedJson(value), ); } else if (value is Iterable) { return MapEntry( key, value.map((e) { if (e is Map) return castNestedJson(e); return e; }).toList(), ); } return MapEntry(key, value); }), ); } void save() async { box.put(cacheKey, toJson()); } FutureOr fromJson(Map json); Map toJson(); @override set state(T value) { if (state == value) return; super.state = value; save(); } }