From f618374c54f7b046f3d72967fbcfe39edd864450 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:23:34 +0800 Subject: [PATCH 01/44] migrate signal.db to identity number folder --- lib/account/account_server.dart | 15 +++- lib/crypto/signal/identity_key_util.dart | 5 +- lib/crypto/signal/pre_key_util.dart | 10 +-- lib/crypto/signal/signal_database.dart | 94 +++++++++++++++++++++--- lib/crypto/signal/signal_key_util.dart | 18 +++-- lib/crypto/signal/signal_protocol.dart | 20 +++-- lib/db/util/open_database.dart | 4 + lib/ui/landing/bloc/landing_cubit.dart | 5 +- lib/ui/landing/landing.dart | 8 +- lib/ui/landing/landing_mobile.dart | 8 +- lib/workers/decrypt_message.dart | 17 +++-- lib/workers/message_worker_isolate.dart | 10 ++- 12 files changed, 166 insertions(+), 48 deletions(-) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 5bcb29c812..95ad5aa8d6 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -73,6 +73,7 @@ class AccountServer { AccountKeyValue.instance.primarySessionId == null; String? userAgent; String? deviceId; + SignalDatabase? signalDatabase; Future initServer( String userId, @@ -92,9 +93,14 @@ class AccountServer { await _initClient(); + signalDatabase = await SignalDatabase.connect( + identityNumber: identityNumber, + openForLogin: false, + fromMainIsolate: true, + ); checkSignalKeyTimer = Timer.periodic(const Duration(days: 1), (timer) { i('refreshSignalKeys periodic'); - checkSignalKey(client); + checkSignalKey(client, signalDatabase!); }); try { @@ -313,7 +319,8 @@ class AccountServer { await clearKeyValues(); try { - await SignalDatabase.get.clear(); + await signalDatabase?.clear(); + await signalDatabase?.close(); } catch (_) { // ignore closed database error } @@ -744,9 +751,9 @@ class AccountServer { Future checkSignalKeys() async { final hasPushSignalKeys = PrivacyKeyValue.instance.hasPushSignalKeys; if (hasPushSignalKeys) { - unawaited(checkSignalKey(client)); + unawaited(checkSignalKey(client, signalDatabase!)); } else { - await refreshSignalKeys(client); + await refreshSignalKeys(client, signalDatabase!); PrivacyKeyValue.instance.hasPushSignalKeys = true; } } diff --git a/lib/crypto/signal/identity_key_util.dart b/lib/crypto/signal/identity_key_util.dart index cc48e7721e..ce9cf960e8 100644 --- a/lib/crypto/signal/identity_key_util.dart +++ b/lib/crypto/signal/identity_key_util.dart @@ -4,11 +4,11 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'identity_extension.dart'; import 'signal_database.dart'; -Future generateSignalDatabaseIdentityKeyPair( +Future generateSignalDatabaseIdentityKeyPair( SignalDatabase db, List? privateKey, + int registrationId, ) async { - final registrationId = generateRegistrationId(false); final identityKeyPair = privateKey == null ? generateIdentityKeyPair() : generateIdentityKeyPairFromPrivate(privateKey); @@ -19,7 +19,6 @@ Future generateSignalDatabaseIdentityKeyPair( privateKey: Value(identityKeyPair.getPrivateKey().serialize()), timestamp: DateTime.now().millisecondsSinceEpoch); await db.identityDao.insert(identity); - return registrationId; } Future getIdentityKeyPair(SignalDatabase db) async => diff --git a/lib/crypto/signal/pre_key_util.dart b/lib/crypto/signal/pre_key_util.dart index 60cd4e9d34..3d5ab67617 100644 --- a/lib/crypto/signal/pre_key_util.dart +++ b/lib/crypto/signal/pre_key_util.dart @@ -9,8 +9,8 @@ import 'storage/mixin_prekey_store.dart'; const batchSize = 700; -Future> generatePreKeys() async { - final preKeyStore = MixinPreKeyStore(SignalDatabase.get); +Future> generatePreKeys(SignalDatabase database) async { + final preKeyStore = MixinPreKeyStore(database); final preKeyIdOffset = CryptoKeyValue.instance.nextPreKeyId; final records = helper.generatePreKeys(preKeyIdOffset, batchSize); final preKeys = []; @@ -23,9 +23,9 @@ Future> generatePreKeys() async { return records; } -Future generateSignedPreKey( - IdentityKeyPair identityKeyPair, bool active) async { - final signedPreKeyStore = MixinPreKeyStore(SignalDatabase.get); +Future generateSignedPreKey(IdentityKeyPair identityKeyPair, + bool active, SignalDatabase database) async { + final signedPreKeyStore = MixinPreKeyStore(database); final signedPreKeyId = CryptoKeyValue.instance.nextSignedPreKeyId; final record = helper.generateSignedPreKey(identityKeyPair, signedPreKeyId); await signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); diff --git a/lib/crypto/signal/signal_database.dart b/lib/crypto/signal/signal_database.dart index 360f0ca8e5..f1a8758641 100644 --- a/lib/crypto/signal/signal_database.dart +++ b/lib/crypto/signal/signal_database.dart @@ -1,10 +1,12 @@ +import 'dart:async'; import 'dart:io'; import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; import 'package:path/path.dart' as p; +import '../../db/util/open_database.dart'; import '../../utils/file.dart'; +import '../../utils/logger.dart'; import 'dao/identity_dao.dart'; import 'dao/pre_key_dao.dart'; import 'dao/ratchet_sender_key_dao.dart'; @@ -31,11 +33,88 @@ part 'signal_database.g.dart'; RatchetSenderKeyDao, ]) class SignalDatabase extends _$SignalDatabase { - SignalDatabase._() : super(_openConnection()); + SignalDatabase._(super.e); - static SignalDatabase? _instance; + static Future _removeLegacySignalDatabase() async { + final dbFolder = mixinDocumentsDirectory.path; + const dbFiles = ['signal.db', 'signal.db-shm', 'signal.db-wal']; + final files = dbFiles.map((e) => File(p.join(dbFolder, e))); + for (final file in files) { + try { + if (file.existsSync()) { + await file.delete(); + } + } catch (error, stacktrace) { + e('_removeLegacySignalDatabase ${file.path} error: $error, stacktrace: $stacktrace'); + } + } + } + + static Future _migrationLegacySignalDatabaseIfNecessary( + String identityNumber) async { + final dbFolder = p.join(mixinDocumentsDirectory.path, identityNumber); + + final dbFile = File(p.join(dbFolder, 'signal.db')); + // migration only when new database file not exists. + if (dbFile.existsSync()) { + return _removeLegacySignalDatabase(); + } + + final legacyDbFolder = mixinDocumentsDirectory.path; + final legacyDbFile = File(p.join(legacyDbFolder, 'signal.db')); + if (!legacyDbFile.existsSync()) { + return; + } + const dbFiles = ['signal.db', 'signal.db-shm', 'signal.db-wal']; + final legacyFiles = dbFiles.map((e) => File(p.join(legacyDbFolder, e))); + bool hasError = false; + for (final file in legacyFiles) { + try { + final newLocation = p.join(dbFolder, p.basename(file.path)); + // delete new location file if exists + final newFile = File(newLocation); + if (newFile.existsSync()) { + await newFile.delete(); + } + if (file.existsSync()) { + await file.copy(newLocation); + } + } catch (error, stacktrace) { + e('_migrationLegacySignalDatabaseIfNecessary ${file.path} error: $error, stacktrace: $stacktrace'); + hasError = true; + } + } + if (hasError) { + // migration error. remove copied database file. + for (final name in dbFiles) { + final file = File(p.join(dbFolder, name)); + if (file.existsSync()) { + await file.delete(); + } + } + } + return _removeLegacySignalDatabase(); + } - static SignalDatabase get get => _instance ??= SignalDatabase._(); + static Future connect({ + required String identityNumber, + required bool openForLogin, + required bool fromMainIsolate, + }) async { + if (openForLogin) { + // delete old database file + await _removeLegacySignalDatabase(); + } else { + await _migrationLegacySignalDatabaseIfNecessary(identityNumber); + } + final executor = await openQueryExecutor( + identityNumber: identityNumber, + dbName: 'signal', + fromMainIsolate: fromMainIsolate, + readCount: 0, + ); + return SignalDatabase._(executor); + } @override int get schemaVersion => 1; @@ -50,15 +129,10 @@ class SignalDatabase extends _$SignalDatabase { }); Future clear() => transaction(() async { + i('clear signal database'); await customStatement('PRAGMA wal_checkpoint(FULL)'); for (final table in allTables) { await delete(table).go(); } }); } - -LazyDatabase _openConnection() => LazyDatabase(() { - final dbFolder = mixinDocumentsDirectory; - final file = File(p.join(dbFolder.path, 'signal.db')); - return NativeDatabase(file); - }); diff --git a/lib/crypto/signal/signal_key_util.dart b/lib/crypto/signal/signal_key_util.dart index cee0c840c4..e0e6a12981 100644 --- a/lib/crypto/signal/signal_key_util.dart +++ b/lib/crypto/signal/signal_key_util.dart @@ -9,27 +9,29 @@ import 'signal_key_request.dart'; const int preKeyMinNum = 500; -Future checkSignalKey(Client client) async { +Future checkSignalKey(Client client, SignalDatabase signalDatabase) async { final response = await client.accountApi.getSignalKeyCount(); final availableKeyCount = response.data.preKeyCount; if (availableKeyCount > preKeyMinNum) { return; } - await refreshSignalKeys(client); + await refreshSignalKeys(client, signalDatabase); } -Future> refreshSignalKeys(Client client) async { - final keys = await generateKeys(); +Future> refreshSignalKeys( + Client client, SignalDatabase signalDatabase) async { + final keys = await generateKeys(signalDatabase); return client.accountApi.pushSignalKeys(keys.toJson()); } -Future generateKeys() async { - final identityKeyPair = await getIdentityKeyPair(SignalDatabase.get); +Future generateKeys(SignalDatabase signalDatabase) async { + final identityKeyPair = await getIdentityKeyPair(signalDatabase); if (identityKeyPair == null) { throw InvalidKeyException('Local identity key pair is null!'); } - final oneTimePreKeys = await generatePreKeys(); - final signedPreKeyRecord = await generateSignedPreKey(identityKeyPair, false); + final oneTimePreKeys = await generatePreKeys(signalDatabase); + final signedPreKeyRecord = + await generateSignedPreKey(identityKeyPair, false, signalDatabase); return SignalKeyRequest.from( identityKeyPair.getPublicKey(), signedPreKeyRecord, preKeyRecords: oneTimePreKeys); diff --git a/lib/crypto/signal/signal_protocol.dart b/lib/crypto/signal/signal_protocol.dart index 2549d75a14..3297af621a 100644 --- a/lib/crypto/signal/signal_protocol.dart +++ b/lib/crypto/signal/signal_protocol.dart @@ -24,22 +24,32 @@ import 'storage/mixin_session_store.dart'; import 'storage/mixin_signal_protocol_store.dart'; class SignalProtocol { - SignalProtocol(this._accountId); + SignalProtocol(this._accountId, this.db); static const int defaultDeviceId = 1; final String _accountId; - late SignalDatabase db; + final SignalDatabase db; late MixinSignalProtocolStore mixinSignalProtocolStore; late MixinSenderKeyStore senderKeyStore; - static Future initSignal(List? private) => - generateSignalDatabaseIdentityKeyPair(SignalDatabase.get, private); + static Future initSignal( + String identityNumber, int registrationId, List? private) async { + final db = await SignalDatabase.connect( + identityNumber: identityNumber, + openForLogin: true, + fromMainIsolate: true, + ); + try { + await generateSignalDatabaseIdentityKeyPair(db, private, registrationId); + } finally { + await db.close(); + } + } void init() { - db = SignalDatabase.get; final preKeyStore = MixinPreKeyStore(db); final signedPreKeyStore = MixinPreKeyStore(db); final identityKeyStore = MixinIdentityKeyStore(db, _accountId); diff --git a/lib/db/util/open_database.dart b/lib/db/util/open_database.dart index 5b9d6e313e..a1fca5b732 100644 --- a/lib/db/util/open_database.dart +++ b/lib/db/util/open_database.dart @@ -66,6 +66,10 @@ Future openQueryExecutor({ return isolate.connect(); })); + if (reads.isEmpty) { + return write; + } + return MultiExecutor.withReadPool( reads: reads.map((e) => e.executor).toList(), write: write.executor, diff --git a/lib/ui/landing/bloc/landing_cubit.dart b/lib/ui/landing/bloc/landing_cubit.dart index bf567abd0b..55c2796af2 100644 --- a/lib/ui/landing/bloc/landing_cubit.dart +++ b/lib/ui/landing/bloc/landing_cubit.dart @@ -141,7 +141,7 @@ class LandingQrCodeCubit extends LandingCubit { final edKeyPair = ed.generateKey(); final private = base64.decode(msg['identity_key_private'] as String); - final registrationId = await SignalProtocol.initSignal(private); + final registrationId = signal.generateRegistrationId(false); final sessionId = msg['session_id'] as String; final info = await getPackageInfo(); @@ -161,6 +161,9 @@ class LandingQrCodeCubit extends LandingCubit { ), ); + await SignalProtocol.initSignal( + rsp.data.identityNumber, registrationId, private); + final privateKey = base64Encode(edKeyPair.privateKey.bytes); await AccountKeyValue.instance.init(rsp.data.identityNumber); diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index 64d4647e4d..762cd6cc0d 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -151,7 +151,13 @@ class _LoginFailed extends HookConsumerWidget { .accountApi .logout(LogoutRequest(authState.account.sessionId)); await clearKeyValues(); - await SignalDatabase.get.clear(); + final signalDb = await SignalDatabase.connect( + identityNumber: authState.account.identityNumber, + openForLogin: true, + fromMainIsolate: true, + ); + await signalDb.clear(); + await signalDb.close(); context.multiAuthChangeNotifier.signOut(); }, child: Text(context.l10n.retry), diff --git a/lib/ui/landing/landing_mobile.dart b/lib/ui/landing/landing_mobile.dart index 4b76822c4d..f6d19b12bc 100644 --- a/lib/ui/landing/landing_mobile.dart +++ b/lib/ui/landing/landing_mobile.dart @@ -10,6 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:pin_code_fields/pin_code_fields.dart'; @@ -150,7 +151,7 @@ class _CodeInputScene extends HookConsumerWidget { assert(code.length == 4, 'Invalid code length: $code'); showToastLoading(); try { - final registrationId = await SignalProtocol.initSignal(null); + final registrationId = generateRegistrationId(false); final sessionKey = ed.generateKey(); final sessionSecret = base64Encode(sessionKey.publicKey.bytes); @@ -174,9 +175,12 @@ class _CodeInputScene extends HookConsumerWidget { verification.value.id, accountRequest, ); - final privateKey = base64Encode(sessionKey.privateKey.bytes); final identityNumber = response.data.identityNumber; + await SignalProtocol.initSignal(identityNumber, registrationId, null); + + final privateKey = base64Encode(sessionKey.privateKey.bytes); + await CryptoKeyValue.instance.init(identityNumber); CryptoKeyValue.instance.localRegistrationId = registrationId; diff --git a/lib/workers/decrypt_message.dart b/lib/workers/decrypt_message.dart index 412ceacc24..026ad3acd0 100644 --- a/lib/workers/decrypt_message.dart +++ b/lib/workers/decrypt_message.dart @@ -6,7 +6,6 @@ import 'package:drift/drift.dart'; import 'package:ed25519_edwards/ed25519_edwards.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; - import 'package:uuid/uuid.dart'; import '../blaze/blaze_message.dart'; @@ -67,6 +66,7 @@ class DecryptMessage extends Injector { this._updateStickerJob, this._updateAssetJob, this._deviceTransfer, + this._signalDatabase, ) : super(userId, database, client) { _encryptedProtocol = EncryptedProtocol(); } @@ -87,6 +87,7 @@ class DecryptMessage extends Injector { final UpdateStickerJob _updateStickerJob; final UpdateAssetJob _updateAssetJob; final DeviceTransferIsolateController? _deviceTransfer; + final SignalDatabase _signalDatabase; final refreshKeyMap = {}; @@ -226,7 +227,7 @@ class DecryptMessage extends Injector { } final address = SignalProtocolAddress(data.senderId, deviceId); - final status = (await SignalDatabase.get.ratchetSenderKeyDao + final status = (await _signalDatabase.ratchetSenderKeyDao .getRatchetSenderKey(data.conversationId, address.toString())) ?.status; if (status == RatchetStatus.requesting.name) { @@ -238,13 +239,13 @@ class DecryptMessage extends Injector { i('decrypt failed ${data.messageId}, $e'); await _refreshSignalKeys(data.conversationId); if (data.category == MessageCategory.signalKey) { - await SignalDatabase.get.ratchetSenderKeyDao.deleteByGroupIdAndSenderId( + await _signalDatabase.ratchetSenderKeyDao.deleteByGroupIdAndSenderId( data.conversationId, SignalProtocolAddress(data.senderId, deviceId).toString()); } else { await _insertFailedMessage(data); final address = SignalProtocolAddress(data.senderId, deviceId); - final status = (await SignalDatabase.get.ratchetSenderKeyDao + final status = (await _signalDatabase.ratchetSenderKeyDao .getRatchetSenderKey(data.conversationId, address.toString())) ?.status; if (status == null) { @@ -1204,7 +1205,7 @@ class DecryptMessage extends Injector { senderId: address.toString(), status: RatchetStatus.requesting.name, createdAt: DateTime.now().millisecondsSinceEpoch.toString()); - await SignalDatabase.get.ratchetSenderKeyDao.insertSenderKey(ratchet); + await _signalDatabase.ratchetSenderKeyDao.insertSenderKey(ratchet); } } @@ -1222,7 +1223,7 @@ class DecryptMessage extends Injector { conversationId, userId, encoded, sessionId: sessionId)); unawaited(_sender.deliver(bm)); - await SignalDatabase.get.ratchetSenderKeyDao.deleteByGroupIdAndSenderId( + await _signalDatabase.ratchetSenderKeyDao.deleteByGroupIdAndSenderId( conversationId, SignalProtocolAddress(userId, sessionId.getDeviceId()).toString()); } @@ -1243,8 +1244,8 @@ class DecryptMessage extends Injector { if (count.preKeyCount >= preKeyMinNum) { return; } - final bm = - createSyncSignalKeys(createSyncSignalKeysParam(await generateKeys())); + final bm = createSyncSignalKeys( + createSyncSignalKeysParam(await generateKeys(_signalDatabase))); final result = await _sender.signalKeysChannel(bm); if (result == null) { i('Registering new pre keys...'); diff --git a/lib/workers/message_worker_isolate.dart b/lib/workers/message_worker_isolate.dart index 6cc32bb69e..72c8f61086 100644 --- a/lib/workers/message_worker_isolate.dart +++ b/lib/workers/message_worker_isolate.dart @@ -14,6 +14,7 @@ import 'package:rxdart/rxdart.dart'; import 'package:stream_channel/isolate_channel.dart'; import '../blaze/blaze.dart'; +import '../crypto/signal/signal_database.dart'; import '../crypto/signal/signal_protocol.dart'; import '../db/database.dart'; import '../db/database_event_bus.dart'; @@ -145,6 +146,12 @@ class _MessageProcessRunner { await FtsDatabase.connect(identityNumber), ); + final signalDb = await SignalDatabase.connect( + identityNumber: identityNumber, + openForLogin: false, + fromMainIsolate: false, + ); + client = createClient( userId: userId, sessionId: sessionId, @@ -190,7 +197,7 @@ class _MessageProcessRunner { WorkerIsolateEventType.onBlazeConnectStateChanged, event); }); - signalProtocol = SignalProtocol(userId)..init(); + signalProtocol = SignalProtocol(userId, signalDb)..init(); _sender = Sender( signalProtocol, @@ -267,6 +274,7 @@ class _MessageProcessRunner { _updateStickerJob, _updateAssetJob, _deviceTransfer, + signalDb, ); _floodJob.start(); } From ede6c409893aa4399152b68b259292950ee2a604 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:51:14 +0800 Subject: [PATCH 02/44] update --- lib/crypto/signal/signal_database.dart | 2 +- lib/ui/provider/multi_auth_provider.dart | 68 +++++++++------------- lib/ui/provider/multi_auth_provider.g.dart | 30 ++++++++++ pubspec.lock | 28 ++++----- 4 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 lib/ui/provider/multi_auth_provider.g.dart diff --git a/lib/crypto/signal/signal_database.dart b/lib/crypto/signal/signal_database.dart index f1a8758641..b43a2177bf 100644 --- a/lib/crypto/signal/signal_database.dart +++ b/lib/crypto/signal/signal_database.dart @@ -67,7 +67,7 @@ class SignalDatabase extends _$SignalDatabase { } const dbFiles = ['signal.db', 'signal.db-shm', 'signal.db-wal']; final legacyFiles = dbFiles.map((e) => File(p.join(legacyDbFolder, e))); - bool hasError = false; + var hasError = false; for (final file in legacyFiles) { try { final newLocation = p.join(dbFolder, p.basename(file.path)); diff --git a/lib/ui/provider/multi_auth_provider.dart b/lib/ui/provider/multi_auth_provider.dart index 7b626d298e..0280f166c6 100644 --- a/lib/ui/provider/multi_auth_provider.dart +++ b/lib/ui/provider/multi_auth_provider.dart @@ -1,29 +1,29 @@ -// ignore_for_file: deprecated_member_use_from_same_package - -import 'dart:convert'; - import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:mixin_logger/mixin_logger.dart'; import '../../utils/hydrated_bloc.dart'; import '../../utils/rivepod.dart'; +part 'multi_auth_provider.g.dart'; + +@JsonSerializable() class AuthState extends Equatable { const AuthState({ required this.account, required this.privateKey, }); - factory AuthState.fromMap(Map map) => AuthState( - account: Account.fromJson(map['account'] as Map), - privateKey: map['privateKey'] as String, - ); + factory AuthState.fromJson(Map map) => + _$AuthStateFromJson(map); + @JsonKey(name: 'account') final Account account; + @JsonKey(name: 'privateKey') final String privateKey; String get userId => account.userId; @@ -31,42 +31,27 @@ class AuthState extends Equatable { @override List get props => [account, privateKey]; - Map toMap() => { - 'account': account.toJson(), - 'privateKey': privateKey, - }; + Map toJson() => _$AuthStateToJson(this); } +@JsonSerializable() class MultiAuthState extends Equatable { const MultiAuthState({ - Set auths = const {}, - }) : _auths = auths; - - factory MultiAuthState.fromMap(Map map) { - final list = map['auths'] as Iterable?; - return MultiAuthState( - auths: list - ?.map((e) => AuthState.fromMap(e as Map)) - .toSet() ?? - {}, - ); - } + this.auths = const {}, + }); - factory MultiAuthState.fromJson(String source) => - MultiAuthState.fromMap(json.decode(source) as Map); + factory MultiAuthState.fromJson(Map map) => + _$MultiAuthStateFromJson(map); - final Set _auths; + @JsonKey(name: 'auths') + final Set auths; - AuthState? get current => _auths.isNotEmpty ? _auths.last : null; + AuthState? get current => auths.isNotEmpty ? auths.last : null; @override - List get props => [_auths]; - - Map toMap() => { - 'auths': _auths.map((x) => x.toMap()).toList(), - }; + List get props => [auths]; - String toJson() => json.encode(toMap()); + Map toJson() => _$MultiAuthStateToJson(this); } class MultiAuthStateNotifier extends DistinctStateNotifier { @@ -77,7 +62,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { void signIn(AuthState authState) { state = MultiAuthState( auths: { - ...state._auths.where( + ...state.auths.where( (element) => element.account.userId != authState.account.userId), authState, }, @@ -85,7 +70,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { } void updateAccount(Account account) { - var authState = state._auths + var authState = state.auths .cast() .firstWhere((element) => element?.account.userId == account.userId); if (authState == null) { @@ -95,7 +80,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { authState = AuthState(account: account, privateKey: authState.privateKey); state = MultiAuthState( auths: { - ...state._auths.where( + ...state.auths.where( (element) => element.account.userId != authState?.account.userId), authState, }, @@ -103,21 +88,20 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { } void signOut() { - if (state._auths.isEmpty) return; + if (state.auths.isEmpty) return; state = - MultiAuthState(auths: state._auths.toSet()..remove(state._auths.last)); + MultiAuthState(auths: state.auths.toSet()..remove(state.auths.last)); } @override @protected set state(MultiAuthState value) { - final hydratedJson = toHydratedJson(state.toMap()); + final hydratedJson = toHydratedJson(state.toJson()); HydratedBloc.storage.write(_kMultiAuthCubitKey, hydratedJson); super.state = value; } } -@Deprecated('Use multiAuthNotifierProvider instead') const _kMultiAuthCubitKey = 'MultiAuthCubit'; final multiAuthStateNotifierProvider = @@ -127,7 +111,7 @@ final multiAuthStateNotifierProvider = final oldJson = HydratedBloc.storage.read(_kMultiAuthCubitKey); if (oldJson != null) { - final multiAuthState = fromHydratedJson(oldJson, MultiAuthState.fromMap); + final multiAuthState = fromHydratedJson(oldJson, MultiAuthState.fromJson); if (multiAuthState == null) { return MultiAuthStateNotifier(const MultiAuthState()); } diff --git a/lib/ui/provider/multi_auth_provider.g.dart b/lib/ui/provider/multi_auth_provider.g.dart new file mode 100644 index 0000000000..7aa72863a1 --- /dev/null +++ b/lib/ui/provider/multi_auth_provider.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'multi_auth_provider.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AuthState _$AuthStateFromJson(Map json) => AuthState( + account: Account.fromJson(json['account'] as Map), + privateKey: json['privateKey'] as String, + ); + +Map _$AuthStateToJson(AuthState instance) => { + 'account': instance.account.toJson(), + 'privateKey': instance.privateKey, + }; + +MultiAuthState _$MultiAuthStateFromJson(Map json) => + MultiAuthState( + auths: (json['auths'] as List?) + ?.map((e) => AuthState.fromJson(e as Map)) + .toSet() ?? + const {}, + ); + +Map _$MultiAuthStateToJson(MultiAuthState instance) => + { + 'auths': instance.auths.map((e) => e.toJson()).toList(), + }; diff --git a/pubspec.lock b/pubspec.lock index 56523c1e09..dd84cae0a2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -238,10 +238,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" common_crypto: dependency: "direct main" description: @@ -1143,18 +1143,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.11.0" mime: dependency: "direct main" description: @@ -1701,10 +1701,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -1717,10 +1717,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: "direct main" description: @@ -1805,10 +1805,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" timezone: dependency: transitive description: @@ -1982,10 +1982,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: "direct main" description: From b5d5393ed682379c9655aff70f1adc3550fbd107 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Wed, 25 Oct 2023 14:13:25 +0800 Subject: [PATCH 03/44] add multi auth --- lib/ui/provider/multi_auth_provider.dart | 52 ++++++++++++++-------- lib/ui/provider/multi_auth_provider.g.dart | 6 ++- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/lib/ui/provider/multi_auth_provider.dart b/lib/ui/provider/multi_auth_provider.dart index 0280f166c6..d2652ecea6 100644 --- a/lib/ui/provider/multi_auth_provider.dart +++ b/lib/ui/provider/multi_auth_provider.dart @@ -6,6 +6,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:mixin_logger/mixin_logger.dart'; +import '../../utils/extension/extension.dart'; import '../../utils/hydrated_bloc.dart'; import '../../utils/rivepod.dart'; @@ -36,20 +37,35 @@ class AuthState extends Equatable { @JsonSerializable() class MultiAuthState extends Equatable { - const MultiAuthState({ - this.auths = const {}, - }); + MultiAuthState({ + this.auths = const [], + String? activeUserId, + }) : activeUserId = activeUserId ?? auths.lastOrNull?.userId; factory MultiAuthState.fromJson(Map map) => _$MultiAuthStateFromJson(map); @JsonKey(name: 'auths') - final Set auths; + final List auths; + + /// activeUserId is the current activated account userId + @JsonKey(name: 'activeUserId') + final String? activeUserId; - AuthState? get current => auths.isNotEmpty ? auths.last : null; + AuthState? get current { + if (auths.isEmpty) { + return null; + } + if (activeUserId != null) { + return auths + .firstWhereOrNull((element) => element.userId == activeUserId); + } + w('activeUserId is null'); + return auths.lastOrNull; + } @override - List get props => [auths]; + List get props => [auths, activeUserId]; Map toJson() => _$MultiAuthStateToJson(this); } @@ -61,11 +77,11 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { void signIn(AuthState authState) { state = MultiAuthState( - auths: { - ...state.auths.where( - (element) => element.account.userId != authState.account.userId), + auths: [ + ...state.auths.where((element) => element.userId != authState.userId), authState, - }, + ], + activeUserId: authState.userId, ); } @@ -79,18 +95,18 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { } authState = AuthState(account: account, privateKey: authState.privateKey); state = MultiAuthState( - auths: { - ...state.auths.where( - (element) => element.account.userId != authState?.account.userId), + auths: [ + ...state.auths.where((element) => element.userId != authState?.userId), authState, - }, + ], ); } void signOut() { if (state.auths.isEmpty) return; - state = - MultiAuthState(auths: state.auths.toSet()..remove(state.auths.last)); + final auths = state.auths.toList()..remove(state.current); + final activeUserId = auths.lastOrNull?.userId; + state = MultiAuthState(auths: auths, activeUserId: activeUserId); } @override @@ -113,13 +129,13 @@ final multiAuthStateNotifierProvider = if (oldJson != null) { final multiAuthState = fromHydratedJson(oldJson, MultiAuthState.fromJson); if (multiAuthState == null) { - return MultiAuthStateNotifier(const MultiAuthState()); + return MultiAuthStateNotifier(MultiAuthState()); } return MultiAuthStateNotifier(multiAuthState); } - return MultiAuthStateNotifier(const MultiAuthState()); + return MultiAuthStateNotifier(MultiAuthState()); }); final authProvider = diff --git a/lib/ui/provider/multi_auth_provider.g.dart b/lib/ui/provider/multi_auth_provider.g.dart index 7aa72863a1..c188762859 100644 --- a/lib/ui/provider/multi_auth_provider.g.dart +++ b/lib/ui/provider/multi_auth_provider.g.dart @@ -20,11 +20,13 @@ MultiAuthState _$MultiAuthStateFromJson(Map json) => MultiAuthState( auths: (json['auths'] as List?) ?.map((e) => AuthState.fromJson(e as Map)) - .toSet() ?? - const {}, + .toList() ?? + const [], + activeUserId: json['activeUserId'] as String?, ); Map _$MultiAuthStateToJson(MultiAuthState instance) => { 'auths': instance.auths.map((e) => e.toJson()).toList(), + 'activeUserId': instance.activeUserId, }; From ee174966781dd780d8df1cf8399c4ba97c542a19 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Thu, 26 Oct 2023 12:25:25 +0800 Subject: [PATCH 04/44] refactor --- lib/constants/constants.dart | 5 + lib/ui/home/slide_page.dart | 151 ++++++++--- lib/ui/landing/bloc/landing_cubit.dart | 201 --------------- lib/ui/landing/landing.dart | 42 ++- lib/ui/landing/landing_initialize.dart | 66 +++++ lib/ui/landing/landing_mobile.dart | 50 ++-- lib/ui/landing/landing_qrcode.dart | 254 ++++++++++++------- lib/ui/landing/{bloc => }/landing_state.dart | 3 +- lib/utils/mixin_api_client.dart | 41 ++- lib/widgets/menu.dart | 137 +++++----- pubspec.yaml | 2 +- 11 files changed, 508 insertions(+), 444 deletions(-) delete mode 100644 lib/ui/landing/bloc/landing_cubit.dart create mode 100644 lib/ui/landing/landing_initialize.dart rename lib/ui/landing/{bloc => }/landing_state.dart (94%) diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart index 52022eb3c6..5350da48ad 100644 --- a/lib/constants/constants.dart +++ b/lib/constants/constants.dart @@ -68,3 +68,8 @@ const kRecaptchaKey = ''; const hCaptchaKey = ''; const kDbFileName = 'mixin'; + +const kHttpLogLevel = String.fromEnvironment( + 'MIXIN_HTTP_LOG_LEVEL', + defaultValue: 'all', +); diff --git a/lib/ui/home/slide_page.dart b/lib/ui/home/slide_page.dart index 91823505bb..90a33699fc 100644 --- a/lib/ui/home/slide_page.dart +++ b/lib/ui/home/slide_page.dart @@ -24,6 +24,7 @@ import '../../widgets/select_item.dart'; import '../../widgets/toast.dart'; import '../../widgets/user_selector/conversation_selector.dart'; import '../../widgets/window/move_window.dart'; +import '../landing/landing.dart'; import '../provider/multi_auth_provider.dart'; import '../provider/setting_provider.dart'; import '../provider/slide_category_provider.dart'; @@ -142,45 +143,129 @@ class _CurrentUser extends HookConsumerWidget { .select((value) => value.type == SlideCategoryType.setting)); return MoveWindowBarrier( - child: SelectItem( - icon: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: AvatarWidget( - avatarUrl: account?.avatarUrl, - size: 24, - name: account?.fullName, - userId: account?.userId, + child: _MultiAccountPopupButton( + child: SelectItem( + icon: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: AvatarWidget( + avatarUrl: account?.avatarUrl, + size: 24, + name: account?.fullName, + userId: account?.userId, + ), + ), + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + account?.fullName ?? '', + style: const TextStyle(fontSize: 14), + ), + const SizedBox(height: 2), + Text( + '${account?.identityNumber}', + style: + TextStyle(color: context.theme.secondaryText, fontSize: 12), + ) + ], + ), + selected: selected, + onTap: () { + ref + .read(slideCategoryStateProvider.notifier) + .select(SlideCategoryType.setting); + + if (ModalRoute.of(context)?.canPop == true) { + Navigator.pop(context); + } + }, + ), + ), + ); + } +} + +class _MultiAccountPopupButton extends HookConsumerWidget { + const _MultiAccountPopupButton({required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final mas = ref.watch(multiAuthStateNotifierProvider); + return ContextMenuPortalEntry( + buildMenus: () => [ + for (final account in mas.auths) + _AccountMenuItem( + account: account.account, + selected: account.userId == mas.activeUserId, ), + Divider( + color: context.theme.divider, + height: 1, + indent: 8, + endIndent: 8, ), - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, + ContextMenu( + title: 'Add Account', + icon: Resources.assetsImagesIcAddSvg, + onTap: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const LandingPage()), + ); + }, + ), + ], + child: child, + ); + } +} + +class _AccountMenuItem extends StatelessWidget { + const _AccountMenuItem({ + required this.account, + required this.selected, + }); + + final Account account; + final bool selected; + + @override + Widget build(BuildContext context) => ContextMenuLayout( + child: Row( children: [ - Text( - account?.fullName ?? '', - style: const TextStyle(fontSize: 14), + Padding( + padding: const EdgeInsets.only(right: 8), + child: AvatarWidget( + avatarUrl: account.avatarUrl, + size: 24, + name: account.fullName, + userId: account.userId, + ), ), - const SizedBox(height: 2), - Text( - '${account?.identityNumber}', - style: - TextStyle(color: context.theme.secondaryText, fontSize: 12), - ) + Expanded( + child: Text( + account.fullName ?? '', + style: TextStyle( + fontSize: 14, + color: context.theme.text, + ), + ), + ), + if (selected) + SvgPicture.asset( + Resources.assetsImagesCheckedSvg, + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + context.theme.secondaryText, + BlendMode.srcIn, + ), + ), ], ), - selected: selected, - onTap: () { - ref - .read(slideCategoryStateProvider.notifier) - .select(SlideCategoryType.setting); - - if (ModalRoute.of(context)?.canPop == true) { - Navigator.pop(context); - } - }, - ), - ); - } + ); } class _CircleList extends HookConsumerWidget { diff --git a/lib/ui/landing/bloc/landing_cubit.dart b/lib/ui/landing/bloc/landing_cubit.dart deleted file mode 100644 index 55c2796af2..0000000000 --- a/lib/ui/landing/bloc/landing_cubit.dart +++ /dev/null @@ -1,201 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'dart:ui'; - -import 'package:bloc/bloc.dart'; -import 'package:dio/dio.dart'; -import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; -import 'package:flutter/material.dart'; -import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart' as signal; -import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; - -import '../../../account/account_key_value.dart'; -import '../../../crypto/crypto_key_value.dart'; -import '../../../crypto/signal/signal_protocol.dart'; -import '../../../generated/l10n.dart'; -import '../../../utils/extension/extension.dart'; -import '../../../utils/logger.dart'; -import '../../../utils/platform.dart'; -import '../../../utils/system/package_info.dart'; -import '../../provider/multi_auth_provider.dart'; -import 'landing_state.dart'; - -class LandingCubit extends Cubit { - LandingCubit( - this.multiAuthChangeNotifier, - Locale locale, - T initialState, { - String? userAgent, - String? deviceId, - }) : client = Client( - dioOptions: BaseOptions( - headers: { - 'Accept-Language': locale.languageCode, - if (userAgent != null) 'User-Agent': userAgent, - if (deviceId != null) 'Mixin-Device-Id': deviceId, - }, - ), - ), - super(initialState); - final Client client; - final MultiAuthStateNotifier multiAuthChangeNotifier; -} - -class LandingQrCodeCubit extends LandingCubit { - LandingQrCodeCubit( - MultiAuthStateNotifier multiAuthChangeNotifier, Locale locale) - : super( - multiAuthChangeNotifier, - locale, - LandingState( - status: multiAuthChangeNotifier.current != null - ? LandingStatus.provisioning - : LandingStatus.init, - ), - ) { - if (multiAuthChangeNotifier.current != null) return; - requestAuthUrl(); - } - - final StreamController<(int, String, signal.ECKeyPair)> - periodicStreamController = - StreamController<(int, String, signal.ECKeyPair)>(); - - StreamSubscription? _periodicSubscription; - - void _cancelPeriodicSubscription() { - final periodicSubscription = _periodicSubscription; - _periodicSubscription = null; - unawaited(periodicSubscription?.cancel()); - } - - Future requestAuthUrl() async { - _cancelPeriodicSubscription(); - try { - final rsp = await client.provisioningApi - .getProvisioningId(Platform.operatingSystem); - final keyPair = signal.Curve.generateKeyPair(); - final pubKey = - Uri.encodeComponent(base64Encode(keyPair.publicKey.serialize())); - - emit(state.copyWith( - authUrl: 'mixin://device/auth?id=${rsp.data.deviceId}&pub_key=$pubKey', - status: LandingStatus.ready, - )); - - _periodicSubscription = Stream.periodic( - const Duration(milliseconds: 1500), - (i) => i, - ) - .asyncBufferMap( - (event) => _checkLanding(event.last, rsp.data.deviceId, keyPair)) - .listen((event) {}); - } catch (error, stack) { - e('requestAuthUrl failed: $error $stack'); - emit(state.needReload('Failed to request auth: $error')); - } - } - - Future _checkLanding( - int count, - String deviceId, - signal.ECKeyPair keyPair, - ) async { - if (_periodicSubscription == null) return; - - if (count > 60) { - _cancelPeriodicSubscription(); - emit(state.needReload(Localization.current.qrCodeExpiredDesc)); - return; - } - - String secret; - try { - secret = - (await client.provisioningApi.getProvisioning(deviceId)).data.secret; - } catch (e) { - return; - } - if (secret.isEmpty) return; - - _cancelPeriodicSubscription(); - emit(state.copyWith(status: LandingStatus.provisioning)); - - try { - final (acount, privateKey) = await _verify(secret, keyPair); - multiAuthChangeNotifier - .signIn(AuthState(account: acount, privateKey: privateKey)); - } catch (error, stack) { - emit(state.needReload('Failed to verify: $error')); - e('_verify: $error $stack'); - } - } - - FutureOr<(Account, String)> _verify( - String secret, signal.ECKeyPair keyPair) async { - final result = - signal.decrypt(base64Encode(keyPair.privateKey.serialize()), secret); - final msg = - json.decode(String.fromCharCodes(result)) as Map; - - final edKeyPair = ed.generateKey(); - final private = base64.decode(msg['identity_key_private'] as String); - final registrationId = signal.generateRegistrationId(false); - - final sessionId = msg['session_id'] as String; - final info = await getPackageInfo(); - final appVersion = '${info.version}(${info.buildNumber})'; - final platformVersion = await getPlatformVersion(); - final rsp = await client.provisioningApi.verifyProvisioning( - ProvisioningRequest( - code: msg['provisioning_code'] as String, - userId: msg['user_id'] as String, - sessionId: sessionId, - purpose: 'SESSION', - sessionSecret: base64Encode(edKeyPair.publicKey.bytes), - appVersion: appVersion, - registrationId: registrationId, - platform: 'Desktop', - platformVersion: platformVersion, - ), - ); - - await SignalProtocol.initSignal( - rsp.data.identityNumber, registrationId, private); - - final privateKey = base64Encode(edKeyPair.privateKey.bytes); - - await AccountKeyValue.instance.init(rsp.data.identityNumber); - AccountKeyValue.instance.primarySessionId = sessionId; - await CryptoKeyValue.instance.init(rsp.data.identityNumber); - CryptoKeyValue.instance.localRegistrationId = registrationId; - - return ( - rsp.data, - privateKey, - ); - } - - @override - Future close() async { - await _periodicSubscription?.cancel(); - await periodicStreamController.close(); - await super.close(); - } -} - -class LandingMobileCubit extends LandingCubit { - LandingMobileCubit( - MultiAuthStateNotifier multiAuthChangeNotifier, - Locale locale, { - required String deviceId, - required String userAgent, - }) : super( - multiAuthChangeNotifier, - locale, - null, - deviceId: deviceId, - userAgent: userAgent, - ); -} diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index 762cd6cc0d..6ed259f226 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; @@ -18,16 +17,13 @@ import '../provider/account_server_provider.dart'; import 'landing_mobile.dart'; import 'landing_qrcode.dart'; -enum LandingMode { +enum _LandingMode { qrcode, mobile, } -class LandingModeCubit extends Cubit { - LandingModeCubit() : super(LandingMode.qrcode); - - void changeMode(LandingMode mode) => emit(mode); -} +final _landingModeProvider = + StateProvider.autoDispose<_LandingMode>((ref) => _LandingMode.qrcode); class LandingPage extends HookConsumerWidget { const LandingPage({super.key}); @@ -36,26 +32,20 @@ class LandingPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final accountServerHasError = ref.watch(accountServerProvider.select((value) => value.hasError)); - - final modeCubit = useBloc(LandingModeCubit.new); - final mode = useBlocState(bloc: modeCubit); - + final mode = ref.watch(_landingModeProvider); Widget child; switch (mode) { - case LandingMode.qrcode: + case _LandingMode.qrcode: child = const LandingQrCodeWidget(); break; - case LandingMode.mobile: + case _LandingMode.mobile: child = const LoginWithMobileWidget(); break; } if (accountServerHasError) { child = const _LoginFailed(); } - return BlocProvider.value( - value: modeCubit, - child: LandingScaffold(child: child), - ); + return LandingScaffold(child: child); } } @@ -228,26 +218,24 @@ class LandingModeSwitchButton extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final mode = useBlocState(); + final mode = ref.watch(_landingModeProvider); final String buttonText; switch (mode) { - case LandingMode.qrcode: + case _LandingMode.qrcode: buttonText = context.l10n.signWithPhoneNumber; break; - case LandingMode.mobile: + case _LandingMode.mobile: buttonText = context.l10n.signWithQrcode; break; } return TextButton( onPressed: () { - final modeCubit = context.read(); + final notifier = ref.read(_landingModeProvider.notifier); switch (mode) { - case LandingMode.qrcode: - modeCubit.changeMode(LandingMode.mobile); - break; - case LandingMode.mobile: - modeCubit.changeMode(LandingMode.qrcode); - break; + case _LandingMode.qrcode: + notifier.state = _LandingMode.mobile; + case _LandingMode.mobile: + notifier.state = _LandingMode.qrcode; } }, child: Text( diff --git a/lib/ui/landing/landing_initialize.dart b/lib/ui/landing/landing_initialize.dart new file mode 100644 index 0000000000..5fc0de6dc6 --- /dev/null +++ b/lib/ui/landing/landing_initialize.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +import '../../utils/extension/extension.dart'; +import 'landing.dart'; + +class AppInitializingPage extends StatelessWidget { + const AppInitializingPage({super.key}); + + @override + Widget build(BuildContext context) => LandingScaffold( + child: Center( + child: LoadingWidget( + title: context.l10n.initializing, + message: context.l10n.chatHintE2e, + ), + ), + ); +} + +class LoadingWidget extends StatelessWidget { + const LoadingWidget({ + super.key, + required this.title, + required this.message, + }); + + final String title; + final String message; + + @override + Widget build(BuildContext context) { + final primaryColor = context.theme.text; + return SizedBox( + width: 375, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(primaryColor), + ), + const SizedBox(height: 24), + Text( + title, + style: TextStyle( + color: primaryColor, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Text( + message, + textAlign: TextAlign.center, + style: TextStyle( + color: context.dynamicColor( + const Color.fromRGBO(188, 190, 195, 1), + darkColor: const Color.fromRGBO(255, 255, 255, 0.4), + ), + fontSize: 16, + ), + ), + ], + ), + ); + } +} diff --git a/lib/ui/landing/landing_mobile.dart b/lib/ui/landing/landing_mobile.dart index f6d19b12bc..4c0a9e0dd8 100644 --- a/lib/ui/landing/landing_mobile.dart +++ b/lib/ui/landing/landing_mobile.dart @@ -1,12 +1,9 @@ -// ignore_for_file: implementation_imports - import 'dart:async'; import 'dart:convert'; import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; @@ -19,8 +16,8 @@ import '../../constants/resources.dart'; import '../../crypto/crypto_key_value.dart'; import '../../crypto/signal/signal_protocol.dart'; import '../../utils/extension/extension.dart'; -import '../../utils/hook.dart'; import '../../utils/logger.dart'; +import '../../utils/mixin_api_client.dart'; import '../../utils/platform.dart'; import '../../utils/system/package_info.dart'; import '../../widgets/action_button.dart'; @@ -30,41 +27,35 @@ import '../../widgets/user/captcha_web_view_dialog.dart'; import '../../widgets/user/phone_number_input.dart'; import '../../widgets/user/verification_dialog.dart'; import '../provider/multi_auth_provider.dart'; -import 'bloc/landing_cubit.dart'; import 'landing.dart'; +final _mobileClientProvider = Provider.autoDispose( + (ref) => createLandingClient(), +); + class LoginWithMobileWidget extends HookConsumerWidget { const LoginWithMobileWidget({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final locale = useMemoized(() => Localizations.localeOf(context)); - final userAgent = useMemoizedFuture(generateUserAgent, null).data; - final deviceId = useMemoizedFuture(getDeviceId, null).data; - - if (userAgent == null || deviceId == null) { - return const Center(child: CircularProgressIndicator()); - } - return BlocProvider( - create: (_) => LandingMobileCubit(context.multiAuthChangeNotifier, locale, - userAgent: userAgent, deviceId: deviceId), - child: Navigator( - onPopPage: (_, __) => true, - pages: const [ - MaterialPage( - child: _PhoneNumberInputScene(), - ), - ], - ), + // keep client provider alive in mobile widget. + ref.watch(_mobileClientProvider); + return Navigator( + onPopPage: (_, __) => true, + pages: const [ + MaterialPage( + child: _PhoneNumberInputScene(), + ), + ], ); } } -class _PhoneNumberInputScene extends StatelessWidget { +class _PhoneNumberInputScene extends ConsumerWidget { const _PhoneNumberInputScene(); @override - Widget build(BuildContext context) => Column( + Widget build(BuildContext context, WidgetRef ref) => Column( children: [ const SizedBox(height: 56), Expanded( @@ -80,6 +71,7 @@ class _PhoneNumberInputScene extends StatelessWidget { final response = await _requestVerificationCode( phone: phoneNumber, context: context, + client: ref.read(_mobileClientProvider), ); Toast.dismiss(); if (response.deactivatedAt?.isNotEmpty ?? false) { @@ -170,7 +162,7 @@ class _CodeInputScene extends HookConsumerWidget { sessionSecret: sessionSecret, pin: '', ); - final client = context.read().client; + final client = ref.read(_mobileClientProvider); final response = await client.accountApi.create( verification.value.id, accountRequest, @@ -273,6 +265,7 @@ class _CodeInputScene extends HookConsumerWidget { final response = await _requestVerificationCode( phone: phoneNumber, context: context, + client: ref.read(_mobileClientProvider), ); Toast.dismiss(); verification.value = response; @@ -311,6 +304,7 @@ class _CodeInputScene extends HookConsumerWidget { Future _requestVerificationCode({ required String phone, required BuildContext context, + required Client client, (CaptchaType, String)? captcha, }) async { final request = VerificationRequest( @@ -322,8 +316,7 @@ Future _requestVerificationCode({ hCaptchaResponse: captcha?.$1 == CaptchaType.hCaptcha ? captcha?.$2 : null, ); try { - final cubit = context.read(); - final response = await cubit.client.accountApi.verification(request); + final response = await client.accountApi.verification(request); return response.data; } on MixinApiError catch (error) { final mixinError = error.error! as MixinError; @@ -338,6 +331,7 @@ Future _requestVerificationCode({ return _requestVerificationCode( phone: phone, context: context, + client: client, captcha: (type, token), ); } diff --git a/lib/ui/landing/landing_qrcode.dart b/lib/ui/landing/landing_qrcode.dart index 1aa9ad0592..dae244a0ef 100644 --- a/lib/ui/landing/landing_qrcode.dart +++ b/lib/ui/landing/landing_qrcode.dart @@ -1,47 +1,180 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart' as signal; +import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; +import 'package:mixin_logger/mixin_logger.dart'; +import '../../account/account_key_value.dart'; import '../../constants/resources.dart'; +import '../../crypto/crypto_key_value.dart'; +import '../../crypto/signal/signal_protocol.dart'; +import '../../generated/l10n.dart'; import '../../utils/extension/extension.dart'; -import '../../utils/hook.dart'; +import '../../utils/mixin_api_client.dart'; import '../../utils/platform.dart'; +import '../../utils/system/package_info.dart'; import '../../widgets/qr_code.dart'; -import 'bloc/landing_cubit.dart'; -import 'bloc/landing_state.dart'; +import '../provider/multi_auth_provider.dart'; import 'landing.dart'; +import 'landing_initialize.dart'; +import 'landing_state.dart'; + +class _QrCodeLoginNotifier extends StateNotifier { + _QrCodeLoginNotifier(Ref ref) + : multiAuth = ref.read(multiAuthStateNotifierProvider.notifier), + super(const LandingState(status: LandingStatus.provisioning)); + + final MultiAuthStateNotifier multiAuth; + final client = createLandingClient(); + + final StreamController<(int, String, signal.ECKeyPair)> + periodicStreamController = + StreamController<(int, String, signal.ECKeyPair)>(); + + StreamSubscription? _periodicSubscription; + + void _cancelPeriodicSubscription() { + final periodicSubscription = _periodicSubscription; + _periodicSubscription = null; + unawaited(periodicSubscription?.cancel()); + } + + Future requestAuthUrl() async { + _cancelPeriodicSubscription(); + try { + final rsp = await client.provisioningApi + .getProvisioningId(Platform.operatingSystem); + final keyPair = signal.Curve.generateKeyPair(); + final pubKey = + Uri.encodeComponent(base64Encode(keyPair.publicKey.serialize())); + + state = state.copyWith( + authUrl: 'mixin://device/auth?id=${rsp.data.deviceId}&pub_key=$pubKey', + status: LandingStatus.ready, + ); + + _periodicSubscription = Stream.periodic( + const Duration(milliseconds: 1500), + (i) => i, + ) + .asyncBufferMap( + (event) => _checkLanding(event.last, rsp.data.deviceId, keyPair)) + .listen((event) {}); + } catch (error, stack) { + e('requestAuthUrl failed: $error $stack'); + state = state.needReload('Failed to request auth: $error'); + } + } + + Future _checkLanding( + int count, + String deviceId, + signal.ECKeyPair keyPair, + ) async { + if (_periodicSubscription == null) return; + + if (count > 40) { + _cancelPeriodicSubscription(); + state = state.needReload(Localization.current.qrCodeExpiredDesc); + return; + } + + String secret; + try { + secret = + (await client.provisioningApi.getProvisioning(deviceId)).data.secret; + } catch (e) { + return; + } + if (secret.isEmpty) return; + + _cancelPeriodicSubscription(); + state = state.copyWith(status: LandingStatus.provisioning); + + try { + final (acount, privateKey) = await _verify(secret, keyPair); + multiAuth.signIn(AuthState(account: acount, privateKey: privateKey)); + } catch (error, stack) { + state = state.needReload('Failed to verify: $error'); + e('_verify: $error $stack'); + } + } + + FutureOr<(Account, String)> _verify( + String secret, signal.ECKeyPair keyPair) async { + final result = + signal.decrypt(base64Encode(keyPair.privateKey.serialize()), secret); + final msg = + json.decode(String.fromCharCodes(result)) as Map; + + final edKeyPair = ed.generateKey(); + final private = base64.decode(msg['identity_key_private'] as String); + final registrationId = signal.generateRegistrationId(false); + + final sessionId = msg['session_id'] as String; + final info = await getPackageInfo(); + final appVersion = '${info.version}(${info.buildNumber})'; + final platformVersion = await getPlatformVersion(); + final rsp = await client.provisioningApi.verifyProvisioning( + ProvisioningRequest( + code: msg['provisioning_code'] as String, + userId: msg['user_id'] as String, + sessionId: sessionId, + purpose: 'SESSION', + sessionSecret: base64Encode(edKeyPair.publicKey.bytes), + appVersion: appVersion, + registrationId: registrationId, + platform: 'Desktop', + platformVersion: platformVersion, + ), + ); + + await SignalProtocol.initSignal( + rsp.data.identityNumber, registrationId, private); + + final privateKey = base64Encode(edKeyPair.privateKey.bytes); + + await AccountKeyValue.instance.init(rsp.data.identityNumber); + AccountKeyValue.instance.primarySessionId = sessionId; + await CryptoKeyValue.instance.init(rsp.data.identityNumber); + CryptoKeyValue.instance.localRegistrationId = registrationId; + + return ( + rsp.data, + privateKey, + ); + } + + @override + Future dispose() async { + await _periodicSubscription?.cancel(); + await periodicStreamController.close(); + super.dispose(); + } +} + +final _qrCodeLoginProvider = + StateNotifierProvider.autoDispose<_QrCodeLoginNotifier, LandingState>( + _QrCodeLoginNotifier.new, +); class LandingQrCodeWidget extends HookConsumerWidget { const LandingQrCodeWidget({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final locale = useMemoized(() => Localizations.localeOf(context)); - - final landingCubit = useBloc(() => LandingQrCodeCubit( - context.multiAuthChangeNotifier, - locale, - )); - final status = - useBlocStateConverter( - bloc: landingCubit, - converter: (state) => state.status, - ); - + ref.watch(_qrCodeLoginProvider.select((value) => value.status)); final Widget child; - if (status == LandingStatus.init) { + if (status == LandingStatus.provisioning) { child = Center( - child: _Loading( - title: context.l10n.initializing, - message: context.l10n.chatHintE2e, - ), - ); - } else if (status == LandingStatus.provisioning) { - child = Center( - child: _Loading( + child: LoadingWidget( title: context.l10n.loading, message: context.l10n.chatHintE2e, ), @@ -68,10 +201,7 @@ class LandingQrCodeWidget extends HookConsumerWidget { ], ); } - return BlocProvider.value( - value: landingCubit, - child: child, - ); + return child; } } @@ -81,17 +211,13 @@ class _QrCode extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final url = - useBlocStateConverter( - converter: (state) => state.authUrl); + ref.watch(_qrCodeLoginProvider.select((value) => value.authUrl)); - final visible = - useBlocStateConverter( - converter: (state) => state.status == LandingStatus.needReload); + final visible = ref.watch(_qrCodeLoginProvider + .select((value) => value.status == LandingStatus.needReload)); final errorMessage = - useBlocStateConverter( - converter: (state) => state.errorMessage, - ); + ref.watch(_qrCodeLoginProvider.select((value) => value.errorMessage)); Widget? qrCode; @@ -118,8 +244,9 @@ class _QrCode extends HookConsumerWidget { visible: visible, child: _Retry( errorMessage: errorMessage, - onTap: () => - context.read().requestAuthUrl(), + onTap: () => ref + .read(_qrCodeLoginProvider.notifier) + .requestAuthUrl(), ), ), ], @@ -161,53 +288,6 @@ class _QrCode extends HookConsumerWidget { } } -class _Loading extends StatelessWidget { - const _Loading({ - required this.title, - required this.message, - }); - - final String title; - final String message; - - @override - Widget build(BuildContext context) { - final primaryColor = context.theme.text; - return SizedBox( - width: 375, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(primaryColor), - ), - const SizedBox(height: 24), - Text( - title, - style: TextStyle( - color: primaryColor, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Text( - message, - textAlign: TextAlign.center, - style: TextStyle( - color: context.dynamicColor( - const Color.fromRGBO(188, 190, 195, 1), - darkColor: const Color.fromRGBO(255, 255, 255, 0.4), - ), - fontSize: 16, - ), - ), - ], - ), - ); - } -} - class _Retry extends StatelessWidget { const _Retry({ required this.onTap, diff --git a/lib/ui/landing/bloc/landing_state.dart b/lib/ui/landing/landing_state.dart similarity index 94% rename from lib/ui/landing/bloc/landing_state.dart rename to lib/ui/landing/landing_state.dart index e69c334df1..44c8747ce4 100644 --- a/lib/ui/landing/bloc/landing_state.dart +++ b/lib/ui/landing/landing_state.dart @@ -4,13 +4,12 @@ enum LandingStatus { needReload, provisioning, ready, - init, } class LandingState extends Equatable { const LandingState({ this.authUrl, - this.status = LandingStatus.init, + required this.status, this.errorMessage, }); diff --git a/lib/utils/mixin_api_client.dart b/lib/utils/mixin_api_client.dart index 3c4efa5f91..396b4b559d 100644 --- a/lib/utils/mixin_api_client.dart +++ b/lib/utils/mixin_api_client.dart @@ -19,26 +19,35 @@ const kRequestTimeStampKey = 'requestTimeStamp'; Future _userAgent = generateUserAgent(); Future _deviceId = getDeviceId(); -Client createClient({ - required String userId, - required String sessionId, - required String privateKey, +final _httpLogLevel = switch (kHttpLogLevel) { + 'null' => null, + 'none' => HttpLogLevel.none, + 'headers' => HttpLogLevel.headers, + 'body' => HttpLogLevel.body, + _ => HttpLogLevel.all, +}; + +Client createLandingClient() => _createClient(); + +Client _createClient({ + String? userId, + String? sessionId, + String? privateKey, + String? scp, List interceptors = const [], - // Hive didn't support multi isolate. - required bool loginByPhoneNumber, }) { final client = Client( userId: userId, sessionId: sessionId, privateKey: privateKey, - scp: loginByPhoneNumber ? scpFull : scp, + scp: scp, + httpLogLevel: _httpLogLevel, dioOptions: BaseOptions( connectTimeout: tenSecond, receiveTimeout: tenSecond, sendTimeout: tenSecond, followRedirects: false, ), - // httpLogLevel: HttpLogLevel.none, jsonDecodeCallback: jsonDecode, interceptors: [ ...interceptors, @@ -80,6 +89,22 @@ Client createClient({ return client; } +Client createClient({ + required String userId, + required String sessionId, + required String privateKey, + List interceptors = const [], + // Hive didn't support multi isolate. + required bool loginByPhoneNumber, +}) => + _createClient( + userId: userId, + sessionId: sessionId, + privateKey: privateKey, + scp: loginByPhoneNumber ? scpFull : scp, + interceptors: interceptors, + ); + final _formatter = DateFormat('yyyy-MM-dd HH:mm:ss.SSS'); extension _DateTimeFormatter on DateTime { diff --git a/lib/widgets/menu.dart b/lib/widgets/menu.dart index c2275e3b8e..a16e813fd6 100644 --- a/lib/widgets/menu.dart +++ b/lib/widgets/menu.dart @@ -327,6 +327,46 @@ class ContextMenuPage extends StatelessWidget { } } +class ContextMenuLayout extends StatelessWidget { + const ContextMenuLayout({ + super.key, + this.onTap, + this.onTapUp, + required this.child, + }); + + final VoidCallback? onTap; + final GestureTapUpCallback? onTapUp; + + final Widget child; + + @override + Widget build(BuildContext context) { + final backgroundColor = context.dynamicColor( + const Color.fromRGBO(255, 255, 255, 1), + darkColor: const Color.fromRGBO(62, 65, 72, 1), + ); + return ConstrainedBox( + constraints: const BoxConstraints(minWidth: 160), + child: InteractiveDecoratedBox.color( + decoration: BoxDecoration( + color: backgroundColor, + ), + tapDowningColor: Color.alphaBlend( + context.theme.listSelected, + backgroundColor, + ), + onTap: onTap, + onTapUp: onTapUp, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: child, + ), + ), + ); + } +} + class ContextMenu extends StatelessWidget { const ContextMenu({ super.key, @@ -351,71 +391,54 @@ class ContextMenu extends StatelessWidget { @override Widget build(BuildContext context) { - final backgroundColor = context.dynamicColor( - const Color.fromRGBO(255, 255, 255, 1), - darkColor: const Color.fromRGBO(62, 65, 72, 1), - ); final color = isDestructiveAction ? context.theme.red : context.dynamicColor( const Color.fromRGBO(0, 0, 0, 1), darkColor: const Color.fromRGBO(255, 255, 255, 0.9), ); - return ConstrainedBox( - constraints: const BoxConstraints(minWidth: 160), - child: InteractiveDecoratedBox.color( - decoration: BoxDecoration( - color: backgroundColor, - ), - tapDowningColor: Color.alphaBlend( - context.theme.listSelected, - backgroundColor, - ), - onTap: () { - onTap?.call(); - if (!_subMenuMode) context.closeMenu(); - }, - onTapUp: (details) { - if (_subMenuMode && details.kind == PointerDeviceKind.touch) { - const SubMenuClickedByTouchNotification().dispatch(context); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - children: [ - if (icon != null) - Padding( - padding: const EdgeInsets.only(right: 8), - child: SvgPicture.asset( - icon!, - colorFilter: ColorFilter.mode(color, BlendMode.srcIn), - width: 20, - height: 20, - ), - ), - Expanded( - child: Text( - title, - style: TextStyle( - fontSize: 14, - color: color, - ), - ), + return ContextMenuLayout( + onTap: () { + onTap?.call(); + if (!_subMenuMode) context.closeMenu(); + }, + onTapUp: (details) { + if (_subMenuMode && details.kind == PointerDeviceKind.touch) { + const SubMenuClickedByTouchNotification().dispatch(context); + } + }, + child: Row( + children: [ + if (icon != null) + Padding( + padding: const EdgeInsets.only(right: 8), + child: SvgPicture.asset( + icon!, + colorFilter: ColorFilter.mode(color, BlendMode.srcIn), + width: 20, + height: 20, ), - if (_subMenuMode) - SvgPicture.asset( - Resources.assetsImagesIcArrowRightSvg, - width: 20, - height: 20, - colorFilter: ColorFilter.mode( - context.theme.secondaryText, - BlendMode.srcIn, - ), - ), - ], + ), + Expanded( + child: Text( + title, + style: TextStyle( + fontSize: 14, + color: color, + ), + ), ), - ), + if (_subMenuMode) + SvgPicture.asset( + Resources.assetsImagesIcArrowRightSvg, + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + context.theme.secondaryText, + BlendMode.srcIn, + ), + ), + ], ), ); } diff --git a/pubspec.yaml b/pubspec.yaml index b5ed07bd74..4bd4982806 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -231,4 +231,4 @@ msix_config: toast_activator: clsid: "94B64592-528D-48B4-B37B-C82D634F1BE7" arguments: "-ToastActivated" - display_name: "Mixin Messenger" + display_name: "Mixin Messenger" \ No newline at end of file From a1501894938192491e3ccf7da3e2d72d1d2c1039 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Thu, 26 Oct 2023 13:29:42 +0800 Subject: [PATCH 05/44] improve --- lib/account/account_server.dart | 10 +++++--- lib/main.dart | 8 +++++- lib/ui/home/slide_page.dart | 10 ++++++-- lib/ui/landing/landing_qrcode.dart | 4 ++- lib/ui/provider/multi_auth_provider.dart | 31 ++++++++++++++++-------- 5 files changed, 46 insertions(+), 17 deletions(-) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 95ad5aa8d6..dd8754487b 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -105,8 +105,8 @@ class AccountServer { try { await checkSignalKeys(); - } catch (e, s) { - w('$e, $s'); + } catch (error, stacktrace) { + e('checkSignalKeys failed: $error $stacktrace'); await signOutAndClear(); multiAuthNotifier.signOut(); rethrow; @@ -312,7 +312,11 @@ class AccountServer { Future signOutAndClear() async { _sendEventToWorkerIsolate(MainIsolateEventType.exit); - await client.accountApi.logout(LogoutRequest(sessionId)); + try { + await client.accountApi.logout(LogoutRequest(sessionId)); + } catch (error, stacktrace) { + e('signOutAndClear logout error: $error $stacktrace'); + } await Future.wait(jobSubscribers.map((s) => s.cancel())); jobSubscribers.clear(); diff --git a/lib/main.dart b/lib/main.dart index a091a207bc..94f4270039 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'dart:math' as math; import 'package:ansicolor/ansicolor.dart'; +import 'package:dio/dio.dart'; import 'package:drift/drift.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; @@ -83,7 +84,12 @@ Future main(List args) async { e('FlutterError: ${details.exception} ${details.stack}'); }; PlatformDispatcher.instance.onError = (error, stack) { - e('unhandled error: $error $stack'); + e('unhandled error: $error'); + if (error is DioException) { + e('stacktrace: ${error.stackTrace}'); + } else { + e('stacktrace: $stack'); + } return true; }; diff --git a/lib/ui/home/slide_page.dart b/lib/ui/home/slide_page.dart index 90a33699fc..bc61758989 100644 --- a/lib/ui/home/slide_page.dart +++ b/lib/ui/home/slide_page.dart @@ -222,7 +222,7 @@ class _MultiAccountPopupButton extends HookConsumerWidget { } } -class _AccountMenuItem extends StatelessWidget { +class _AccountMenuItem extends ConsumerWidget { const _AccountMenuItem({ required this.account, required this.selected, @@ -232,7 +232,13 @@ class _AccountMenuItem extends StatelessWidget { final bool selected; @override - Widget build(BuildContext context) => ContextMenuLayout( + Widget build(BuildContext context, WidgetRef ref) => ContextMenuLayout( + onTap: () { + ref + .read(multiAuthStateNotifierProvider.notifier) + .active(account.userId); + context.closeMenu(); + }, child: Row( children: [ Padding( diff --git a/lib/ui/landing/landing_qrcode.dart b/lib/ui/landing/landing_qrcode.dart index dae244a0ef..a546f35f06 100644 --- a/lib/ui/landing/landing_qrcode.dart +++ b/lib/ui/landing/landing_qrcode.dart @@ -28,7 +28,9 @@ import 'landing_state.dart'; class _QrCodeLoginNotifier extends StateNotifier { _QrCodeLoginNotifier(Ref ref) : multiAuth = ref.read(multiAuthStateNotifierProvider.notifier), - super(const LandingState(status: LandingStatus.provisioning)); + super(const LandingState(status: LandingStatus.provisioning)) { + requestAuthUrl(); + } final MultiAuthStateNotifier multiAuth; final client = createLandingClient(); diff --git a/lib/ui/provider/multi_auth_provider.dart b/lib/ui/provider/multi_auth_provider.dart index d2652ecea6..bd6c9ce21b 100644 --- a/lib/ui/provider/multi_auth_provider.dart +++ b/lib/ui/provider/multi_auth_provider.dart @@ -86,20 +86,18 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { } void updateAccount(Account account) { - var authState = state.auths - .cast() - .firstWhere((element) => element?.account.userId == account.userId); - if (authState == null) { + final index = + state.auths.indexWhere((element) => element.userId == account.userId); + if (index == -1) { i('update account, but ${account.userId} auth state not found.'); return; } - authState = AuthState(account: account, privateKey: authState.privateKey); - state = MultiAuthState( - auths: [ - ...state.auths.where((element) => element.userId != authState?.userId), - authState, - ], + final auths = state.auths.toList(); + auths[index] = AuthState( + account: account, + privateKey: state.auths[index].privateKey, ); + state = MultiAuthState(auths: auths); } void signOut() { @@ -116,6 +114,18 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { HydratedBloc.storage.write(_kMultiAuthCubitKey, hydratedJson); super.state = value; } + + void active(String userId) { + final exist = state.auths.any((element) => element.userId == userId); + if (!exist) { + e('failed to active, no account exist for id: $userId'); + return; + } + state = MultiAuthState( + auths: state.auths, + activeUserId: userId, + ); + } } const _kMultiAuthCubitKey = 'MultiAuthCubit'; @@ -132,6 +142,7 @@ final multiAuthStateNotifierProvider = return MultiAuthStateNotifier(MultiAuthState()); } + d('read multi auth state from storage: $multiAuthState'); return MultiAuthStateNotifier(multiAuthState); } From a62b5671e8aab0750bbef72b8b8f30fa547a0fac Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:09:18 +0800 Subject: [PATCH 06/44] refactor --- lib/account/account_key_value.dart | 8 +- lib/account/account_server.dart | 49 ++++--- lib/app.dart | 14 +- lib/crypto/crypto_key_value.dart | 6 +- lib/crypto/signal/pre_key_util.dart | 17 ++- lib/crypto/signal/signal_key_util.dart | 21 +-- lib/ui/landing/landing.dart | 7 +- lib/ui/landing/landing_mobile.dart | 7 +- lib/ui/landing/landing_qrcode.dart | 18 +-- lib/ui/provider/account_server_provider.dart | 134 +++++++++---------- lib/ui/provider/hive_key_value_provider.dart | 36 +++++ lib/ui/provider/multi_auth_provider.dart | 18 ++- lib/ui/setting/setting_page.dart | 3 +- lib/utils/hive_key_values.dart | 29 +++- lib/widgets/sticker_page/emoji_page.dart | 20 ++- lib/widgets/sticker_page/sticker_page.dart | 21 +-- lib/workers/decrypt_message.dart | 5 +- 17 files changed, 240 insertions(+), 173 deletions(-) create mode 100644 lib/ui/provider/hive_key_value_provider.dart diff --git a/lib/account/account_key_value.dart b/lib/account/account_key_value.dart index ec9d5e6991..a86f350ec4 100644 --- a/lib/account/account_key_value.dart +++ b/lib/account/account_key_value.dart @@ -1,12 +1,8 @@ import '../utils/extension/extension.dart'; import '../utils/hive_key_values.dart'; -class AccountKeyValue extends HiveKeyValue { - AccountKeyValue._() : super(_hiveAccount); - - static AccountKeyValue? _instance; - - static AccountKeyValue get instance => _instance ??= AccountKeyValue._(); +class AccountKeyValue extends HiveKeyValue { + AccountKeyValue() : super(_hiveAccount); static const _hiveAccount = 'account_box'; static const _hasSyncCircle = 'has_sync_circle'; diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index dd8754487b..1413327130 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -7,6 +7,7 @@ import 'package:cross_file/cross_file.dart'; import 'package:dio/dio.dart'; import 'package:drift/drift.dart'; import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_channel/isolate_channel.dart'; @@ -15,6 +16,7 @@ import 'package:uuid/uuid.dart'; import '../blaze/blaze.dart'; import '../blaze/vo/pin_message_minimal.dart'; import '../constants/constants.dart'; +import '../crypto/crypto_key_value.dart'; import '../crypto/privacy_key_value.dart'; import '../crypto/signal/signal_database.dart'; import '../crypto/signal/signal_key_util.dart'; @@ -56,7 +58,12 @@ class AccountServer { required this.settingChangeNotifier, required this.database, required this.currentConversationId, - }); + required this.ref, + required this.accountKeyValue, + required this.cryptoKeyValue, + }) { + i('AccountServer init'); + } static String? sid; @@ -67,13 +74,16 @@ class AccountServer { final SettingChangeNotifier settingChangeNotifier; final Database database; final GetCurrentConversationId currentConversationId; + final AccountKeyValue accountKeyValue; + final CryptoKeyValue cryptoKeyValue; + Timer? checkSignalKeyTimer; - bool get _loginByPhoneNumber => - AccountKeyValue.instance.primarySessionId == null; + bool get loginByPhoneNumber => accountKeyValue.primarySessionId == null; String? userAgent; String? deviceId; SignalDatabase? signalDatabase; + final Ref ref; Future initServer( String userId, @@ -98,9 +108,10 @@ class AccountServer { openForLogin: false, fromMainIsolate: true, ); - checkSignalKeyTimer = Timer.periodic(const Duration(days: 1), (timer) { + checkSignalKeyTimer = + Timer.periodic(const Duration(days: 1), (timer) async { i('refreshSignalKeys periodic'); - checkSignalKey(client, signalDatabase!); + await checkSignalKey(client, signalDatabase!, cryptoKeyValue); }); try { @@ -150,7 +161,7 @@ class AccountServer { userId: userId, sessionId: sessionId, privateKey: privateKey, - loginByPhoneNumber: _loginByPhoneNumber, + loginByPhoneNumber: loginByPhoneNumber, interceptors: [ InterceptorsWrapper( onError: ( @@ -213,8 +224,8 @@ class AccountServer { sessionId: sessionId, privateKey: privateKey, mixinDocumentDirectory: mixinDocumentsDirectory.path, - primarySessionId: AccountKeyValue.instance.primarySessionId, - loginByPhoneNumber: _loginByPhoneNumber, + primarySessionId: accountKeyValue.primarySessionId, + loginByPhoneNumber: loginByPhoneNumber, rootIsolateToken: ServicesBinding.rootIsolateToken!, ), errorsAreFatal: false, @@ -701,7 +712,7 @@ class AccountServer { List messageIds, Map messageExpireAt, ) async { - final primarySessionId = AccountKeyValue.instance.primarySessionId; + final primarySessionId = accountKeyValue.primarySessionId; if (primarySessionId == null) { return; } @@ -717,6 +728,7 @@ class AccountServer { } Future stop() async { + i('stop account server'); appActiveListener.removeListener(onActive); checkSignalKeyTimer?.cancel(); _sendEventToWorkerIsolate(MainIsolateEventType.exit); @@ -755,16 +767,15 @@ class AccountServer { Future checkSignalKeys() async { final hasPushSignalKeys = PrivacyKeyValue.instance.hasPushSignalKeys; if (hasPushSignalKeys) { - unawaited(checkSignalKey(client, signalDatabase!)); + unawaited(checkSignalKey(client, signalDatabase!, cryptoKeyValue)); } else { - await refreshSignalKeys(client, signalDatabase!); + await refreshSignalKeys(client, signalDatabase!, cryptoKeyValue); PrivacyKeyValue.instance.hasPushSignalKeys = true; } } Future refreshSticker({bool force = false}) async { - final refreshStickerLastTime = - AccountKeyValue.instance.refreshStickerLastTime; + final refreshStickerLastTime = accountKeyValue.refreshStickerLastTime; final now = DateTime.now().millisecondsSinceEpoch; if (!force && now - refreshStickerLastTime < hours24) { return; @@ -805,16 +816,16 @@ class AccountServer { } if (hasNewAlbum) { - AccountKeyValue.instance.hasNewAlbum = true; + accountKeyValue.hasNewAlbum = true; } - AccountKeyValue.instance.refreshStickerLastTime = now; + accountKeyValue.refreshStickerLastTime = now; } final refreshUserIdSet = {}; Future initCircles() async { - final hasSyncCircle = AccountKeyValue.instance.hasSyncCircle; + final hasSyncCircle = accountKeyValue.hasSyncCircle; if (hasSyncCircle) { return; } @@ -829,16 +840,16 @@ class AccountServer { await handleCircle(circle); }); - AccountKeyValue.instance.hasSyncCircle = true; + accountKeyValue.hasSyncCircle = true; } Future _cleanupQuoteContent() async { - final clean = AccountKeyValue.instance.alreadyCleanupQuoteContent; + final clean = accountKeyValue.alreadyCleanupQuoteContent; if (clean) { return; } await database.jobDao.insert(createCleanupQuoteContentJob()); - AccountKeyValue.instance.alreadyCleanupQuoteContent = true; + accountKeyValue.alreadyCleanupQuoteContent = true; } Future checkMigration() async { diff --git a/lib/app.dart b/lib/app.dart index 6eed312d79..c64695605e 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -10,12 +10,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart' hide Provider, FutureProvider, Consumer; import 'package:provider/provider.dart'; -import 'account/account_key_value.dart'; import 'account/notification_service.dart'; import 'constants/brightness_theme_data.dart'; import 'constants/resources.dart'; import 'generated/l10n.dart'; - import 'ui/home/bloc/conversation_list_bloc.dart'; import 'ui/home/conversation/conversation_page.dart'; import 'ui/home/home.dart'; @@ -23,6 +21,7 @@ import 'ui/landing/landing.dart'; import 'ui/landing/landing_failed.dart'; import 'ui/provider/account_server_provider.dart'; import 'ui/provider/database_provider.dart'; +import 'ui/provider/hive_key_value_provider.dart'; import 'ui/provider/mention_cache_provider.dart'; import 'ui/provider/multi_auth_provider.dart'; import 'ui/provider/setting_provider.dart'; @@ -233,10 +232,15 @@ class _Home extends HookConsumerWidget { final currentDeviceId = await getDeviceId(); if (currentDeviceId == 'unknown') return; - final deviceId = AccountKeyValue.instance.deviceId; - + final accountKeyValue = + await ref.read(currentAccountKeyValueProvider.future); + if (accountKeyValue == null) { + w('checkDeviceId error: accountKeyValue is null'); + return; + } + final deviceId = accountKeyValue.deviceId; if (deviceId == null) { - await AccountKeyValue.instance.setDeviceId(currentDeviceId); + await accountKeyValue.setDeviceId(currentDeviceId); return; } diff --git a/lib/crypto/crypto_key_value.dart b/lib/crypto/crypto_key_value.dart index 0a9e299e05..44e84c5b87 100644 --- a/lib/crypto/crypto_key_value.dart +++ b/lib/crypto/crypto_key_value.dart @@ -5,11 +5,7 @@ import '../utils/crypto_util.dart'; import '../utils/hive_key_values.dart'; class CryptoKeyValue extends HiveKeyValue { - CryptoKeyValue._() : super(_hiveCrypto); - - static CryptoKeyValue? _instance; - - static CryptoKeyValue get instance => _instance ??= CryptoKeyValue._(); + CryptoKeyValue() : super(_hiveCrypto); static const _hiveCrypto = 'crypto_box'; static const _localRegistrationId = 'local_registration_id'; diff --git a/lib/crypto/signal/pre_key_util.dart b/lib/crypto/signal/pre_key_util.dart index 3d5ab67617..16c807e988 100644 --- a/lib/crypto/signal/pre_key_util.dart +++ b/lib/crypto/signal/pre_key_util.dart @@ -1,5 +1,4 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; - // ignore: implementation_imports import 'package:libsignal_protocol_dart/src/util/key_helper.dart' as helper; @@ -9,30 +8,30 @@ import 'storage/mixin_prekey_store.dart'; const batchSize = 700; -Future> generatePreKeys(SignalDatabase database) async { +Future> generatePreKeys( + SignalDatabase database, CryptoKeyValue cryptoKeyValue) async { final preKeyStore = MixinPreKeyStore(database); - final preKeyIdOffset = CryptoKeyValue.instance.nextPreKeyId; + final preKeyIdOffset = cryptoKeyValue.nextPreKeyId; final records = helper.generatePreKeys(preKeyIdOffset, batchSize); final preKeys = []; for (final r in records) { preKeys.add(PrekeysCompanion.insert(prekeyId: r.id, record: r.serialize())); } await preKeyStore.storePreKeyList(preKeys); - CryptoKeyValue.instance.nextPreKeyId = - (preKeyIdOffset + batchSize + 1) % maxValue; + cryptoKeyValue.nextPreKeyId = (preKeyIdOffset + batchSize + 1) % maxValue; return records; } Future generateSignedPreKey(IdentityKeyPair identityKeyPair, - bool active, SignalDatabase database) async { + bool active, SignalDatabase database, CryptoKeyValue cryptoKeyValue) async { final signedPreKeyStore = MixinPreKeyStore(database); - final signedPreKeyId = CryptoKeyValue.instance.nextSignedPreKeyId; + final signedPreKeyId = cryptoKeyValue.nextSignedPreKeyId; final record = helper.generateSignedPreKey(identityKeyPair, signedPreKeyId); await signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); - CryptoKeyValue.instance.nextSignedPreKeyId = (signedPreKeyId + 1) % maxValue; + cryptoKeyValue.nextSignedPreKeyId = (signedPreKeyId + 1) % maxValue; if (active) { - CryptoKeyValue.instance.activeSignedPreKeyId = signedPreKeyId; + cryptoKeyValue.activeSignedPreKeyId = signedPreKeyId; } return record; } diff --git a/lib/crypto/signal/signal_key_util.dart b/lib/crypto/signal/signal_key_util.dart index e0e6a12981..e4038c3849 100644 --- a/lib/crypto/signal/signal_key_util.dart +++ b/lib/crypto/signal/signal_key_util.dart @@ -2,6 +2,7 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart' hide generateSignedPreKey, generatePreKeys; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; +import '../crypto_key_value.dart'; import 'identity_key_util.dart'; import 'pre_key_util.dart'; import 'signal_database.dart'; @@ -9,29 +10,31 @@ import 'signal_key_request.dart'; const int preKeyMinNum = 500; -Future checkSignalKey(Client client, SignalDatabase signalDatabase) async { +Future checkSignalKey(Client client, SignalDatabase signalDatabase, + CryptoKeyValue cryptoKeyValue) async { final response = await client.accountApi.getSignalKeyCount(); final availableKeyCount = response.data.preKeyCount; if (availableKeyCount > preKeyMinNum) { return; } - await refreshSignalKeys(client, signalDatabase); + await refreshSignalKeys(client, signalDatabase, cryptoKeyValue); } -Future> refreshSignalKeys( - Client client, SignalDatabase signalDatabase) async { - final keys = await generateKeys(signalDatabase); +Future> refreshSignalKeys(Client client, + SignalDatabase signalDatabase, CryptoKeyValue cryptoKeyValue) async { + final keys = await generateKeys(signalDatabase, cryptoKeyValue); return client.accountApi.pushSignalKeys(keys.toJson()); } -Future generateKeys(SignalDatabase signalDatabase) async { +Future generateKeys( + SignalDatabase signalDatabase, CryptoKeyValue cryptoKeyValue) async { final identityKeyPair = await getIdentityKeyPair(signalDatabase); if (identityKeyPair == null) { throw InvalidKeyException('Local identity key pair is null!'); } - final oneTimePreKeys = await generatePreKeys(signalDatabase); - final signedPreKeyRecord = - await generateSignedPreKey(identityKeyPair, false, signalDatabase); + final oneTimePreKeys = await generatePreKeys(signalDatabase, cryptoKeyValue); + final signedPreKeyRecord = await generateSignedPreKey( + identityKeyPair, false, signalDatabase, cryptoKeyValue); return SignalKeyRequest.from( identityKeyPair.getPublicKey(), signedPreKeyRecord, preKeyRecords: oneTimePreKeys); diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index 6ed259f226..d1fef859a9 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -4,7 +4,6 @@ import 'package:flutter_portal/flutter_portal.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; -import '../../account/account_key_value.dart'; import '../../crypto/signal/signal_database.dart'; import '../../utils/extension/extension.dart'; import '../../utils/hive_key_values.dart'; @@ -14,6 +13,7 @@ import '../../utils/system/package_info.dart'; import '../../widgets/dialog.dart'; import '../../widgets/toast.dart'; import '../provider/account_server_provider.dart'; +import '../provider/hive_key_value_provider.dart'; import 'landing_mobile.dart'; import 'landing_qrcode.dart'; @@ -131,12 +131,15 @@ class _LoginFailed extends HookConsumerWidget { final authState = context.auth; if (authState == null) return; + final accountKeyValue = + await ref.read(currentAccountKeyValueProvider.future); + await createClient( userId: authState.account.userId, sessionId: authState.account.sessionId, privateKey: authState.privateKey, loginByPhoneNumber: - AccountKeyValue.instance.primarySessionId == null, + accountKeyValue?.primarySessionId == null, ) .accountApi .logout(LogoutRequest(authState.account.sessionId)); diff --git a/lib/ui/landing/landing_mobile.dart b/lib/ui/landing/landing_mobile.dart index 4c0a9e0dd8..a612485308 100644 --- a/lib/ui/landing/landing_mobile.dart +++ b/lib/ui/landing/landing_mobile.dart @@ -13,7 +13,6 @@ import 'package:pin_code_fields/pin_code_fields.dart'; import '../../account/session_key_value.dart'; import '../../constants/resources.dart'; -import '../../crypto/crypto_key_value.dart'; import '../../crypto/signal/signal_protocol.dart'; import '../../utils/extension/extension.dart'; import '../../utils/logger.dart'; @@ -26,6 +25,7 @@ import '../../widgets/toast.dart'; import '../../widgets/user/captcha_web_view_dialog.dart'; import '../../widgets/user/phone_number_input.dart'; import '../../widgets/user/verification_dialog.dart'; +import '../provider/hive_key_value_provider.dart'; import '../provider/multi_auth_provider.dart'; import 'landing.dart'; @@ -173,8 +173,9 @@ class _CodeInputScene extends HookConsumerWidget { final privateKey = base64Encode(sessionKey.privateKey.bytes); - await CryptoKeyValue.instance.init(identityNumber); - CryptoKeyValue.instance.localRegistrationId = registrationId; + final cryptoKeyValue = + await ref.read(cryptoKeyValueProvider(identityNumber).future); + cryptoKeyValue.localRegistrationId = registrationId; await SessionKeyValue.instance.init(identityNumber); SessionKeyValue.instance.pinToken = base64Encode(decryptPinToken( diff --git a/lib/ui/landing/landing_qrcode.dart b/lib/ui/landing/landing_qrcode.dart index a546f35f06..e90cd85571 100644 --- a/lib/ui/landing/landing_qrcode.dart +++ b/lib/ui/landing/landing_qrcode.dart @@ -10,9 +10,7 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart' as signal; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:mixin_logger/mixin_logger.dart'; -import '../../account/account_key_value.dart'; import '../../constants/resources.dart'; -import '../../crypto/crypto_key_value.dart'; import '../../crypto/signal/signal_protocol.dart'; import '../../generated/l10n.dart'; import '../../utils/extension/extension.dart'; @@ -20,13 +18,14 @@ import '../../utils/mixin_api_client.dart'; import '../../utils/platform.dart'; import '../../utils/system/package_info.dart'; import '../../widgets/qr_code.dart'; +import '../provider/hive_key_value_provider.dart'; import '../provider/multi_auth_provider.dart'; import 'landing.dart'; import 'landing_initialize.dart'; import 'landing_state.dart'; class _QrCodeLoginNotifier extends StateNotifier { - _QrCodeLoginNotifier(Ref ref) + _QrCodeLoginNotifier(this.ref) : multiAuth = ref.read(multiAuthStateNotifierProvider.notifier), super(const LandingState(status: LandingStatus.provisioning)) { requestAuthUrl(); @@ -34,6 +33,7 @@ class _QrCodeLoginNotifier extends StateNotifier { final MultiAuthStateNotifier multiAuth; final client = createLandingClient(); + final Ref ref; final StreamController<(int, String, signal.ECKeyPair)> periodicStreamController = @@ -137,15 +137,15 @@ class _QrCodeLoginNotifier extends StateNotifier { ), ); - await SignalProtocol.initSignal( - rsp.data.identityNumber, registrationId, private); + final identityNumber = rsp.data.identityNumber; + await SignalProtocol.initSignal(identityNumber, registrationId, private); final privateKey = base64Encode(edKeyPair.privateKey.bytes); - await AccountKeyValue.instance.init(rsp.data.identityNumber); - AccountKeyValue.instance.primarySessionId = sessionId; - await CryptoKeyValue.instance.init(rsp.data.identityNumber); - CryptoKeyValue.instance.localRegistrationId = registrationId; + final accountKeyValue = await ref.read(accountKeyValueProvider(identityNumber).future); + accountKeyValue.primarySessionId = sessionId; + final cryptoKeyValue = await ref.read(cryptoKeyValueProvider(identityNumber).future); + cryptoKeyValue.localRegistrationId = registrationId; return ( rsp.data, diff --git a/lib/ui/provider/account_server_provider.dart b/lib/ui/provider/account_server_provider.dart index 786125b06e..0c4f1b0eb6 100644 --- a/lib/ui/provider/account_server_provider.dart +++ b/lib/ui/provider/account_server_provider.dart @@ -1,66 +1,48 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../../account/account_key_value.dart'; import '../../account/account_server.dart'; +import '../../crypto/crypto_key_value.dart'; import '../../db/database.dart'; -import '../../utils/logger.dart'; -import '../../utils/rivepod.dart'; import 'conversation_provider.dart'; import 'database_provider.dart'; +import 'hive_key_value_provider.dart'; import 'multi_auth_provider.dart'; import 'setting_provider.dart'; typedef GetCurrentConversationId = String? Function(); -class AccountServerOpener - extends DistinctStateNotifier> { - AccountServerOpener() : super(const AsyncValue.loading()); - - AccountServerOpener.open({ - required this.multiAuthChangeNotifier, - required this.settingChangeNotifier, - required this.database, - required this.userId, - required this.sessionId, - required this.identityNumber, - required this.privateKey, - required this.currentConversationId, - }) : super(const AsyncValue.loading()) { - _init(); - } - - late final MultiAuthStateNotifier multiAuthChangeNotifier; - late final SettingChangeNotifier settingChangeNotifier; - late final Database database; - - late final String userId; - late final String sessionId; - late final String identityNumber; - late final String privateKey; - late final GetCurrentConversationId currentConversationId; +class AccountServerOpener extends AutoDisposeStreamNotifier { + AccountServerOpener._(); - Future _init() async { + @override + Stream build() async* { + final args = await ref.watch(_argsProvider.future); + if (args == null) { + return; + } final accountServer = AccountServer( - multiAuthNotifier: multiAuthChangeNotifier, - settingChangeNotifier: settingChangeNotifier, - database: database, - currentConversationId: currentConversationId, + multiAuthNotifier: args.multiAuthChangeNotifier, + settingChangeNotifier: args.settingChangeNotifier, + database: args.database, + currentConversationId: args.currentConversationId, + ref: ref, + accountKeyValue: args.accountKeyValue, + cryptoKeyValue: args.cryptoKeyValue, ); await accountServer.initServer( - userId, - sessionId, - identityNumber, - privateKey, + args.userId, + args.sessionId, + args.identityNumber, + args.privateKey, ); - state = AsyncValue.data(accountServer); - } - - @override - Future dispose() async { - await state.valueOrNull?.stop(); - super.dispose(); + ref.onDispose(accountServer.stop); + yield accountServer; } } @@ -75,16 +57,20 @@ class _Args extends Equatable { required this.multiAuthChangeNotifier, required this.settingChangeNotifier, required this.currentConversationId, + required this.accountKeyValue, + required this.cryptoKeyValue, }); - final Database? database; - final String? userId; - final String? sessionId; - final String? identityNumber; - final String? privateKey; + final Database database; + final String userId; + final String sessionId; + final String identityNumber; + final String privateKey; final MultiAuthStateNotifier multiAuthChangeNotifier; final SettingChangeNotifier settingChangeNotifier; final GetCurrentConversationId currentConversationId; + final AccountKeyValue accountKeyValue; + final CryptoKeyValue cryptoKeyValue; @override List get props => [ @@ -96,6 +82,8 @@ class _Args extends Equatable { multiAuthChangeNotifier, settingChangeNotifier, currentConversationId, + accountKeyValue, + cryptoKeyValue, ]; } @@ -104,9 +92,12 @@ final Provider _currentConversationIdProvider = (ref) => () => ref.read(currentConversationIdProvider), ); -final _argsProvider = Provider.autoDispose((ref) { +final _argsProvider = FutureProvider.autoDispose((ref) async { final database = ref.watch(databaseProvider.select((value) => value.valueOrNull)); + if (database == null) { + return null; + } final (userId, sessionId, identityNumber, privateKey) = ref.watch(authProvider.select((value) => ( value?.account.userId, @@ -114,10 +105,23 @@ final _argsProvider = Provider.autoDispose((ref) { value?.account.identityNumber, value?.privateKey, ))); + if (userId == null || + sessionId == null || + identityNumber == null || + privateKey == null) { + return null; + } final multiAuthChangeNotifier = ref.watch(multiAuthStateNotifierProvider.notifier); final settingChangeNotifier = ref.watch(settingProvider); final currentConversationId = ref.read(_currentConversationIdProvider); + final accountKeyValue = + await ref.watch(currentAccountKeyValueProvider.future); + final cryptoKeyValue = + await ref.watch(cryptoKeyValueProvider(identityNumber).future); + if (accountKeyValue == null) { + return null; + } return _Args( database: database, @@ -128,30 +132,12 @@ final _argsProvider = Provider.autoDispose((ref) { multiAuthChangeNotifier: multiAuthChangeNotifier, settingChangeNotifier: settingChangeNotifier, currentConversationId: currentConversationId, + accountKeyValue: accountKeyValue, + cryptoKeyValue: cryptoKeyValue, ); }); -final accountServerProvider = StateNotifierProvider.autoDispose< - AccountServerOpener, AsyncValue>((ref) { - final args = ref.watch(_argsProvider); - - if (args.database == null) return AccountServerOpener(); - if (args.userId == null || - args.sessionId == null || - args.identityNumber == null || - args.privateKey == null) { - w('[accountServerProvider] Account not ready'); - return AccountServerOpener(); - } - - return AccountServerOpener.open( - multiAuthChangeNotifier: args.multiAuthChangeNotifier, - settingChangeNotifier: args.settingChangeNotifier, - database: args.database!, - userId: args.userId!, - sessionId: args.sessionId!, - identityNumber: args.identityNumber!, - privateKey: args.privateKey!, - currentConversationId: args.currentConversationId, - ); -}); +final accountServerProvider = + StreamNotifierProvider.autoDispose( + AccountServerOpener._, +); diff --git a/lib/ui/provider/hive_key_value_provider.dart b/lib/ui/provider/hive_key_value_provider.dart new file mode 100644 index 0000000000..78889ad5e3 --- /dev/null +++ b/lib/ui/provider/hive_key_value_provider.dart @@ -0,0 +1,36 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../account/account_key_value.dart'; +import '../../crypto/crypto_key_value.dart'; +import '../../utils/hive_key_values.dart'; +import 'multi_auth_provider.dart'; + +AutoDisposeFutureProviderFamily + _createHiveKeyValueProvider( + T Function() create, +) => + AutoDisposeFutureProviderFamily( + (ref, identityNumber) async { + final keyValue = create(); + ref.onDispose(keyValue.dispose); + await keyValue.init(identityNumber); + return keyValue; + }, + ); + +final cryptoKeyValueProvider = _createHiveKeyValueProvider(CryptoKeyValue.new); + +final accountKeyValueProvider = + _createHiveKeyValueProvider(AccountKeyValue.new); + +final currentAccountKeyValueProvider = + FutureProvider.autoDispose( + (ref) async { + final identityNumber = + ref.watch(authAccountProvider.select((value) => value?.identityNumber)); + if (identityNumber == null) { + return null; + } + return ref.watch(accountKeyValueProvider(identityNumber).future); + }, +); diff --git a/lib/ui/provider/multi_auth_provider.dart b/lib/ui/provider/multi_auth_provider.dart index bd6c9ce21b..6f9b04599d 100644 --- a/lib/ui/provider/multi_auth_provider.dart +++ b/lib/ui/provider/multi_auth_provider.dart @@ -101,8 +101,16 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { } void signOut() { + final currentUserId = state.activeUserId; + if (currentUserId == null) { + w('signOut: currentUserId is null'); + state = MultiAuthState(); + return; + } + i('sign out: $currentUserId'); if (state.auths.isEmpty) return; - final auths = state.auths.toList()..remove(state.current); + final auths = state.auths.toList() + ..removeWhere((element) => element.userId == currentUserId); final activeUserId = auths.lastOrNull?.userId; state = MultiAuthState(auths: auths, activeUserId: activeUserId); } @@ -110,7 +118,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { @override @protected set state(MultiAuthState value) { - final hydratedJson = toHydratedJson(state.toJson()); + final hydratedJson = toHydratedJson(value.toJson()); HydratedBloc.storage.write(_kMultiAuthCubitKey, hydratedJson); super.state = value; } @@ -131,10 +139,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { const _kMultiAuthCubitKey = 'MultiAuthCubit'; final multiAuthStateNotifierProvider = - StateNotifierProvider.autoDispose( - (ref) { - ref.keepAlive(); - + StateNotifierProvider((ref) { final oldJson = HydratedBloc.storage.read(_kMultiAuthCubitKey); if (oldJson != null) { final multiAuthState = fromHydratedJson(oldJson, MultiAuthState.fromJson); @@ -142,7 +147,6 @@ final multiAuthStateNotifierProvider = return MultiAuthStateNotifier(MultiAuthState()); } - d('read multi auth state from storage: $multiAuthState'); return MultiAuthStateNotifier(multiAuthState); } diff --git a/lib/ui/setting/setting_page.dart b/lib/ui/setting/setting_page.dart index 6dae01b121..a91aaf320b 100644 --- a/lib/ui/setting/setting_page.dart +++ b/lib/ui/setting/setting_page.dart @@ -5,7 +5,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import '../../account/account_key_value.dart'; import '../../constants/resources.dart'; import '../../utils/app_lifecycle.dart'; import '../../utils/extension/extension.dart'; @@ -78,7 +77,7 @@ class SettingPage extends HookConsumerWidget { children: [ if (Platform.isIOS && userHasPin && - AccountKeyValue.instance.primarySessionId == null) + context.accountServer.loginByPhoneNumber) _Item( leadingAssetName: Resources.assetsImagesAccountSvg, diff --git a/lib/utils/hive_key_values.dart b/lib/utils/hive_key_values.dart index 5d44e2eec3..75c95f2dbd 100644 --- a/lib/utils/hive_key_values.dart +++ b/lib/utils/hive_key_values.dart @@ -3,22 +3,19 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; +import 'package:mixin_logger/mixin_logger.dart'; import 'package:path/path.dart' as p; -import '../account/account_key_value.dart'; import '../account/scam_warning_key_value.dart'; import '../account/security_key_value.dart'; import '../account/session_key_value.dart'; import '../account/show_pin_message_key_value.dart'; -import '../crypto/crypto_key_value.dart'; import '../crypto/privacy_key_value.dart'; import 'attachment/download_key_value.dart'; import 'file.dart'; Future initKeyValues(String identityNumber) => Future.wait([ PrivacyKeyValue.instance.init(identityNumber), - CryptoKeyValue.instance.init(identityNumber), - AccountKeyValue.instance.init(identityNumber), ShowPinMessageKeyValue.instance.init(identityNumber), ScamWarningKeyValue.instance.init(identityNumber), DownloadKeyValue.instance.init(identityNumber), @@ -28,8 +25,6 @@ Future initKeyValues(String identityNumber) => Future.wait([ Future clearKeyValues() => Future.wait([ PrivacyKeyValue.instance.delete(), - CryptoKeyValue.instance.delete(), - AccountKeyValue.instance.delete(), ShowPinMessageKeyValue.instance.delete(), ScamWarningKeyValue.instance.delete(), DownloadKeyValue.instance.delete(), @@ -37,6 +32,15 @@ Future clearKeyValues() => Future.wait([ SecurityKeyValue.instance.delete(), ]); +Future disposeKeyValues() => Future.wait([ + PrivacyKeyValue.instance.dispose(), + ShowPinMessageKeyValue.instance.dispose(), + ScamWarningKeyValue.instance.dispose(), + DownloadKeyValue.instance.dispose(), + SessionKeyValue.instance.dispose(), + SecurityKeyValue.instance.dispose(), + ]); + abstract class HiveKeyValue { HiveKeyValue(this._boxName); @@ -44,6 +48,8 @@ abstract class HiveKeyValue { late Box box; bool _hasInit = false; + String? _identityNumber; + Future init(String identityNumber) async { if (_hasInit) { return; @@ -65,9 +71,20 @@ abstract class HiveKeyValue { Hive.init(directory.absolute.path); } box = await Hive.openBox(_boxName); + i('HiveKeyValue: open $_boxName'); + _identityNumber = identityNumber; _hasInit = true; } + Future dispose() async { + if (!_hasInit) { + return; + } + i('HiveKeyValue: dispose $_boxName $_identityNumber'); + await box.close(); + _hasInit = false; + } + Future delete() async { if (!_hasInit) return; try { diff --git a/lib/widgets/sticker_page/emoji_page.dart b/lib/widgets/sticker_page/emoji_page.dart index 42a7516f9d..b65b7c4455 100644 --- a/lib/widgets/sticker_page/emoji_page.dart +++ b/lib/widgets/sticker_page/emoji_page.dart @@ -6,8 +6,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import '../../account/account_key_value.dart'; import '../../constants/resources.dart'; +import '../../ui/provider/hive_key_value_provider.dart'; import '../../utils/emoji.dart'; import '../../utils/extension/extension.dart'; import '../interactive_decorated_box.dart'; @@ -69,8 +69,12 @@ class _EmojiPageBody extends HookConsumerWidget { } }, [layoutWidth]); - final recentUsedEmoji = - useMemoized(() => AccountKeyValue.instance.recentUsedEmoji); + final accountKeyValue = + ref.watch(currentAccountKeyValueProvider).valueOrNull; + final recentUsedEmoji = useMemoized( + () => accountKeyValue?.recentUsedEmoji ?? [], + [accountKeyValue], + ); final groupedEmojis = useMemoized( () => [ @@ -321,16 +325,16 @@ class _EmojiGroupTitle extends StatelessWidget { ); } -class _EmojiItem extends StatelessWidget { +class _EmojiItem extends ConsumerWidget { const _EmojiItem({required this.emoji}); final String emoji; @override - Widget build(BuildContext context) => Padding( + Widget build(BuildContext context, WidgetRef ref) => Padding( padding: const EdgeInsets.all(2), child: InteractiveDecoratedBox( - onTap: () { + onTap: () async { final textController = context.read(); final textEditingValue = textController.value; final selection = textEditingValue.selection; @@ -345,7 +349,9 @@ class _EmojiItem extends StatelessWidget { textController.value = collapsedTextEditingValue.replaced(selection, emoji); } - AccountKeyValue.instance.onEmojiUsed(emoji); + final accountKeyValue = + await ref.read(currentAccountKeyValueProvider.future); + accountKeyValue?.onEmojiUsed(emoji); }, hoveringDecoration: BoxDecoration( color: context.dynamicColor( diff --git a/lib/widgets/sticker_page/sticker_page.dart b/lib/widgets/sticker_page/sticker_page.dart index 37bc80ca8c..967bf0a8cf 100644 --- a/lib/widgets/sticker_page/sticker_page.dart +++ b/lib/widgets/sticker_page/sticker_page.dart @@ -4,12 +4,12 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import '../../account/account_key_value.dart'; import '../../bloc/bloc_converter.dart'; import '../../constants/resources.dart'; import '../../db/database_event_bus.dart'; import '../../db/mixin_database.dart'; import '../../ui/provider/conversation_provider.dart'; +import '../../ui/provider/hive_key_value_provider.dart'; import '../../utils/extension/extension.dart'; import '../../utils/hook.dart'; import '../automatic_keep_alive_client_widget.dart'; @@ -299,7 +299,9 @@ class _StickerAlbumBar extends HookConsumerWidget { if (tabController.index != 0) return; HoverOverlay.forceHidden(context); - AccountKeyValue.instance.hasNewAlbum = false; + final accountKeyValue = + await ref.read(currentAccountKeyValueProvider.future); + accountKeyValue?.hasNewAlbum = false; setPreviousIndex(); if (!(await showStickerStorePageDialog(context))) return; @@ -361,13 +363,16 @@ class _StickerAlbumBarItem extends StatelessWidget { child: _StickerGroupIconHoverContainer( child: Center( child: Center( - child: Builder( - builder: (context) { + child: Consumer( + builder: (context, ref, child) { final presetStickerAlbum = { - PresetStickerGroup.store: - AccountKeyValue.instance.hasNewAlbum - ? Resources.assetsImagesStickerStoreRedDotSvg - : Resources.assetsImagesStickerStoreSvg, + PresetStickerGroup.store: ref + .watch(currentAccountKeyValueProvider) + .valueOrNull + ?.hasNewAlbum ?? + false + ? Resources.assetsImagesStickerStoreRedDotSvg + : Resources.assetsImagesStickerStoreSvg, PresetStickerGroup.emoji: Resources.assetsImagesEmojiStickerSvg, PresetStickerGroup.recent: diff --git a/lib/workers/decrypt_message.dart b/lib/workers/decrypt_message.dart index 026ad3acd0..5f3640cc4e 100644 --- a/lib/workers/decrypt_message.dart +++ b/lib/workers/decrypt_message.dart @@ -19,6 +19,7 @@ import '../blaze/vo/system_conversation_message.dart'; import '../blaze/vo/system_user_message.dart'; import '../blaze/vo/transcript_minimal.dart'; import '../constants/constants.dart'; +import '../crypto/crypto_key_value.dart'; import '../crypto/encrypted/encrypted_protocol.dart'; import '../crypto/signal/ratchet_status.dart'; import '../crypto/signal/signal_database.dart'; @@ -1244,8 +1245,8 @@ class DecryptMessage extends Injector { if (count.preKeyCount >= preKeyMinNum) { return; } - final bm = createSyncSignalKeys( - createSyncSignalKeysParam(await generateKeys(_signalDatabase))); + final bm = createSyncSignalKeys(createSyncSignalKeysParam( + await generateKeys(_signalDatabase, CryptoKeyValue()))); final result = await _sender.signalKeysChannel(bm); if (result == null) { i('Registering new pre keys...'); From 50d0ea3ff8919edf46ac46f14e7152afffc3aedb Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 30 Oct 2023 09:48:18 +0800 Subject: [PATCH 07/44] fix --- lib/account/account_server.dart | 8 ++++---- lib/app.dart | 1 + lib/crypto/signal/signal_database.dart | 3 +++ lib/ui/provider/database_provider.dart | 2 +- lib/ui/provider/hive_key_value_provider.dart | 4 ++-- lib/ui/provider/multi_auth_provider.dart | 3 ++- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 1413327130..3d2edca4b7 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -62,7 +62,7 @@ class AccountServer { required this.accountKeyValue, required this.cryptoKeyValue, }) { - i('AccountServer init'); + i('AccountServer init: ${database.hashCode}'); } static String? sid; @@ -234,7 +234,7 @@ class AccountServer { ); jobSubscribers ..add(exitReceivePort.listen((message) { - w('worker isolate service exited. $message'); + w('worker isolate service exited($identityNumber). $message'); _connectedStateBehaviorSubject.add(ConnectedState.disconnected); })) ..add(errorReceivePort.listen((error) { @@ -336,8 +336,8 @@ class AccountServer { try { await signalDatabase?.clear(); await signalDatabase?.close(); - } catch (_) { - // ignore closed database error + } catch (error, stacktrace) { + e('signOutAndClear signalDatabase error: $error $stacktrace'); } try { diff --git a/lib/app.dart b/lib/app.dart index c64695605e..72010db0d9 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -120,6 +120,7 @@ class _Providers extends HookConsumerWidget { return MultiBlocProvider( providers: [ BlocProvider( + key: ValueKey(accountServer), create: (BuildContext context) => ConversationListBloc( ref.read(slideCategoryStateProvider.notifier), accountServer.database, diff --git a/lib/crypto/signal/signal_database.dart b/lib/crypto/signal/signal_database.dart index b43a2177bf..f967c09484 100644 --- a/lib/crypto/signal/signal_database.dart +++ b/lib/crypto/signal/signal_database.dart @@ -42,6 +42,7 @@ class SignalDatabase extends _$SignalDatabase { for (final file in files) { try { if (file.existsSync()) { + i('remove legacy signal database: ${file.path}'); await file.delete(); } } catch (error, stacktrace) { @@ -79,6 +80,7 @@ class SignalDatabase extends _$SignalDatabase { if (file.existsSync()) { await file.copy(newLocation); } + i('migrate legacy signal database: ${file.path}'); } catch (error, stacktrace) { e('_migrationLegacySignalDatabaseIfNecessary ${file.path} error: $error, stacktrace: $stacktrace'); hasError = true; @@ -101,6 +103,7 @@ class SignalDatabase extends _$SignalDatabase { required bool openForLogin, required bool fromMainIsolate, }) async { + i('connect to signal database: $identityNumber'); if (openForLogin) { // delete old database file await _removeLegacySignalDatabase(); diff --git a/lib/ui/provider/database_provider.dart b/lib/ui/provider/database_provider.dart index 1f3c3af89f..f8aa37594b 100644 --- a/lib/ui/provider/database_provider.dart +++ b/lib/ui/provider/database_provider.dart @@ -38,7 +38,7 @@ class DatabaseOpener extends DistinctStateNotifier> { final Lock _lock = Lock(); Future open() => _lock.synchronized(() async { - i('connect to database: $identityNumber'); + d('connect to database: $identityNumber'); if (state.hasValue) { e('database already opened'); return; diff --git a/lib/ui/provider/hive_key_value_provider.dart b/lib/ui/provider/hive_key_value_provider.dart index 78889ad5e3..5568329549 100644 --- a/lib/ui/provider/hive_key_value_provider.dart +++ b/lib/ui/provider/hive_key_value_provider.dart @@ -5,11 +5,11 @@ import '../../crypto/crypto_key_value.dart'; import '../../utils/hive_key_values.dart'; import 'multi_auth_provider.dart'; -AutoDisposeFutureProviderFamily +FutureProviderFamily _createHiveKeyValueProvider( T Function() create, ) => - AutoDisposeFutureProviderFamily( + FutureProviderFamily( (ref, identityNumber) async { final keyValue = create(); ref.onDispose(keyValue.dispose); diff --git a/lib/ui/provider/multi_auth_provider.dart b/lib/ui/provider/multi_auth_provider.dart index 6f9b04599d..11aeacf785 100644 --- a/lib/ui/provider/multi_auth_provider.dart +++ b/lib/ui/provider/multi_auth_provider.dart @@ -97,7 +97,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { account: account, privateKey: state.auths[index].privateKey, ); - state = MultiAuthState(auths: auths); + state = MultiAuthState(auths: auths, activeUserId: state.activeUserId); } void signOut() { @@ -129,6 +129,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { e('failed to active, no account exist for id: $userId'); return; } + i('active account: $userId'); state = MultiAuthState( auths: state.auths, activeUserId: userId, From 900667718b8ec4e548cfae9e0551f38da41d35db Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:05:47 +0800 Subject: [PATCH 08/44] fix network status --- lib/account/account_server.dart | 2 ++ lib/ui/home/conversation/network_status.dart | 7 ++----- lib/ui/home/home.dart | 11 +++-------- lib/ui/provider/account_server_provider.dart | 11 +++++++++++ lib/utils/mixin_api_client.dart | 1 + 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 3d2edca4b7..c16bd01c37 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -231,6 +231,7 @@ class AccountServer { errorsAreFatal: false, onExit: exitReceivePort.sendPort, onError: errorReceivePort.sendPort, + debugName: 'message_process_isolate_$identityNumber', ); jobSubscribers ..add(exitReceivePort.listen((message) { @@ -259,6 +260,7 @@ class AccountServer { d('message process service ready'); break; case WorkerIsolateEventType.onBlazeConnectStateChanged: + d('blaze connect state changed: ${event.argument}'); _connectedStateBehaviorSubject.add(event.argument as ConnectedState); break; case WorkerIsolateEventType.onApiRequestedError: diff --git a/lib/ui/home/conversation/network_status.dart b/lib/ui/home/conversation/network_status.dart index 3862308434..8681fc3647 100644 --- a/lib/ui/home/conversation/network_status.dart +++ b/lib/ui/home/conversation/network_status.dart @@ -9,18 +9,15 @@ import '../../../blaze/blaze.dart'; import '../../../constants/resources.dart'; import '../../../utils/extension/extension.dart'; import '../../../utils/file.dart'; -import '../../../utils/hook.dart'; import '../../../utils/uri_utils.dart'; +import '../../provider/account_server_provider.dart'; class NetworkStatus extends HookConsumerWidget { const NetworkStatus({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final connectedState = useMemoizedStream( - () => context.accountServer.connectedStateStream.distinct(), - initialData: ConnectedState.connecting) - .requireData; + final connectedState = ref.watch(blazeConnectedStateProvider).value; final hasDisconnectedBefore = useRef(false); diff --git a/lib/ui/home/home.dart b/lib/ui/home/home.dart index 068941f781..32edc752ff 100644 --- a/lib/ui/home/home.dart +++ b/lib/ui/home/home.dart @@ -9,7 +9,6 @@ import '../../blaze/blaze.dart'; import '../../utils/audio_message_player/audio_message_service.dart'; import '../../utils/device_transfer/device_transfer_widget.dart'; import '../../utils/extension/extension.dart'; -import '../../utils/hook.dart'; import '../../utils/platform.dart'; import '../../utils/system/text_input.dart'; import '../../widgets/automatic_keep_alive_client_widget.dart'; @@ -17,13 +16,13 @@ import '../../widgets/dialog.dart'; import '../../widgets/empty.dart'; import '../../widgets/protocol_handler.dart'; import '../../widgets/toast.dart'; +import '../provider/account_server_provider.dart'; import '../provider/conversation_provider.dart'; import '../provider/multi_auth_provider.dart'; import '../provider/responsive_navigator_provider.dart'; import '../provider/setting_provider.dart'; import '../provider/slide_category_provider.dart'; import '../setting/setting_page.dart'; - import 'command_palette_wrapper.dart'; import 'conversation/conversation_hotkey.dart'; import 'conversation/conversation_page.dart'; @@ -48,12 +47,8 @@ class HomePage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final localTimeError = useMemoizedStream( - () => context.accountServer.connectedStateStream - .map((event) => event == ConnectedState.hasLocalTimeError) - .distinct(), - keys: [context.accountServer]).data ?? - false; + final localTimeError = ref.watch(blazeConnectedStateProvider + .select((value) => value.value == ConnectedState.hasLocalTimeError)); final isEmptyUserName = ref.watch(authAccountProvider .select((value) => value?.fullName?.isEmpty ?? true)); diff --git a/lib/ui/provider/account_server_provider.dart b/lib/ui/provider/account_server_provider.dart index 0c4f1b0eb6..262e04dd5e 100644 --- a/lib/ui/provider/account_server_provider.dart +++ b/lib/ui/provider/account_server_provider.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../account/account_key_value.dart'; import '../../account/account_server.dart'; +import '../../blaze/blaze.dart'; import '../../crypto/crypto_key_value.dart'; import '../../db/database.dart'; import 'conversation_provider.dart'; @@ -141,3 +142,13 @@ final accountServerProvider = StreamNotifierProvider.autoDispose( AccountServerOpener._, ); + +final blazeConnectedStateProvider = + StreamProvider.autoDispose((ref) { + final accountServer = + ref.watch(accountServerProvider.select((value) => value.valueOrNull)); + if (accountServer == null) { + return const Stream.empty(); + } + return accountServer.connectedStateStream; +}); diff --git a/lib/utils/mixin_api_client.dart b/lib/utils/mixin_api_client.dart index 396b4b559d..4dc5201d69 100644 --- a/lib/utils/mixin_api_client.dart +++ b/lib/utils/mixin_api_client.dart @@ -71,6 +71,7 @@ Client _createClient({ 'requestTimeStamp = ${requestTimeStamp?.outputFormat()} ' 'serverTimeStamp = ${serverTimeStamp?.outputFormat()} ' 'now = ${DateTime.now().outputFormat()}'); + w('error: ${e.message} ${e.stackTrace}'); handler.next(e); }, ), From ef306bbd522ee0c58180636a1986ab3f3bf4d53b Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:32:51 +0800 Subject: [PATCH 09/44] fix swap account --- lib/account/account_server.dart | 12 ++++----- lib/account/notification_service.dart | 25 +++++++++---------- lib/app.dart | 20 ++++++++------- lib/ui/home/conversation/network_status.dart | 2 +- lib/ui/home/home.dart | 2 +- lib/ui/landing/landing.dart | 2 +- .../account_server_provider.dart | 20 +++++++-------- lib/ui/provider/conversation_provider.dart | 2 +- lib/utils/extension/extension.dart | 2 +- lib/utils/local_notification_center.dart | 8 ++++++ lib/widgets/auth.dart | 2 +- lib/widgets/window/menus.dart | 2 +- 12 files changed, 53 insertions(+), 46 deletions(-) rename lib/ui/provider/{ => account}/account_server_provider.dart (91%) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index c16bd01c37..2f41e4adec 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -29,7 +29,7 @@ import '../db/extension/job.dart'; import '../db/mixin_database.dart' as db; import '../enum/encrypt_category.dart'; import '../enum/message_category.dart'; -import '../ui/provider/account_server_provider.dart'; +import '../ui/provider/account/account_server_provider.dart'; import '../ui/provider/multi_auth_provider.dart'; import '../ui/provider/setting_provider.dart'; import '../utils/app_lifecycle.dart'; @@ -61,15 +61,10 @@ class AccountServer { required this.ref, required this.accountKeyValue, required this.cryptoKeyValue, - }) { - i('AccountServer init: ${database.hashCode}'); - } + }); static String? sid; - set language(String language) => - client.dio.options.headers['Accept-Language'] = language; - final MultiAuthStateNotifier multiAuthNotifier; final SettingChangeNotifier settingChangeNotifier; final Database database; @@ -92,6 +87,9 @@ class AccountServer { String privateKey, ) async { if (sid == sessionId) return; + + i('AccountServer init: $identityNumber ${database.hashCode}'); + sid = sessionId; this.userId = userId; diff --git a/lib/account/notification_service.dart b/lib/account/notification_service.dart index 0d80acf7c9..bee0217e0f 100644 --- a/lib/account/notification_service.dart +++ b/lib/account/notification_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:stream_transform/stream_transform.dart'; @@ -9,7 +10,6 @@ import '../db/database_event_bus.dart'; import '../db/extension/conversation.dart'; import '../enum/message_category.dart'; import '../generated/l10n.dart'; - import '../ui/provider/conversation_provider.dart'; import '../ui/provider/mention_cache_provider.dart'; import '../ui/provider/slide_category_provider.dart'; @@ -22,12 +22,15 @@ import '../utils/message_optimize.dart'; import '../utils/reg_exp_utils.dart'; import '../widgets/message/item/pin_message.dart'; import '../widgets/message/item/system_message.dart'; +import 'account_server.dart'; const _keyConversationId = 'conversationId'; class NotificationService { NotificationService({ required BuildContext context, + required AccountServer accountServer, + required WidgetRef ref, }) { streamSubscriptions ..add(DataBaseEventBus.instance.notificationMessageStream @@ -41,31 +44,28 @@ class NotificationService { })) ..add(DataBaseEventBus.instance.notificationMessageStream .where((event) => event.type != MessageCategory.messageRecall) - .where((event) => event.senderId != context.accountServer.userId) + .where((event) => event.senderId != accountServer.userId) .where((event) => event.createdAt != null && event.createdAt! .isAfter(DateTime.now().subtract(const Duration(minutes: 2)))) .where((event) { if (isAppActive) { - final conversationState = - context.providerContainer.read(conversationProvider); + final conversationState = ref.read(conversationProvider); return event.conversationId != (conversationState?.conversationId ?? conversationState?.conversation?.conversationId); } return true; }) - .asyncMapBuffer((event) => context.database.messageDao + .asyncMapBuffer((event) => accountServer.database.messageDao .notificationMessage(event.map((e) => e.messageId).toList()) .get()) .expand((event) => event) .asyncWhere((event) async { - final account = context.account!; - bool mentionedCurrentUser() => mentionNumberRegExp .allMatchesAndSort(event.content ?? '') - .any((element) => element[1] == account.identityNumber); + .any((element) => element[1] == accountServer.identityNumber); // mention current user if (event.type.isText && mentionedCurrentUser()) return true; @@ -75,7 +75,7 @@ class NotificationService { final json = await jsonDecodeWithIsolate(event.quoteContent ?? '') ?? {}; // ignore: avoid_dynamic_calls - return json['user_id'] == account.userId; + return json['user_id'] == accountServer.userId; } catch (_) { // json decode failed return false; @@ -98,15 +98,14 @@ class NotificationService { String? body; if (context.settingChangeNotifier.messagePreview) { - final mentionCache = - context.providerContainer.read(mentionCacheProvider); + final mentionCache = ref.read(mentionCacheProvider); if (event.type == MessageCategory.systemConversation) { body = generateSystemText( actionName: event.actionName, participantUserId: event.participantUserId, senderId: event.senderId, - currentUserId: context.accountServer.userId, + currentUserId: accountServer.userId, participantFullName: event.participantFullName, senderFullName: event.senderFullName, expireIn: int.tryParse(event.content ?? '0'), @@ -174,7 +173,7 @@ class NotificationService { (event) { i('select notification $event'); - context.providerContainer + ref .read(slideCategoryStateProvider.notifier) .switchToChatsIfSettings(); diff --git a/lib/app.dart b/lib/app.dart index 72010db0d9..f42c7665fa 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -19,7 +19,8 @@ import 'ui/home/conversation/conversation_page.dart'; import 'ui/home/home.dart'; import 'ui/landing/landing.dart'; import 'ui/landing/landing_failed.dart'; -import 'ui/provider/account_server_provider.dart'; +import 'ui/landing/landing_initialize.dart'; +import 'ui/provider/account/account_server_provider.dart'; import 'ui/provider/database_provider.dart'; import 'ui/provider/hive_key_value_provider.dart'; import 'ui/provider/mention_cache_provider.dart'; @@ -55,7 +56,7 @@ class App extends HookConsumerWidget { Widget child; if (authState == null) { - child = const _App(home: LandingPage()); + child = const _App(home: AppInitializingPage()); } else { child = _LoginApp(authState: authState); } @@ -72,9 +73,10 @@ class _LoginApp extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final database = ref.watch(databaseProvider); + final accountServer = ref.watch(accountServerProvider); - if (database.isLoading) { - return const _App(home: LandingPage()); + if (database.isLoading || accountServer.isLoading) { + return const _App(home: AppInitializingPage()); } if (database.hasError) { var error = database.error; @@ -129,7 +131,11 @@ class _Providers extends HookConsumerWidget { ), ], child: Provider( - create: (BuildContext context) => NotificationService(context: context), + create: (BuildContext context) => NotificationService( + context: context, + accountServer: accountServer, + ref: ref, + ), lazy: false, dispose: (_, notificationService) => notificationService.close(), child: PortalProviders(child: app), @@ -177,10 +183,6 @@ class _App extends HookConsumerWidget { ).withFallbackFonts(), themeMode: ref.watch(settingProvider).themeMode, builder: (context, child) { - try { - context.accountServer.language = - Localizations.localeOf(context).languageCode; - } catch (_) {} final mediaQueryData = MediaQuery.of(context); return BrightnessObserver( lightThemeData: lightBrightnessThemeData, diff --git a/lib/ui/home/conversation/network_status.dart b/lib/ui/home/conversation/network_status.dart index 8681fc3647..8e9c119449 100644 --- a/lib/ui/home/conversation/network_status.dart +++ b/lib/ui/home/conversation/network_status.dart @@ -10,7 +10,7 @@ import '../../../constants/resources.dart'; import '../../../utils/extension/extension.dart'; import '../../../utils/file.dart'; import '../../../utils/uri_utils.dart'; -import '../../provider/account_server_provider.dart'; +import '../../provider/account/account_server_provider.dart'; class NetworkStatus extends HookConsumerWidget { const NetworkStatus({super.key}); diff --git a/lib/ui/home/home.dart b/lib/ui/home/home.dart index 32edc752ff..75dc1a625a 100644 --- a/lib/ui/home/home.dart +++ b/lib/ui/home/home.dart @@ -16,7 +16,7 @@ import '../../widgets/dialog.dart'; import '../../widgets/empty.dart'; import '../../widgets/protocol_handler.dart'; import '../../widgets/toast.dart'; -import '../provider/account_server_provider.dart'; +import '../provider/account/account_server_provider.dart'; import '../provider/conversation_provider.dart'; import '../provider/multi_auth_provider.dart'; import '../provider/responsive_navigator_provider.dart'; diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index d1fef859a9..51237b8214 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -12,7 +12,7 @@ import '../../utils/mixin_api_client.dart'; import '../../utils/system/package_info.dart'; import '../../widgets/dialog.dart'; import '../../widgets/toast.dart'; -import '../provider/account_server_provider.dart'; +import '../provider/account/account_server_provider.dart'; import '../provider/hive_key_value_provider.dart'; import 'landing_mobile.dart'; import 'landing_qrcode.dart'; diff --git a/lib/ui/provider/account_server_provider.dart b/lib/ui/provider/account/account_server_provider.dart similarity index 91% rename from lib/ui/provider/account_server_provider.dart rename to lib/ui/provider/account/account_server_provider.dart index 262e04dd5e..718427ebfb 100644 --- a/lib/ui/provider/account_server_provider.dart +++ b/lib/ui/provider/account/account_server_provider.dart @@ -3,16 +3,16 @@ import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import '../../account/account_key_value.dart'; -import '../../account/account_server.dart'; -import '../../blaze/blaze.dart'; -import '../../crypto/crypto_key_value.dart'; -import '../../db/database.dart'; -import 'conversation_provider.dart'; -import 'database_provider.dart'; -import 'hive_key_value_provider.dart'; -import 'multi_auth_provider.dart'; -import 'setting_provider.dart'; +import '../../../account/account_key_value.dart'; +import '../../../account/account_server.dart'; +import '../../../blaze/blaze.dart'; +import '../../../crypto/crypto_key_value.dart'; +import '../../../db/database.dart'; +import '../conversation_provider.dart'; +import '../database_provider.dart'; +import '../hive_key_value_provider.dart'; +import '../multi_auth_provider.dart'; +import '../setting_provider.dart'; typedef GetCurrentConversationId = String? Function(); diff --git a/lib/ui/provider/conversation_provider.dart b/lib/ui/provider/conversation_provider.dart index 982baadaaf..290914ac5d 100644 --- a/lib/ui/provider/conversation_provider.dart +++ b/lib/ui/provider/conversation_provider.dart @@ -20,7 +20,7 @@ import '../../utils/rivepod.dart'; import '../../widgets/toast.dart'; import '../home/bloc/conversation_list_bloc.dart'; import '../home/bloc/subscriber_mixin.dart'; -import 'account_server_provider.dart'; +import 'account/account_server_provider.dart'; import 'is_bot_group_provider.dart'; import 'recent_conversation_provider.dart'; import 'responsive_navigator_provider.dart'; diff --git a/lib/utils/extension/extension.dart b/lib/utils/extension/extension.dart index 51f7bef04e..5f5403d70e 100644 --- a/lib/utils/extension/extension.dart +++ b/lib/utils/extension/extension.dart @@ -24,7 +24,7 @@ import '../../account/account_server.dart'; import '../../db/dao/snapshot_dao.dart'; import '../../db/database.dart'; import '../../generated/l10n.dart'; -import '../../ui/provider/account_server_provider.dart'; +import '../../ui/provider/account/account_server_provider.dart'; import '../../ui/provider/database_provider.dart'; import '../../ui/provider/multi_auth_provider.dart'; import '../../ui/provider/setting_provider.dart'; diff --git a/lib/utils/local_notification_center.dart b/lib/utils/local_notification_center.dart index ad48a1a4d7..396e876c24 100644 --- a/lib/utils/local_notification_center.dart +++ b/lib/utils/local_notification_center.dart @@ -44,6 +44,8 @@ abstract class _NotificationManager { Future dismissByMessageId(String messageId, String conversationId); + Future dismissAll(); + Future requestPermission(); @protected @@ -171,6 +173,9 @@ class _LocalNotificationManager extends _NotificationManager { await flutterLocalNotificationsPlugin.cancel(id); notifications.remove(notification); } + + @override + Future dismissAll() => flutterLocalNotificationsPlugin.cancelAll(); } class _WindowsNotificationManager extends _NotificationManager { @@ -243,6 +248,9 @@ class _WindowsNotificationManager extends _NotificationManager { @override Future requestPermission() async => null; + + @override + Future dismissAll() => WinToast.instance().clear(); } enum NotificationScheme { diff --git a/lib/widgets/auth.dart b/lib/widgets/auth.dart index e48b1a2fab..623d333230 100644 --- a/lib/widgets/auth.dart +++ b/lib/widgets/auth.dart @@ -11,7 +11,7 @@ import 'package:rxdart/rxdart.dart'; import '../account/security_key_value.dart'; import '../constants/resources.dart'; -import '../ui/provider/account_server_provider.dart'; +import '../ui/provider/account/account_server_provider.dart'; import '../utils/app_lifecycle.dart'; import '../utils/authentication.dart'; import '../utils/event_bus.dart'; diff --git a/lib/widgets/window/menus.dart b/lib/widgets/window/menus.dart index f40967047e..06d002f191 100644 --- a/lib/widgets/window/menus.dart +++ b/lib/widgets/window/menus.dart @@ -9,7 +9,7 @@ import 'package:window_manager/window_manager.dart'; import '../../account/security_key_value.dart'; import '../../ui/home/conversation/conversation_hotkey.dart'; -import '../../ui/provider/account_server_provider.dart'; +import '../../ui/provider/account/account_server_provider.dart'; import '../../ui/provider/slide_category_provider.dart'; import '../../utils/device_transfer/device_transfer_dialog.dart'; import '../../utils/event_bus.dart'; From 482516f0a1e4f6ed2043c742d2908d339630b8bc Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:08:07 +0800 Subject: [PATCH 10/44] fix menu --- lib/ui/home/chat/chat_page.dart | 8 +-- lib/ui/provider/menu_handle_provider.dart | 63 +++++++++++++++++++++++ lib/widgets/window/menus.dart | 42 +-------------- 3 files changed, 68 insertions(+), 45 deletions(-) create mode 100644 lib/ui/provider/menu_handle_provider.dart diff --git a/lib/ui/home/chat/chat_page.dart b/lib/ui/home/chat/chat_page.dart index c992c52a0e..900081ce3b 100644 --- a/lib/ui/home/chat/chat_page.dart +++ b/lib/ui/home/chat/chat_page.dart @@ -32,10 +32,10 @@ import '../../../widgets/message/message_bubble.dart'; import '../../../widgets/message/message_day_time.dart'; import '../../../widgets/pin_bubble.dart'; import '../../../widgets/toast.dart'; -import '../../../widgets/window/menus.dart'; import '../../provider/abstract_responsive_navigator.dart'; import '../../provider/conversation_provider.dart'; import '../../provider/mention_cache_provider.dart'; +import '../../provider/menu_handle_provider.dart'; import '../../provider/message_selection_provider.dart'; import '../../provider/pending_jump_message_provider.dart'; import '../bloc/blink_cubit.dart'; @@ -1112,12 +1112,12 @@ class _ChatMenuHandler extends HookConsumerWidget { final conversationId = ref.watch(currentConversationIdProvider); useEffect(() { - final cubit = ref.read(macMenuBarProvider.notifier); + final controller = ref.read(macMenuBarProvider.notifier); if (conversationId == null) return null; final handle = _ConversationHandle(context, conversationId); - Future(() => cubit.attach(handle)); - return () => Future(() => cubit.unAttach(handle)); + Future(() => controller.attach(handle)); + return () => Future(() => controller.unAttach(handle)); }, [conversationId]); return child; diff --git a/lib/ui/provider/menu_handle_provider.dart b/lib/ui/provider/menu_handle_provider.dart new file mode 100644 index 0000000000..1119132219 --- /dev/null +++ b/lib/ui/provider/menu_handle_provider.dart @@ -0,0 +1,63 @@ +import 'dart:io'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../utils/rivepod.dart'; +import 'multi_auth_provider.dart'; + +abstract class ConversationMenuHandle { + Stream get isMuted; + + Stream get isPinned; + + void mute(); + + void unmute(); + + void showSearch(); + + void pin(); + + void unPin(); + + void toggleSideBar(); + + void delete(); +} + +class MacMenuBarStateNotifier + extends DistinctStateNotifier { + MacMenuBarStateNotifier(super.state); + + void attach(ConversationMenuHandle handle) { + if (!Platform.isMacOS) return; + Future(() => state = handle); + } + + void unAttach(ConversationMenuHandle handle) { + if (!Platform.isMacOS) return; + if (state != handle) return; + state = null; + } + + void _clear() { + if (!Platform.isMacOS) return; + state = null; + } +} + +final macMenuBarProvider = + StateNotifierProvider( + (ref) { + // clear state when account changed + ref.listen( + authAccountProvider.select((value) => value?.identityNumber), + (previous, next) { + if (previous != null && next != null) { + ref.notifier._clear(); + } + }, + ); + return MacMenuBarStateNotifier(null); + }, +); diff --git a/lib/widgets/window/menus.dart b/lib/widgets/window/menus.dart index 06d002f191..448184963c 100644 --- a/lib/widgets/window/menus.dart +++ b/lib/widgets/window/menus.dart @@ -10,56 +10,16 @@ import 'package:window_manager/window_manager.dart'; import '../../account/security_key_value.dart'; import '../../ui/home/conversation/conversation_hotkey.dart'; import '../../ui/provider/account/account_server_provider.dart'; +import '../../ui/provider/menu_handle_provider.dart'; import '../../ui/provider/slide_category_provider.dart'; import '../../utils/device_transfer/device_transfer_dialog.dart'; import '../../utils/event_bus.dart'; import '../../utils/extension/extension.dart'; import '../../utils/hook.dart'; -import '../../utils/rivepod.dart'; import '../../utils/uri_utils.dart'; import '../actions/actions.dart'; import '../auth.dart'; -abstract class ConversationMenuHandle { - Stream get isMuted; - - Stream get isPinned; - - void mute(); - - void unmute(); - - void showSearch(); - - void pin(); - - void unPin(); - - void toggleSideBar(); - - void delete(); -} - -class MacMenuBarStateNotifier - extends DistinctStateNotifier { - MacMenuBarStateNotifier(super.state); - - void attach(ConversationMenuHandle handle) { - if (!Platform.isMacOS) return; - Future(() => state = handle); - } - - void unAttach(ConversationMenuHandle handle) { - if (!Platform.isMacOS) return; - if (state != handle) return; - state = null; - } -} - -final macMenuBarProvider = - StateNotifierProvider( - (ref) => MacMenuBarStateNotifier(null)); - class MacosMenuBar extends HookConsumerWidget { const MacosMenuBar({ super.key, From 762552b41f6ce4304f8502c1fe9115ff6e6f2f8a Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 31 Oct 2023 09:22:49 +0800 Subject: [PATCH 11/44] improve --- lib/blaze/blaze.dart | 4 ++-- lib/ui/home/slide_page.dart | 1 + lib/ui/provider/conversation_provider.dart | 10 +++++----- lib/ui/provider/slide_category_provider.dart | 11 ++++++++--- lib/widgets/menu.dart | 11 ++++++++++- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/blaze/blaze.dart b/lib/blaze/blaze.dart index 350ce42b42..66ae1d96aa 100644 --- a/lib/blaze/blaze.dart +++ b/lib/blaze/blaze.dart @@ -330,8 +330,8 @@ class Blaze { await client.accountApi.getMe(); i('http ping'); await connect(); - } catch (e) { - w('ws ping error: $e'); + } catch (e, s) { + w('ws ping error: $e $s'); if (e is MixinApiError && e.error != null && e.error is MixinError && diff --git a/lib/ui/home/slide_page.dart b/lib/ui/home/slide_page.dart index bc61758989..4b153a0f56 100644 --- a/lib/ui/home/slide_page.dart +++ b/lib/ui/home/slide_page.dart @@ -195,6 +195,7 @@ class _MultiAccountPopupButton extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final mas = ref.watch(multiAuthStateNotifierProvider); return ContextMenuPortalEntry( + autofocus: true, buildMenus: () => [ for (final account in mas.auths) _AccountMenuItem( diff --git a/lib/ui/provider/conversation_provider.dart b/lib/ui/provider/conversation_provider.dart index 290914ac5d..d75362cac3 100644 --- a/lib/ui/provider/conversation_provider.dart +++ b/lib/ui/provider/conversation_provider.dart @@ -402,7 +402,7 @@ class _LastConversationNotifier final conversationProvider = StateNotifierProvider.autoDispose< ConversationStateNotifier, ConversationState?>((ref) { - final keepAlive = ref.keepAlive(); + ref.keepAlive(); final accountServerAsync = ref.watch(accountServerProvider); @@ -413,10 +413,10 @@ final conversationProvider = StateNotifierProvider.autoDispose< final responsiveNavigatorNotifier = ref.watch(responsiveNavigatorProvider.notifier); - ref - ..listen(accountServerProvider, (previous, next) => keepAlive.close()) - ..listen(responsiveNavigatorProvider.notifier, - (previous, next) => keepAlive.close()); + ref.listen( + accountServerProvider, + (previous, next) => ref.notifier.unselected(), + ); return ConversationStateNotifier( accountServer: accountServerAsync.requireValue, diff --git a/lib/ui/provider/slide_category_provider.dart b/lib/ui/provider/slide_category_provider.dart index ecd76baa23..bf21de7251 100644 --- a/lib/ui/provider/slide_category_provider.dart +++ b/lib/ui/provider/slide_category_provider.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../utils/rivepod.dart'; +import 'account/account_server_provider.dart'; enum SlideCategoryType { chats, @@ -42,6 +43,10 @@ class SlideCategoryStateNotifier } } -final slideCategoryStateProvider = - StateNotifierProvider( - (ref) => SlideCategoryStateNotifier()); +final slideCategoryStateProvider = StateNotifierProvider.autoDispose< + SlideCategoryStateNotifier, SlideCategoryState>((ref) { + ref.listen(accountServerProvider, (previous, next) { + ref.notifier.switchToChatsIfSettings(); + }); + return SlideCategoryStateNotifier(); +}); diff --git a/lib/widgets/menu.dart b/lib/widgets/menu.dart index a16e813fd6..7af86a4e5f 100644 --- a/lib/widgets/menu.dart +++ b/lib/widgets/menu.dart @@ -126,6 +126,7 @@ class ContextMenuPortalEntry extends HookConsumerWidget { this.enable = true, this.onTap, this.interactive = true, + this.autofocus = false, }); final Widget child; @@ -137,6 +138,8 @@ class ContextMenuPortalEntry extends HookConsumerWidget { /// Whether right click or long press to show menu final bool interactive; + final bool autofocus; + @override Widget build(BuildContext context, WidgetRef ref) { final offsetCubit = useBloc(() => _OffsetCubit(null)); @@ -149,8 +152,13 @@ class ContextMenuPortalEntry extends HookConsumerWidget { converter: (state) => state != null, ); + final node = useFocusScopeNode(); + useEffect(() { showedMenu?.call(visible); + if (visible && autofocus && !node.hasFocus) { + node.requestFocus(); + } }, [visible]); useEffect(() { @@ -195,7 +203,8 @@ class ContextMenuPortalEntry extends HookConsumerWidget { } }, onTap: onTap, - child: Focus( + child: FocusScope( + node: node, onKeyEvent: (node, key) { final show = offset != null && visible; if (show && key.logicalKey == LogicalKeyboardKey.escape) { From 2c413997a4323cd26ce9b5a64ce5c3d09747c408 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 31 Oct 2023 09:49:26 +0800 Subject: [PATCH 12/44] move file --- lib/account/account_server.dart | 2 +- lib/app.dart | 2 +- lib/ui/home/chat/chat_bar.dart | 4 +-- lib/ui/home/chat/chat_page.dart | 2 +- lib/ui/home/chat/input_container.dart | 2 +- .../home/conversation/conversation_list.dart | 2 +- .../unseen_conversation_list.dart | 2 +- lib/ui/home/home.dart | 4 +-- lib/ui/home/route/responsive_navigator.dart | 4 +-- lib/ui/home/slide_page.dart | 2 +- lib/ui/landing/landing_mobile.dart | 2 +- lib/ui/landing/landing_qrcode.dart | 8 +++--- .../account/account_server_provider.dart | 2 +- .../{ => account}/multi_auth_provider.dart | 6 ++--- .../{ => account}/multi_auth_provider.g.dart | 0 lib/ui/provider/conversation_provider.dart | 2 +- lib/ui/provider/database_provider.dart | 2 +- lib/ui/provider/hive_key_value_provider.dart | 2 +- lib/ui/provider/mention_provider.dart | 2 +- lib/ui/provider/menu_handle_provider.dart | 2 +- .../abstract_responsive_navigator.dart | 2 +- .../responsive_navigator_provider.dart | 26 +++++++++---------- lib/ui/setting/account_page.dart | 2 +- lib/ui/setting/edit_profile_page.dart | 2 +- lib/ui/setting/setting_page.dart | 4 +-- lib/ui/setting/storage_page.dart | 2 +- lib/ui/setting/storage_usage_list_page.dart | 2 +- lib/utils/extension/extension.dart | 2 +- lib/widgets/cell.dart | 2 +- 29 files changed, 50 insertions(+), 48 deletions(-) rename lib/ui/provider/{ => account}/multi_auth_provider.dart (97%) rename lib/ui/provider/{ => account}/multi_auth_provider.g.dart (100%) rename lib/ui/provider/{ => navigation}/abstract_responsive_navigator.dart (98%) rename lib/ui/provider/{ => navigation}/responsive_navigator_provider.dart (88%) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 2f41e4adec..d5d8f3bb3c 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -30,7 +30,7 @@ import '../db/mixin_database.dart' as db; import '../enum/encrypt_category.dart'; import '../enum/message_category.dart'; import '../ui/provider/account/account_server_provider.dart'; -import '../ui/provider/multi_auth_provider.dart'; +import '../ui/provider/account/multi_auth_provider.dart'; import '../ui/provider/setting_provider.dart'; import '../utils/app_lifecycle.dart'; import '../utils/attachment/attachment_util.dart'; diff --git a/lib/app.dart b/lib/app.dart index f42c7665fa..dce4bc8925 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -21,10 +21,10 @@ import 'ui/landing/landing.dart'; import 'ui/landing/landing_failed.dart'; import 'ui/landing/landing_initialize.dart'; import 'ui/provider/account/account_server_provider.dart'; +import 'ui/provider/account/multi_auth_provider.dart'; import 'ui/provider/database_provider.dart'; import 'ui/provider/hive_key_value_provider.dart'; import 'ui/provider/mention_cache_provider.dart'; -import 'ui/provider/multi_auth_provider.dart'; import 'ui/provider/setting_provider.dart'; import 'ui/provider/slide_category_provider.dart'; import 'utils/extension/extension.dart'; diff --git a/lib/ui/home/chat/chat_bar.dart b/lib/ui/home/chat/chat_bar.dart index 953942ec7e..5aedd8c0e7 100644 --- a/lib/ui/home/chat/chat_bar.dart +++ b/lib/ui/home/chat/chat_bar.dart @@ -15,10 +15,10 @@ import '../../../widgets/conversation/verified_or_bot_widget.dart'; import '../../../widgets/high_light_text.dart'; import '../../../widgets/interactive_decorated_box.dart'; import '../../../widgets/window/move_window.dart'; -import '../../provider/abstract_responsive_navigator.dart'; import '../../provider/conversation_provider.dart'; import '../../provider/message_selection_provider.dart'; -import '../../provider/responsive_navigator_provider.dart'; +import '../../provider/navigation/abstract_responsive_navigator.dart'; +import '../../provider/navigation/responsive_navigator_provider.dart'; import 'chat_page.dart'; class ChatBar extends HookConsumerWidget { diff --git a/lib/ui/home/chat/chat_page.dart b/lib/ui/home/chat/chat_page.dart index 900081ce3b..c2e1292641 100644 --- a/lib/ui/home/chat/chat_page.dart +++ b/lib/ui/home/chat/chat_page.dart @@ -32,11 +32,11 @@ import '../../../widgets/message/message_bubble.dart'; import '../../../widgets/message/message_day_time.dart'; import '../../../widgets/pin_bubble.dart'; import '../../../widgets/toast.dart'; -import '../../provider/abstract_responsive_navigator.dart'; import '../../provider/conversation_provider.dart'; import '../../provider/mention_cache_provider.dart'; import '../../provider/menu_handle_provider.dart'; import '../../provider/message_selection_provider.dart'; +import '../../provider/navigation/abstract_responsive_navigator.dart'; import '../../provider/pending_jump_message_provider.dart'; import '../bloc/blink_cubit.dart'; import '../bloc/message_bloc.dart'; diff --git a/lib/ui/home/chat/input_container.dart b/lib/ui/home/chat/input_container.dart index f23306bb2c..7b5563b70a 100644 --- a/lib/ui/home/chat/input_container.dart +++ b/lib/ui/home/chat/input_container.dart @@ -42,10 +42,10 @@ import '../../../widgets/sticker_page/bloc/cubit/sticker_albums_cubit.dart'; import '../../../widgets/sticker_page/sticker_page.dart'; import '../../../widgets/toast.dart'; import '../../../widgets/user_selector/conversation_selector.dart'; -import '../../provider/abstract_responsive_navigator.dart'; import '../../provider/conversation_provider.dart'; import '../../provider/mention_cache_provider.dart'; import '../../provider/mention_provider.dart'; +import '../../provider/navigation/abstract_responsive_navigator.dart'; import '../../provider/quote_message_provider.dart'; import '../../provider/recall_message_reedit_provider.dart'; import 'chat_page.dart'; diff --git a/lib/ui/home/conversation/conversation_list.dart b/lib/ui/home/conversation/conversation_list.dart index cfb3cb0d70..78a4f834fd 100644 --- a/lib/ui/home/conversation/conversation_list.dart +++ b/lib/ui/home/conversation/conversation_list.dart @@ -24,7 +24,7 @@ import '../../../widgets/unread_text.dart'; import '../../provider/conversation_provider.dart'; import '../../provider/mention_cache_provider.dart'; import '../../provider/minute_timer_provider.dart'; -import '../../provider/responsive_navigator_provider.dart'; +import '../../provider/navigation/responsive_navigator_provider.dart'; import '../../provider/slide_category_provider.dart'; import '../bloc/conversation_list_bloc.dart'; import 'audio_player_bar.dart'; diff --git a/lib/ui/home/conversation/unseen_conversation_list.dart b/lib/ui/home/conversation/unseen_conversation_list.dart index 98b1d00680..4b0b703857 100644 --- a/lib/ui/home/conversation/unseen_conversation_list.dart +++ b/lib/ui/home/conversation/unseen_conversation_list.dart @@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import '../../provider/conversation_provider.dart'; -import '../../provider/responsive_navigator_provider.dart'; +import '../../provider/navigation/responsive_navigator_provider.dart'; import '../../provider/unseen_conversations_provider.dart'; import 'conversation_list.dart'; import 'menu_wrapper.dart'; diff --git a/lib/ui/home/home.dart b/lib/ui/home/home.dart index 75dc1a625a..811860e201 100644 --- a/lib/ui/home/home.dart +++ b/lib/ui/home/home.dart @@ -17,9 +17,9 @@ import '../../widgets/empty.dart'; import '../../widgets/protocol_handler.dart'; import '../../widgets/toast.dart'; import '../provider/account/account_server_provider.dart'; +import '../provider/account/multi_auth_provider.dart'; import '../provider/conversation_provider.dart'; -import '../provider/multi_auth_provider.dart'; -import '../provider/responsive_navigator_provider.dart'; +import '../provider/navigation/responsive_navigator_provider.dart'; import '../provider/setting_provider.dart'; import '../provider/slide_category_provider.dart'; import '../setting/setting_page.dart'; diff --git a/lib/ui/home/route/responsive_navigator.dart b/lib/ui/home/route/responsive_navigator.dart index 64fc17b77b..f294af62b5 100644 --- a/lib/ui/home/route/responsive_navigator.dart +++ b/lib/ui/home/route/responsive_navigator.dart @@ -5,8 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import '../../provider/abstract_responsive_navigator.dart'; -import '../../provider/responsive_navigator_provider.dart'; +import '../../provider/navigation/abstract_responsive_navigator.dart'; +import '../../provider/navigation/responsive_navigator_provider.dart'; abstract class AbstractResponsiveNavigatorCubit extends Cubit { diff --git a/lib/ui/home/slide_page.dart b/lib/ui/home/slide_page.dart index 4b153a0f56..f21f5014ef 100644 --- a/lib/ui/home/slide_page.dart +++ b/lib/ui/home/slide_page.dart @@ -25,7 +25,7 @@ import '../../widgets/toast.dart'; import '../../widgets/user_selector/conversation_selector.dart'; import '../../widgets/window/move_window.dart'; import '../landing/landing.dart'; -import '../provider/multi_auth_provider.dart'; +import '../provider/account/multi_auth_provider.dart'; import '../provider/setting_provider.dart'; import '../provider/slide_category_provider.dart'; diff --git a/lib/ui/landing/landing_mobile.dart b/lib/ui/landing/landing_mobile.dart index a612485308..2e517b77c6 100644 --- a/lib/ui/landing/landing_mobile.dart +++ b/lib/ui/landing/landing_mobile.dart @@ -25,8 +25,8 @@ import '../../widgets/toast.dart'; import '../../widgets/user/captcha_web_view_dialog.dart'; import '../../widgets/user/phone_number_input.dart'; import '../../widgets/user/verification_dialog.dart'; +import '../provider/account/multi_auth_provider.dart'; import '../provider/hive_key_value_provider.dart'; -import '../provider/multi_auth_provider.dart'; import 'landing.dart'; final _mobileClientProvider = Provider.autoDispose( diff --git a/lib/ui/landing/landing_qrcode.dart b/lib/ui/landing/landing_qrcode.dart index e90cd85571..e897e35403 100644 --- a/lib/ui/landing/landing_qrcode.dart +++ b/lib/ui/landing/landing_qrcode.dart @@ -18,8 +18,8 @@ import '../../utils/mixin_api_client.dart'; import '../../utils/platform.dart'; import '../../utils/system/package_info.dart'; import '../../widgets/qr_code.dart'; +import '../provider/account/multi_auth_provider.dart'; import '../provider/hive_key_value_provider.dart'; -import '../provider/multi_auth_provider.dart'; import 'landing.dart'; import 'landing_initialize.dart'; import 'landing_state.dart'; @@ -142,9 +142,11 @@ class _QrCodeLoginNotifier extends StateNotifier { final privateKey = base64Encode(edKeyPair.privateKey.bytes); - final accountKeyValue = await ref.read(accountKeyValueProvider(identityNumber).future); + final accountKeyValue = + await ref.read(accountKeyValueProvider(identityNumber).future); accountKeyValue.primarySessionId = sessionId; - final cryptoKeyValue = await ref.read(cryptoKeyValueProvider(identityNumber).future); + final cryptoKeyValue = + await ref.read(cryptoKeyValueProvider(identityNumber).future); cryptoKeyValue.localRegistrationId = registrationId; return ( diff --git a/lib/ui/provider/account/account_server_provider.dart b/lib/ui/provider/account/account_server_provider.dart index 718427ebfb..1e03731970 100644 --- a/lib/ui/provider/account/account_server_provider.dart +++ b/lib/ui/provider/account/account_server_provider.dart @@ -11,8 +11,8 @@ import '../../../db/database.dart'; import '../conversation_provider.dart'; import '../database_provider.dart'; import '../hive_key_value_provider.dart'; -import '../multi_auth_provider.dart'; import '../setting_provider.dart'; +import 'multi_auth_provider.dart'; typedef GetCurrentConversationId = String? Function(); diff --git a/lib/ui/provider/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart similarity index 97% rename from lib/ui/provider/multi_auth_provider.dart rename to lib/ui/provider/account/multi_auth_provider.dart index 11aeacf785..92e8381394 100644 --- a/lib/ui/provider/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -6,9 +6,9 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:mixin_logger/mixin_logger.dart'; -import '../../utils/extension/extension.dart'; -import '../../utils/hydrated_bloc.dart'; -import '../../utils/rivepod.dart'; +import '../../../utils/extension/extension.dart'; +import '../../../utils/hydrated_bloc.dart'; +import '../../../utils/rivepod.dart'; part 'multi_auth_provider.g.dart'; diff --git a/lib/ui/provider/multi_auth_provider.g.dart b/lib/ui/provider/account/multi_auth_provider.g.dart similarity index 100% rename from lib/ui/provider/multi_auth_provider.g.dart rename to lib/ui/provider/account/multi_auth_provider.g.dart diff --git a/lib/ui/provider/conversation_provider.dart b/lib/ui/provider/conversation_provider.dart index d75362cac3..e4516fe604 100644 --- a/lib/ui/provider/conversation_provider.dart +++ b/lib/ui/provider/conversation_provider.dart @@ -22,8 +22,8 @@ import '../home/bloc/conversation_list_bloc.dart'; import '../home/bloc/subscriber_mixin.dart'; import 'account/account_server_provider.dart'; import 'is_bot_group_provider.dart'; +import 'navigation/responsive_navigator_provider.dart'; import 'recent_conversation_provider.dart'; -import 'responsive_navigator_provider.dart'; class ConversationState extends Equatable { const ConversationState({ diff --git a/lib/ui/provider/database_provider.dart b/lib/ui/provider/database_provider.dart index f8aa37594b..125cbda07f 100644 --- a/lib/ui/provider/database_provider.dart +++ b/lib/ui/provider/database_provider.dart @@ -6,7 +6,7 @@ import '../../db/fts_database.dart'; import '../../db/mixin_database.dart'; import '../../utils/rivepod.dart'; import '../../utils/synchronized.dart'; -import 'multi_auth_provider.dart'; +import 'account/multi_auth_provider.dart'; import 'slide_category_provider.dart'; final databaseProvider = diff --git a/lib/ui/provider/hive_key_value_provider.dart b/lib/ui/provider/hive_key_value_provider.dart index 5568329549..9d95286ba8 100644 --- a/lib/ui/provider/hive_key_value_provider.dart +++ b/lib/ui/provider/hive_key_value_provider.dart @@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../account/account_key_value.dart'; import '../../crypto/crypto_key_value.dart'; import '../../utils/hive_key_values.dart'; -import 'multi_auth_provider.dart'; +import 'account/multi_auth_provider.dart'; FutureProviderFamily _createHiveKeyValueProvider( diff --git a/lib/ui/provider/mention_provider.dart b/lib/ui/provider/mention_provider.dart index a1a5de377f..fe27dc7b3e 100644 --- a/lib/ui/provider/mention_provider.dart +++ b/lib/ui/provider/mention_provider.dart @@ -14,9 +14,9 @@ import '../../utils/reg_exp_utils.dart'; import '../../utils/rivepod.dart'; import '../../widgets/mention_panel.dart'; import '../home/bloc/subscriber_mixin.dart'; +import 'account/multi_auth_provider.dart'; import 'conversation_provider.dart'; import 'database_provider.dart'; -import 'multi_auth_provider.dart'; class MentionState extends Equatable { const MentionState({ diff --git a/lib/ui/provider/menu_handle_provider.dart b/lib/ui/provider/menu_handle_provider.dart index 1119132219..21dd74fa86 100644 --- a/lib/ui/provider/menu_handle_provider.dart +++ b/lib/ui/provider/menu_handle_provider.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../utils/rivepod.dart'; -import 'multi_auth_provider.dart'; +import 'account/multi_auth_provider.dart'; abstract class ConversationMenuHandle { Stream get isMuted; diff --git a/lib/ui/provider/abstract_responsive_navigator.dart b/lib/ui/provider/navigation/abstract_responsive_navigator.dart similarity index 98% rename from lib/ui/provider/abstract_responsive_navigator.dart rename to lib/ui/provider/navigation/abstract_responsive_navigator.dart index 3089f9aa07..c83f73c031 100644 --- a/lib/ui/provider/abstract_responsive_navigator.dart +++ b/lib/ui/provider/navigation/abstract_responsive_navigator.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; -import '../../utils/rivepod.dart'; +import '../../../utils/rivepod.dart'; class ResponsiveNavigatorState extends Equatable { const ResponsiveNavigatorState({ diff --git a/lib/ui/provider/responsive_navigator_provider.dart b/lib/ui/provider/navigation/responsive_navigator_provider.dart similarity index 88% rename from lib/ui/provider/responsive_navigator_provider.dart rename to lib/ui/provider/navigation/responsive_navigator_provider.dart index ba957d2217..aa401dc102 100644 --- a/lib/ui/provider/responsive_navigator_provider.dart +++ b/lib/ui/provider/navigation/responsive_navigator_provider.dart @@ -1,19 +1,19 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import '../home/chat/chat_page.dart'; -import '../setting/about_page.dart'; -import '../setting/account_delete_page.dart'; -import '../setting/account_page.dart'; -import '../setting/appearance_page.dart'; -import '../setting/backup_page.dart'; -import '../setting/edit_profile_page.dart'; -import '../setting/notification_page.dart'; -import '../setting/proxy_page.dart'; -import '../setting/security_page.dart'; -import '../setting/storage_page.dart'; -import '../setting/storage_usage_detail_page.dart'; -import '../setting/storage_usage_list_page.dart'; +import '../../home/chat/chat_page.dart'; +import '../../setting/about_page.dart'; +import '../../setting/account_delete_page.dart'; +import '../../setting/account_page.dart'; +import '../../setting/appearance_page.dart'; +import '../../setting/backup_page.dart'; +import '../../setting/edit_profile_page.dart'; +import '../../setting/notification_page.dart'; +import '../../setting/proxy_page.dart'; +import '../../setting/security_page.dart'; +import '../../setting/storage_page.dart'; +import '../../setting/storage_usage_detail_page.dart'; +import '../../setting/storage_usage_list_page.dart'; import 'abstract_responsive_navigator.dart'; class ResponsiveNavigatorStateNotifier diff --git a/lib/ui/setting/account_page.dart b/lib/ui/setting/account_page.dart index c295544c7a..d816feb7a1 100644 --- a/lib/ui/setting/account_page.dart +++ b/lib/ui/setting/account_page.dart @@ -5,7 +5,7 @@ import '../../utils/extension/extension.dart'; import '../../widgets/app_bar.dart'; import '../../widgets/cell.dart'; import '../../widgets/user/change_number_dialog.dart'; -import '../provider/responsive_navigator_provider.dart'; +import '../provider/navigation/responsive_navigator_provider.dart'; class AccountPage extends HookConsumerWidget { const AccountPage({super.key}); diff --git a/lib/ui/setting/edit_profile_page.dart b/lib/ui/setting/edit_profile_page.dart index 49add31ceb..5e6e95b74b 100644 --- a/lib/ui/setting/edit_profile_page.dart +++ b/lib/ui/setting/edit_profile_page.dart @@ -10,7 +10,7 @@ import '../../widgets/app_bar.dart'; import '../../widgets/avatar_view/avatar_view.dart'; import '../../widgets/dialog.dart'; import '../../widgets/toast.dart'; -import '../provider/multi_auth_provider.dart'; +import '../provider/account/multi_auth_provider.dart'; class EditProfilePage extends HookConsumerWidget { const EditProfilePage({super.key}); diff --git a/lib/ui/setting/setting_page.dart b/lib/ui/setting/setting_page.dart index a91aaf320b..011851de5c 100644 --- a/lib/ui/setting/setting_page.dart +++ b/lib/ui/setting/setting_page.dart @@ -16,8 +16,8 @@ import '../../widgets/avatar_view/avatar_view.dart'; import '../../widgets/cell.dart'; import '../../widgets/toast.dart'; import '../home/home.dart'; -import '../provider/multi_auth_provider.dart'; -import '../provider/responsive_navigator_provider.dart'; +import '../provider/account/multi_auth_provider.dart'; +import '../provider/navigation/responsive_navigator_provider.dart'; class SettingPage extends HookConsumerWidget { const SettingPage({super.key}); diff --git a/lib/ui/setting/storage_page.dart b/lib/ui/setting/storage_page.dart index 67955bb95e..b8708e236d 100644 --- a/lib/ui/setting/storage_page.dart +++ b/lib/ui/setting/storage_page.dart @@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../utils/extension/extension.dart'; import '../../widgets/app_bar.dart'; import '../../widgets/cell.dart'; -import '../provider/responsive_navigator_provider.dart'; +import '../provider/navigation/responsive_navigator_provider.dart'; import '../provider/setting_provider.dart'; class StoragePage extends HookConsumerWidget { diff --git a/lib/ui/setting/storage_usage_list_page.dart b/lib/ui/setting/storage_usage_list_page.dart index 093c63cacf..01d47a7fc9 100644 --- a/lib/ui/setting/storage_usage_list_page.dart +++ b/lib/ui/setting/storage_usage_list_page.dart @@ -11,7 +11,7 @@ import '../../utils/hook.dart'; import '../../widgets/app_bar.dart'; import '../../widgets/avatar_view/avatar_view.dart'; import '../../widgets/cell.dart'; -import '../provider/responsive_navigator_provider.dart'; +import '../provider/navigation/responsive_navigator_provider.dart'; class StorageUsageListPage extends HookConsumerWidget { const StorageUsageListPage({super.key}); diff --git a/lib/utils/extension/extension.dart b/lib/utils/extension/extension.dart index 5f5403d70e..b15bbe41d8 100644 --- a/lib/utils/extension/extension.dart +++ b/lib/utils/extension/extension.dart @@ -25,8 +25,8 @@ import '../../db/dao/snapshot_dao.dart'; import '../../db/database.dart'; import '../../generated/l10n.dart'; import '../../ui/provider/account/account_server_provider.dart'; +import '../../ui/provider/account/multi_auth_provider.dart'; import '../../ui/provider/database_provider.dart'; -import '../../ui/provider/multi_auth_provider.dart'; import '../../ui/provider/setting_provider.dart'; import '../../widgets/brightness_observer.dart'; import '../audio_message_player/audio_message_service.dart'; diff --git a/lib/widgets/cell.dart b/lib/widgets/cell.dart index f37fe4f010..562fbccb48 100644 --- a/lib/widgets/cell.dart +++ b/lib/widgets/cell.dart @@ -3,7 +3,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../constants/resources.dart'; -import '../ui/provider/responsive_navigator_provider.dart'; +import '../ui/provider/navigation/responsive_navigator_provider.dart'; import '../utils/extension/extension.dart'; import 'interactive_decorated_box.dart'; From ab67293be183bab53048d0b9faa33ae6a6ed8297 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:00:56 +0800 Subject: [PATCH 13/44] fix mention --- lib/ui/provider/mention_provider.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ui/provider/mention_provider.dart b/lib/ui/provider/mention_provider.dart index fe27dc7b3e..fa0e214b5d 100644 --- a/lib/ui/provider/mention_provider.dart +++ b/lib/ui/provider/mention_provider.dart @@ -14,9 +14,9 @@ import '../../utils/reg_exp_utils.dart'; import '../../utils/rivepod.dart'; import '../../widgets/mention_panel.dart'; import '../home/bloc/subscriber_mixin.dart'; +import 'account/account_server_provider.dart'; import 'account/multi_auth_provider.dart'; import 'conversation_provider.dart'; -import 'database_provider.dart'; class MentionState extends Equatable { const MentionState({ @@ -209,8 +209,8 @@ class MentionStateNotifier extends DistinctStateNotifier final mentionProvider = StateNotifierProvider.autoDispose .family>( (ref, stream) { - final userDao = ref - .watch(databaseProvider.select((value) => value.requireValue.userDao)); + final userDao = ref.watch(accountServerProvider + .select((value) => value.requireValue.database.userDao)); final authStateNotifier = ref.watch(multiAuthStateNotifierProvider.notifier); final (conversationId, isGroup, isBot) = ref.watch( From 8f85ebfc2df6f1dd5bfd0d0e0c62ae2592ebd4f3 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:56:30 +0800 Subject: [PATCH 14/44] refactor hive key values --- lib/account/account_server.dart | 43 +++++--- lib/account/scam_warning_key_value.dart | 7 +- lib/account/security_key_value.dart | 6 +- lib/account/send_message_helper.dart | 2 - lib/account/session_key_value.dart | 46 ++++---- lib/account/show_pin_message_key_value.dart | 7 +- lib/crypto/privacy_key_value.dart | 6 +- lib/ui/home/chat/chat_page.dart | 12 +- lib/ui/home/hook/pin_message.dart | 3 +- lib/ui/landing/landing_mobile.dart | 5 +- .../account/account_server_provider.dart | 26 ++--- lib/ui/provider/hive_key_value_provider.dart | 103 ++++++++++++++++++ lib/ui/provider/menu_handle_provider.dart | 2 + lib/ui/setting/account_delete_page.dart | 14 ++- lib/ui/setting/security_page.dart | 23 ++-- lib/utils/attachment/attachment_util.dart | 11 +- lib/utils/attachment/download_key_value.dart | 6 +- lib/utils/extension/extension.dart | 1 + lib/utils/extension/src/provider.dart | 2 + lib/utils/hive_key_values.dart | 50 ++------- lib/widgets/auth.dart | 17 ++- lib/widgets/user/change_number_dialog.dart | 3 +- lib/widgets/user/pin_verification_dialog.dart | 4 +- lib/widgets/window/menus.dart | 9 +- pubspec.lock | 36 +++++- 25 files changed, 268 insertions(+), 176 deletions(-) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index d5d8f3bb3c..bd73145106 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -31,10 +31,10 @@ import '../enum/encrypt_category.dart'; import '../enum/message_category.dart'; import '../ui/provider/account/account_server_provider.dart'; import '../ui/provider/account/multi_auth_provider.dart'; +import '../ui/provider/hive_key_value_provider.dart'; import '../ui/provider/setting_provider.dart'; import '../utils/app_lifecycle.dart'; import '../utils/attachment/attachment_util.dart'; -import '../utils/attachment/download_key_value.dart'; import '../utils/extension/extension.dart'; import '../utils/file.dart'; import '../utils/hive_key_values.dart'; @@ -48,7 +48,6 @@ import '../workers/isolate_event.dart'; import '../workers/message_worker_isolate.dart'; import 'account_key_value.dart'; import 'send_message_helper.dart'; -import 'show_pin_message_key_value.dart'; class AccountServer { AccountServer({ @@ -59,8 +58,7 @@ class AccountServer { required this.database, required this.currentConversationId, required this.ref, - required this.accountKeyValue, - required this.cryptoKeyValue, + required this.hiveKeyValues, }); static String? sid; @@ -69,8 +67,14 @@ class AccountServer { final SettingChangeNotifier settingChangeNotifier; final Database database; final GetCurrentConversationId currentConversationId; - final AccountKeyValue accountKeyValue; - final CryptoKeyValue cryptoKeyValue; + + final HiveKeyValues hiveKeyValues; + + CryptoKeyValue get cryptoKeyValue => hiveKeyValues.cryptoKeyValue; + + PrivacyKeyValue get privacyKeyValue => hiveKeyValues.privacyKeyValue; + + AccountKeyValue get accountKeyValue => hiveKeyValues.accountKeyValue; Timer? checkSignalKeyTimer; @@ -123,7 +127,7 @@ class AccountServer { unawaited(_start()); - DownloadKeyValue.instance.messageIds.forEach((messageId) { + hiveKeyValues.downloadKeyValue.messageIds.forEach((messageId) { attachmentUtil.downloadAttachment(messageId: messageId); }); appActiveListener.addListener(onActive); @@ -173,7 +177,8 @@ class AccountServer { ], )..configProxySetting(database.settingProperties); - attachmentUtil = AttachmentUtil.init(client, database, identityNumber); + attachmentUtil = AttachmentUtil.init( + client, database, identityNumber, hiveKeyValues.downloadKeyValue); _sendMessageHelper = SendMessageHelper(database, attachmentUtil, addSendingJob); @@ -270,7 +275,7 @@ class AccountServer { break; case WorkerIsolateEventType.showPinMessage: final conversationId = event.argument as String; - unawaited(ShowPinMessageKeyValue.instance.show(conversationId)); + unawaited(hiveKeyValues.showPinMessageKeyValue.show(conversationId)); break; } } @@ -765,12 +770,12 @@ class AccountServer { } Future checkSignalKeys() async { - final hasPushSignalKeys = PrivacyKeyValue.instance.hasPushSignalKeys; + final hasPushSignalKeys = privacyKeyValue.hasPushSignalKeys; if (hasPushSignalKeys) { unawaited(checkSignalKey(client, signalDatabase!, cryptoKeyValue)); } else { await refreshSignalKeys(client, signalDatabase!, cryptoKeyValue); - PrivacyKeyValue.instance.hasPushSignalKeys = true; + privacyKeyValue.hasPushSignalKeys = true; } } @@ -1405,13 +1410,15 @@ class AccountServer { Future pinMessage({ required String conversationId, required List pinMessageMinimals, - }) => - _sendMessageHelper.sendPinMessage( - conversationId: conversationId, - senderId: userId, - pinMessageMinimals: pinMessageMinimals, - pin: true, - ); + }) async { + await _sendMessageHelper.sendPinMessage( + conversationId: conversationId, + senderId: userId, + pinMessageMinimals: pinMessageMinimals, + pin: true, + ); + unawaited(hiveKeyValues.showPinMessageKeyValue.show(conversationId)); + } Future unpinMessage({ required String conversationId, diff --git a/lib/account/scam_warning_key_value.dart b/lib/account/scam_warning_key_value.dart index 513e175b5c..f458ccf37a 100644 --- a/lib/account/scam_warning_key_value.dart +++ b/lib/account/scam_warning_key_value.dart @@ -3,14 +3,9 @@ import 'package:rxdart/rxdart.dart'; import '../utils/hive_key_values.dart'; class ScamWarningKeyValue extends HiveKeyValue { - ScamWarningKeyValue._() : super(_hiveName); + ScamWarningKeyValue() : super(_hiveName); static const _hiveName = 'scam_warning_key_value'; - static ScamWarningKeyValue? _instance; - - static ScamWarningKeyValue get instance => - _instance ??= ScamWarningKeyValue._(); - bool _isShow(String userId) => box.get(userId, defaultValue: null).isShow; Future dismiss(String userId) => diff --git a/lib/account/security_key_value.dart b/lib/account/security_key_value.dart index a338f96979..25e250455e 100644 --- a/lib/account/security_key_value.dart +++ b/lib/account/security_key_value.dart @@ -4,11 +4,7 @@ import 'package:rxdart/rxdart.dart'; import '../utils/hive_key_values.dart'; class SecurityKeyValue extends HiveKeyValue { - SecurityKeyValue._() : super(_hiveSecurity); - - static SecurityKeyValue? _instance; - - static SecurityKeyValue get instance => _instance ??= SecurityKeyValue._(); + SecurityKeyValue() : super(_hiveSecurity); static const _hiveSecurity = 'security_box'; static const _passcode = 'passcode'; diff --git a/lib/account/send_message_helper.dart b/lib/account/send_message_helper.dart index 837e7e2cef..831c1685e4 100644 --- a/lib/account/send_message_helper.dart +++ b/lib/account/send_message_helper.dart @@ -30,7 +30,6 @@ import '../utils/logger.dart'; import '../utils/reg_exp_utils.dart'; import '../widgets/cache_image.dart'; import '../widgets/message/send_message_dialog/attachment_extra.dart'; -import 'show_pin_message_key_value.dart'; const jpegMimeType = 'image/jpeg'; const gifMimeType = 'image/gif'; @@ -1187,7 +1186,6 @@ class SendMessageHelper { cleanDraft: false, ); }); - unawaited(ShowPinMessageKeyValue.instance.show(conversationId)); } else { await _pinMessageDao .deleteByIds(pinMessageMinimals.map((e) => e.messageId).toList()); diff --git a/lib/account/session_key_value.dart b/lib/account/session_key_value.dart index adcd36ce63..df367f4eb5 100644 --- a/lib/account/session_key_value.dart +++ b/lib/account/session_key_value.dart @@ -9,11 +9,7 @@ import '../utils/hive_key_values.dart'; import '../utils/logger.dart'; class SessionKeyValue extends HiveKeyValue { - SessionKeyValue._() : super('session_box'); - - static SessionKeyValue? _instance; - - static SessionKeyValue get instance => _instance ??= SessionKeyValue._(); + SessionKeyValue() : super('session_box'); static const _keyPinToken = 'pinToken'; static const _keyPinIterator = 'pinIterator'; @@ -36,30 +32,30 @@ List decryptPinToken(String serverPublicKey, ed.PrivateKey privateKey) { return calculateAgreement(bytes, private); } -String? encryptPin(String code) { - assert(code.isNotEmpty, 'code is empty'); - final iterator = SessionKeyValue.instance.pinIterator; - final pinToken = SessionKeyValue.instance.pinToken; +extension EncryptPin on SessionKeyValue { + String? encryptPin(String code) { + assert(code.isNotEmpty, 'code is empty'); - if (pinToken == null) { - e('pinToken is null'); - return null; - } + if (pinToken == null) { + e('pinToken is null'); + return null; + } - d('pinToken: $pinToken'); + d('pinToken: $pinToken'); - final pinBytes = Uint8List.fromList(utf8.encode(code)); - final timeBytes = Uint8List(8); - final iteratorBytes = Uint8List(8); - final nowSec = DateTime.now().millisecondsSinceEpoch ~/ 1000; - timeBytes.buffer.asByteData().setUint64(0, nowSec, Endian.little); - iteratorBytes.buffer.asByteData().setUint64(0, iterator, Endian.little); + final pinBytes = Uint8List.fromList(utf8.encode(code)); + final timeBytes = Uint8List(8); + final iteratorBytes = Uint8List(8); + final nowSec = DateTime.now().millisecondsSinceEpoch ~/ 1000; + timeBytes.buffer.asByteData().setUint64(0, nowSec, Endian.little); + iteratorBytes.buffer.asByteData().setUint64(0, pinIterator, Endian.little); - // pin+time+iterator - final plaintext = Uint8List.fromList(pinBytes + timeBytes + iteratorBytes); - final ciphertext = aesEncrypt(base64Decode(pinToken), plaintext); + // pin+time+iterator + final plaintext = Uint8List.fromList(pinBytes + timeBytes + iteratorBytes); + final ciphertext = aesEncrypt(base64Decode(pinToken!), plaintext); - SessionKeyValue.instance.pinIterator = iterator + 1; + pinIterator = pinIterator + 1; - return base64Encode(ciphertext); + return base64Encode(ciphertext); + } } diff --git a/lib/account/show_pin_message_key_value.dart b/lib/account/show_pin_message_key_value.dart index e976ce0f83..d5c964f4fe 100644 --- a/lib/account/show_pin_message_key_value.dart +++ b/lib/account/show_pin_message_key_value.dart @@ -3,15 +3,10 @@ import 'package:rxdart/rxdart.dart'; import '../utils/hive_key_values.dart'; class ShowPinMessageKeyValue extends HiveKeyValue { - ShowPinMessageKeyValue._() : super(_hiveName); + ShowPinMessageKeyValue() : super(_hiveName); static const _hiveName = 'show_pin_message_box'; - static ShowPinMessageKeyValue? _instance; - - static ShowPinMessageKeyValue get instance => - _instance ??= ShowPinMessageKeyValue._(); - Future show(String conversationId) => box.put(conversationId, true); bool isShow(String conversationId) => diff --git a/lib/crypto/privacy_key_value.dart b/lib/crypto/privacy_key_value.dart index 12d46cfae2..a17d12a83c 100644 --- a/lib/crypto/privacy_key_value.dart +++ b/lib/crypto/privacy_key_value.dart @@ -1,11 +1,7 @@ import '../utils/hive_key_values.dart'; class PrivacyKeyValue extends HiveKeyValue { - PrivacyKeyValue._() : super(_hivePrivacy); - - static PrivacyKeyValue? _instance; - - static PrivacyKeyValue get instance => _instance ??= PrivacyKeyValue._(); + PrivacyKeyValue() : super(_hivePrivacy); static const _hivePrivacy = 'privacy_box'; static const _hasSyncSession = 'has_sync_session'; diff --git a/lib/ui/home/chat/chat_page.dart b/lib/ui/home/chat/chat_page.dart index c2e1292641..2b54ad4d90 100644 --- a/lib/ui/home/chat/chat_page.dart +++ b/lib/ui/home/chat/chat_page.dart @@ -9,8 +9,6 @@ import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart' hide Provider; import 'package:provider/provider.dart'; -import '../../../account/scam_warning_key_value.dart'; -import '../../../account/show_pin_message_key_value.dart'; import '../../../bloc/simple_cubit.dart'; import '../../../bloc/subscribe_mixin.dart'; import '../../../constants/resources.dart'; @@ -760,7 +758,7 @@ class _BottomBanner extends HookConsumerWidget { final showScamWarning = useMemoizedStream( () { if (userId == null || !isScam) return Stream.value(false); - return ScamWarningKeyValue.instance.watch(userId); + return context.hiveKeyValues.scamWarningKeyValue.watch(userId); }, initialData: false, keys: [userId], @@ -814,7 +812,7 @@ class _BottomBanner extends HookConsumerWidget { size: 20, onTap: () { if (userId == null) return; - ScamWarningKeyValue.instance.dismiss(userId); + context.hiveKeyValues.scamWarningKeyValue.dismiss(userId); }, ), ], @@ -857,7 +855,7 @@ class _PinMessagesBanner extends HookConsumerWidget { final conversationId = ref.read(currentConversationIdProvider); if (conversationId == null) return; - ShowPinMessageKeyValue.instance + context.hiveKeyValues.showPinMessageKeyValue .dismiss(conversationId); }, ), @@ -1228,4 +1226,8 @@ class _ConversationHandle extends ConversationMenuHandle { ), ); } + + @override + Stream get hasPasscode => + context.hiveKeyValues.securityKeyValue.watchHasPasscode(); } diff --git a/lib/ui/home/hook/pin_message.dart b/lib/ui/home/hook/pin_message.dart index 4f8fe8b1b1..a1fe45ad45 100644 --- a/lib/ui/home/hook/pin_message.dart +++ b/lib/ui/home/hook/pin_message.dart @@ -2,7 +2,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import '../../../account/show_pin_message_key_value.dart'; import '../../../blaze/vo/pin_message_minimal.dart'; import '../../../db/database_event_bus.dart'; import '../../../utils/extension/extension.dart'; @@ -59,7 +58,7 @@ PinMessageState usePinMessageState(String? conversationId) { final showLastPinMessage = useMemoizedStream( () { if (conversationId == null) return Stream.value(false); - return ShowPinMessageKeyValue.instance.watch(conversationId); + return context.hiveKeyValues.showPinMessageKeyValue.watch(conversationId); }, initialData: false, keys: [conversationId], diff --git a/lib/ui/landing/landing_mobile.dart b/lib/ui/landing/landing_mobile.dart index 2e517b77c6..1271e80b78 100644 --- a/lib/ui/landing/landing_mobile.dart +++ b/lib/ui/landing/landing_mobile.dart @@ -177,8 +177,9 @@ class _CodeInputScene extends HookConsumerWidget { await ref.read(cryptoKeyValueProvider(identityNumber).future); cryptoKeyValue.localRegistrationId = registrationId; - await SessionKeyValue.instance.init(identityNumber); - SessionKeyValue.instance.pinToken = base64Encode(decryptPinToken( + final sessionKeyValue = + await ref.read(sessionKeyValueProvider(identityNumber).future); + sessionKeyValue.pinToken = base64Encode(decryptPinToken( response.data.pinToken, sessionKey.privateKey, )); diff --git a/lib/ui/provider/account/account_server_provider.dart b/lib/ui/provider/account/account_server_provider.dart index 1e03731970..78a22bc35f 100644 --- a/lib/ui/provider/account/account_server_provider.dart +++ b/lib/ui/provider/account/account_server_provider.dart @@ -3,10 +3,8 @@ import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import '../../../account/account_key_value.dart'; import '../../../account/account_server.dart'; import '../../../blaze/blaze.dart'; -import '../../../crypto/crypto_key_value.dart'; import '../../../db/database.dart'; import '../conversation_provider.dart'; import '../database_provider.dart'; @@ -31,8 +29,7 @@ class AccountServerOpener extends AutoDisposeStreamNotifier { database: args.database, currentConversationId: args.currentConversationId, ref: ref, - accountKeyValue: args.accountKeyValue, - cryptoKeyValue: args.cryptoKeyValue, + hiveKeyValues: args.hiveKeyValues, ); await accountServer.initServer( @@ -58,8 +55,7 @@ class _Args extends Equatable { required this.multiAuthChangeNotifier, required this.settingChangeNotifier, required this.currentConversationId, - required this.accountKeyValue, - required this.cryptoKeyValue, + required this.hiveKeyValues, }); final Database database; @@ -70,8 +66,7 @@ class _Args extends Equatable { final MultiAuthStateNotifier multiAuthChangeNotifier; final SettingChangeNotifier settingChangeNotifier; final GetCurrentConversationId currentConversationId; - final AccountKeyValue accountKeyValue; - final CryptoKeyValue cryptoKeyValue; + final HiveKeyValues hiveKeyValues; @override List get props => [ @@ -83,8 +78,7 @@ class _Args extends Equatable { multiAuthChangeNotifier, settingChangeNotifier, currentConversationId, - accountKeyValue, - cryptoKeyValue, + hiveKeyValues, ]; } @@ -116,13 +110,8 @@ final _argsProvider = FutureProvider.autoDispose((ref) async { ref.watch(multiAuthStateNotifierProvider.notifier); final settingChangeNotifier = ref.watch(settingProvider); final currentConversationId = ref.read(_currentConversationIdProvider); - final accountKeyValue = - await ref.watch(currentAccountKeyValueProvider.future); - final cryptoKeyValue = - await ref.watch(cryptoKeyValueProvider(identityNumber).future); - if (accountKeyValue == null) { - return null; - } + final hiveKeyValues = + await ref.watch(hiveKeyValueProvider(identityNumber).future); return _Args( database: database, @@ -133,8 +122,7 @@ final _argsProvider = FutureProvider.autoDispose((ref) async { multiAuthChangeNotifier: multiAuthChangeNotifier, settingChangeNotifier: settingChangeNotifier, currentConversationId: currentConversationId, - accountKeyValue: accountKeyValue, - cryptoKeyValue: cryptoKeyValue, + hiveKeyValues: hiveKeyValues, ); }); diff --git a/lib/ui/provider/hive_key_value_provider.dart b/lib/ui/provider/hive_key_value_provider.dart index 9d95286ba8..d6b74c87dc 100644 --- a/lib/ui/provider/hive_key_value_provider.dart +++ b/lib/ui/provider/hive_key_value_provider.dart @@ -1,7 +1,14 @@ +import 'package:equatable/equatable.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../account/account_key_value.dart'; +import '../../account/scam_warning_key_value.dart'; +import '../../account/security_key_value.dart'; +import '../../account/session_key_value.dart'; +import '../../account/show_pin_message_key_value.dart'; import '../../crypto/crypto_key_value.dart'; +import '../../crypto/privacy_key_value.dart'; +import '../../utils/attachment/download_key_value.dart'; import '../../utils/hive_key_values.dart'; import 'account/multi_auth_provider.dart'; @@ -34,3 +41,99 @@ final currentAccountKeyValueProvider = return ref.watch(accountKeyValueProvider(identityNumber).future); }, ); + +final downloadKeyValueProvider = + _createHiveKeyValueProvider(DownloadKeyValue.new); + +final sessionKeyValueProvider = + _createHiveKeyValueProvider(SessionKeyValue.new); + +final currentSessionKeyValueProvider = + FutureProvider.autoDispose( + (ref) async { + final identityNumber = + ref.watch(authAccountProvider.select((value) => value?.identityNumber)); + if (identityNumber == null) { + return null; + } + return ref.watch(sessionKeyValueProvider(identityNumber).future); + }, +); + +final privacyKeyValueProvider = + _createHiveKeyValueProvider(PrivacyKeyValue.new); + +final securityKeyValueProvider = + _createHiveKeyValueProvider(SecurityKeyValue.new); + +final showPinMessageKeyValueProvider = + _createHiveKeyValueProvider(ShowPinMessageKeyValue.new); + +final scamWarningKeyValueProvider = + _createHiveKeyValueProvider(ScamWarningKeyValue.new); + +class HiveKeyValues with EquatableMixin { + HiveKeyValues({ + required this.accountKeyValue, + required this.cryptoKeyValue, + required this.sessionKeyValue, + required this.privacyKeyValue, + required this.downloadKeyValue, + required this.securityKeyValue, + required this.showPinMessageKeyValue, + required this.scamWarningKeyValue, + }); + + final AccountKeyValue accountKeyValue; + final CryptoKeyValue cryptoKeyValue; + final SessionKeyValue sessionKeyValue; + final PrivacyKeyValue privacyKeyValue; + final DownloadKeyValue downloadKeyValue; + final SecurityKeyValue securityKeyValue; + final ShowPinMessageKeyValue showPinMessageKeyValue; + final ScamWarningKeyValue scamWarningKeyValue; + + @override + List get props => [ + accountKeyValue, + cryptoKeyValue, + sessionKeyValue, + privacyKeyValue, + downloadKeyValue, + securityKeyValue, + showPinMessageKeyValue, + scamWarningKeyValue, + ]; +} + +final hiveKeyValueProvider = + FutureProvider.autoDispose.family( + (ref, identityNumber) async { + final accountKeyValue = + await ref.watch(accountKeyValueProvider(identityNumber).future); + final cryptoKeyValue = + await ref.watch(cryptoKeyValueProvider(identityNumber).future); + final sessionKeyValue = + await ref.watch(sessionKeyValueProvider(identityNumber).future); + final privacyKeyValue = + await ref.watch(privacyKeyValueProvider(identityNumber).future); + final downloadKeyValue = + await ref.watch(downloadKeyValueProvider(identityNumber).future); + final securityKeyValue = + await ref.watch(securityKeyValueProvider(identityNumber).future); + final showPinMessageKeyValue = + await ref.watch(showPinMessageKeyValueProvider(identityNumber).future); + final scamWarningKeyValue = + await ref.watch(scamWarningKeyValueProvider(identityNumber).future); + return HiveKeyValues( + accountKeyValue: accountKeyValue, + cryptoKeyValue: cryptoKeyValue, + sessionKeyValue: sessionKeyValue, + privacyKeyValue: privacyKeyValue, + downloadKeyValue: downloadKeyValue, + securityKeyValue: securityKeyValue, + showPinMessageKeyValue: showPinMessageKeyValue, + scamWarningKeyValue: scamWarningKeyValue, + ); + }, +); diff --git a/lib/ui/provider/menu_handle_provider.dart b/lib/ui/provider/menu_handle_provider.dart index 21dd74fa86..8b4629ca7e 100644 --- a/lib/ui/provider/menu_handle_provider.dart +++ b/lib/ui/provider/menu_handle_provider.dart @@ -10,6 +10,8 @@ abstract class ConversationMenuHandle { Stream get isPinned; + Stream get hasPasscode; + void mute(); void unmute(); diff --git a/lib/ui/setting/account_delete_page.dart b/lib/ui/setting/account_delete_page.dart index 4046099f1a..f4c6f6f7d8 100644 --- a/lib/ui/setting/account_delete_page.dart +++ b/lib/ui/setting/account_delete_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart' hide encryptPin; @@ -20,11 +21,11 @@ import '../../widgets/user/change_number_dialog.dart'; import '../../widgets/user/pin_verification_dialog.dart'; import '../../widgets/user/verification_dialog.dart'; -class AccountDeletePage extends StatelessWidget { +class AccountDeletePage extends ConsumerWidget { const AccountDeletePage({super.key}); @override - Widget build(BuildContext context) => Scaffold( + Widget build(BuildContext context, WidgetRef ref) => Scaffold( backgroundColor: context.theme.background, appBar: MixinAppBar( title: Text(context.l10n.deleteMyAccount), @@ -43,7 +44,9 @@ class AccountDeletePage extends StatelessWidget { title: Text(context.l10n.deleteMyAccount), color: context.theme.red, onTap: () async { - if (!SessionKeyValue.instance.checkPinToken()) { + final sessionKeyValue = + context.hiveKeyValues.sessionKeyValue; + if (!sessionKeyValue.checkPinToken()) { showToastFailed( ToastError(context.l10n.errorNoPinToken), ); @@ -264,7 +267,10 @@ class _DeleteAccountPinDialog extends StatelessWidget { PinInputLayout( doVerify: (String pin) async { await context.accountServer.client.accountApi.deactive( - DeactivateRequest(encryptPin(pin)!, verificationId), + DeactivateRequest( + context.hiveKeyValues.sessionKeyValue.encryptPin(pin)!, + verificationId, + ), ); Navigator.pop(context, true); }, diff --git a/lib/ui/setting/security_page.dart b/lib/ui/setting/security_page.dart index 70ff9076b3..38702de638 100644 --- a/lib/ui/setting/security_page.dart +++ b/lib/ui/setting/security_page.dart @@ -5,7 +5,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:pin_code_fields/pin_code_fields.dart'; -import '../../account/security_key_value.dart'; import '../../utils/authentication.dart'; import '../../utils/extension/extension.dart'; import '../../utils/hook.dart'; @@ -46,15 +45,17 @@ class _Passcode extends HookConsumerWidget { final globalKey = useMemoized(GlobalKey>.new, []); + final securityKeyValue = context.hiveKeyValues.securityKeyValue; + final hasPasscode = - useMemoizedStream(SecurityKeyValue.instance.watchHasPasscode).data ?? - SecurityKeyValue.instance.hasPasscode; + useMemoizedStream(securityKeyValue.watchHasPasscode).data ?? + securityKeyValue.hasPasscode; final enableBiometric = - useMemoizedStream(SecurityKeyValue.instance.watchBiometric).data ?? - SecurityKeyValue.instance.biometric; + useMemoizedStream(securityKeyValue.watchBiometric).data ?? + securityKeyValue.biometric; - final minutes = useStream(SecurityKeyValue.instance + final minutes = useStream(securityKeyValue .watchLockDuration() .map((event) => event.inMinutes)).data; @@ -75,7 +76,7 @@ class _Passcode extends HookConsumerWidget { value: hasPasscode, onChanged: (value) { if (!value) { - SecurityKeyValue.instance.passcode = null; + securityKeyValue.passcode = null; return; } showMixinDialog( @@ -124,8 +125,8 @@ class _Passcode extends HookConsumerWidget { ), ) .toList(), - onSelected: (value) => - SecurityKeyValue.instance.lockDuration = value, + onSelected: (value) => context + .hiveKeyValues.securityKeyValue.lockDuration = value, child: Text( (minutes == null || minutes == 0) ? context.l10n.disabled @@ -156,7 +157,7 @@ class _Passcode extends HookConsumerWidget { return; } - SecurityKeyValue.instance.biometric = value; + securityKeyValue.biometric = value; }, ), ), @@ -206,7 +207,7 @@ class _InputPasscode extends HookConsumerWidget { return; } - SecurityKeyValue.instance.passcode = passcode.value; + context.hiveKeyValues.securityKeyValue.passcode = passcode.value; Navigator.maybePop(context); }); diff --git a/lib/utils/attachment/attachment_util.dart b/lib/utils/attachment/attachment_util.dart index 5ded9c75ed..2622b02e2c 100644 --- a/lib/utils/attachment/attachment_util.dart +++ b/lib/utils/attachment/attachment_util.dart @@ -28,7 +28,6 @@ import '../proxy.dart'; import 'download_key_value.dart'; part 'attachment_download_job.dart'; - part 'attachment_upload_job.dart'; final _dio = (() { @@ -143,6 +142,7 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { this._transcriptMessageDao, this._settingProperties, super.mediaPath, + this.downloadKeyValue, ) { final httpClientAdapter = _dio.httpClientAdapter; if (httpClientAdapter is IOHttpClientAdapter) { @@ -159,6 +159,7 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { final TranscriptMessageDao _transcriptMessageDao; final Client _client; final SettingPropertyStorage _settingProperties; + final DownloadKeyValue downloadKeyValue; final _attachmentJob = {}; @@ -461,6 +462,7 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { Client client, Database database, String identityNumber, + DownloadKeyValue downloadKeyValue, ) { final documentDirectory = mixinDocumentsDirectory; final mediaDirectory = @@ -471,6 +473,7 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { database.transcriptMessageDao, database.settingProperties, mediaDirectory.path, + downloadKeyValue, ); } @@ -483,12 +486,12 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { void _setAttachmentJob(String messageId, _AttachmentJobBase job) { _attachmentJob[messageId] = job; if (job is _AttachmentDownloadJob) { - DownloadKeyValue.instance.addMessageId(messageId); + downloadKeyValue.addMessageId(messageId); } } Future removeAttachmentJob(String messageId) async { - await DownloadKeyValue.instance.removeMessageId(messageId); + await downloadKeyValue.removeMessageId(messageId); _attachmentJob[messageId]?.cancel(); _attachmentJob.remove(messageId); } @@ -496,7 +499,7 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { Future cancelProgressAttachmentJob(String messageId) async { if (!_hasAttachmentJob(messageId)) return false; await _messageDao.updateMediaStatus(messageId, MediaStatus.canceled); - await DownloadKeyValue.instance.removeMessageId(messageId); + await downloadKeyValue.removeMessageId(messageId); _attachmentJob[messageId]?.cancel(); _attachmentJob.remove(messageId); return true; diff --git a/lib/utils/attachment/download_key_value.dart b/lib/utils/attachment/download_key_value.dart index dc03e87bd9..733e92ab31 100644 --- a/lib/utils/attachment/download_key_value.dart +++ b/lib/utils/attachment/download_key_value.dart @@ -1,11 +1,7 @@ import '../hive_key_values.dart'; class DownloadKeyValue extends HiveKeyValue { - DownloadKeyValue._() : super(_hiveName); - - static DownloadKeyValue? _instance; - - static DownloadKeyValue get instance => _instance ??= DownloadKeyValue._(); + DownloadKeyValue() : super(_hiveName); static const _hiveName = 'download_box'; diff --git a/lib/utils/extension/extension.dart b/lib/utils/extension/extension.dart index b15bbe41d8..3c56c96425 100644 --- a/lib/utils/extension/extension.dart +++ b/lib/utils/extension/extension.dart @@ -27,6 +27,7 @@ import '../../generated/l10n.dart'; import '../../ui/provider/account/account_server_provider.dart'; import '../../ui/provider/account/multi_auth_provider.dart'; import '../../ui/provider/database_provider.dart'; +import '../../ui/provider/hive_key_value_provider.dart'; import '../../ui/provider/setting_provider.dart'; import '../../widgets/brightness_observer.dart'; import '../audio_message_player/audio_message_service.dart'; diff --git a/lib/utils/extension/src/provider.dart b/lib/utils/extension/src/provider.dart index adc955300c..9cfa0b3991 100644 --- a/lib/utils/extension/src/provider.dart +++ b/lib/utils/extension/src/provider.dart @@ -17,6 +17,8 @@ extension ProviderExtension on BuildContext { return value.requireValue; })); + HiveKeyValues get hiveKeyValues => accountServer.hiveKeyValues; + AudioMessagePlayService get audioMessageService => read(); diff --git a/lib/utils/hive_key_values.dart b/lib/utils/hive_key_values.dart index 75c95f2dbd..e582343eb3 100644 --- a/lib/utils/hive_key_values.dart +++ b/lib/utils/hive_key_values.dart @@ -6,45 +6,18 @@ import 'package:hive/hive.dart'; import 'package:mixin_logger/mixin_logger.dart'; import 'package:path/path.dart' as p; -import '../account/scam_warning_key_value.dart'; -import '../account/security_key_value.dart'; -import '../account/session_key_value.dart'; -import '../account/show_pin_message_key_value.dart'; -import '../crypto/privacy_key_value.dart'; -import 'attachment/download_key_value.dart'; import 'file.dart'; -Future initKeyValues(String identityNumber) => Future.wait([ - PrivacyKeyValue.instance.init(identityNumber), - ShowPinMessageKeyValue.instance.init(identityNumber), - ScamWarningKeyValue.instance.init(identityNumber), - DownloadKeyValue.instance.init(identityNumber), - SessionKeyValue.instance.init(identityNumber), - SecurityKeyValue.instance.init(identityNumber), - ]); +Future initKeyValues(String identityNumber) => Future.wait([]); -Future clearKeyValues() => Future.wait([ - PrivacyKeyValue.instance.delete(), - ShowPinMessageKeyValue.instance.delete(), - ScamWarningKeyValue.instance.delete(), - DownloadKeyValue.instance.delete(), - SessionKeyValue.instance.delete(), - SecurityKeyValue.instance.delete(), - ]); +Future clearKeyValues() => Future.wait([]); -Future disposeKeyValues() => Future.wait([ - PrivacyKeyValue.instance.dispose(), - ShowPinMessageKeyValue.instance.dispose(), - ScamWarningKeyValue.instance.dispose(), - DownloadKeyValue.instance.dispose(), - SessionKeyValue.instance.dispose(), - SecurityKeyValue.instance.dispose(), - ]); +Future disposeKeyValues() => Future.wait([]); abstract class HiveKeyValue { - HiveKeyValue(this._boxName); + HiveKeyValue(this.boxName); - final String _boxName; + final String boxName; late Box box; bool _hasInit = false; @@ -56,9 +29,8 @@ abstract class HiveKeyValue { } final dbFolder = mixinDocumentsDirectory; - final legacyBoxDirectory = Directory(p.join(dbFolder.path, _boxName)); - final directory = - Directory(p.join(dbFolder.path, identityNumber, _boxName)); + final legacyBoxDirectory = Directory(p.join(dbFolder.path, boxName)); + final directory = Directory(p.join(dbFolder.path, identityNumber, boxName)); if (legacyBoxDirectory.existsSync()) { // copy legacy file to new file @@ -70,8 +42,8 @@ abstract class HiveKeyValue { if (!kIsWeb) { Hive.init(directory.absolute.path); } - box = await Hive.openBox(_boxName); - i('HiveKeyValue: open $_boxName'); + box = await Hive.openBox(boxName); + i('HiveKeyValue: open $boxName'); _identityNumber = identityNumber; _hasInit = true; } @@ -80,7 +52,7 @@ abstract class HiveKeyValue { if (!_hasInit) { return; } - i('HiveKeyValue: dispose $_boxName $_identityNumber'); + i('HiveKeyValue: dispose $boxName $_identityNumber'); await box.close(); _hasInit = false; } @@ -88,7 +60,7 @@ abstract class HiveKeyValue { Future delete() async { if (!_hasInit) return; try { - await Hive.deleteBoxFromDisk(_boxName); + await Hive.deleteBoxFromDisk(boxName); } catch (_) { // ignore already deleted } diff --git a/lib/widgets/auth.dart b/lib/widgets/auth.dart index 623d333230..cc33188988 100644 --- a/lib/widgets/auth.dart +++ b/lib/widgets/auth.dart @@ -9,7 +9,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:pin_code_fields/pin_code_fields.dart'; import 'package:rxdart/rxdart.dart'; -import '../account/security_key_value.dart'; import '../constants/resources.dart'; import '../ui/provider/account/account_server_provider.dart'; import '../utils/app_lifecycle.dart'; @@ -51,17 +50,18 @@ class _AuthGuard extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final focusNode = useFocusNode(); final textEditingController = useTextEditingController(); + final securityKeyValue = context.hiveKeyValues.securityKeyValue; final hasPasscode = - useMemoizedStream(SecurityKeyValue.instance.watchHasPasscode).data ?? - SecurityKeyValue.instance.hasPasscode; + useMemoizedStream(securityKeyValue.watchHasPasscode).data ?? + securityKeyValue.hasPasscode; final enableBiometric = - useMemoizedStream(SecurityKeyValue.instance.watchBiometric).data ?? - SecurityKeyValue.instance.biometric; + useMemoizedStream(securityKeyValue.watchBiometric).data ?? + securityKeyValue.biometric; final hasError = useState(false); - final lock = useState(SecurityKeyValue.instance.hasPasscode); + final lock = useState(securityKeyValue.hasPasscode); useEffect(() { final listen = @@ -84,8 +84,7 @@ class _AuthGuard extends HookConsumerWidget { final needLock = !isAppActive; - final lockDuration = - SecurityKeyValue.instance.lockDuration ?? _lockDuration; + final lockDuration = securityKeyValue.lockDuration ?? _lockDuration; if (needLock) { if (lockDuration.inMinutes > 0) { timer = Timer(lockDuration, () { @@ -201,7 +200,7 @@ class _AuthGuard extends HookConsumerWidget { showCursor: false, onCompleted: (value) { textEditingController.text = ''; - if (SecurityKeyValue.instance.passcode == value) { + if (securityKeyValue.passcode == value) { lock.value = false; } else { hasError.value = true; diff --git a/lib/widgets/user/change_number_dialog.dart b/lib/widgets/user/change_number_dialog.dart index 16a9daad25..bee8a822f6 100644 --- a/lib/widgets/user/change_number_dialog.dart +++ b/lib/widgets/user/change_number_dialog.dart @@ -60,7 +60,8 @@ Future showChangeNumberDialog(BuildContext context) async { platformVersion: platformVersion, appVersion: packageInfo.version, packageName: 'one.mixin.messenger', - pin: encryptPin(pinCode), + pin: context.accountServer.hiveKeyValues.sessionKeyValue + .encryptPin(pinCode), code: code, ), ); diff --git a/lib/widgets/user/pin_verification_dialog.dart b/lib/widgets/user/pin_verification_dialog.dart index 15a08db63e..209ac6e380 100644 --- a/lib/widgets/user/pin_verification_dialog.dart +++ b/lib/widgets/user/pin_verification_dialog.dart @@ -46,8 +46,10 @@ class _PinVerificationDialog extends StatelessWidget { const SizedBox(height: 20), PinInputLayout( doVerify: (String pin) async { + final sessionKeyValue = + context.hiveKeyValues.sessionKeyValue; await context.accountServer.client.accountApi - .verifyPin(encryptPin(pin)!); + .verifyPin(sessionKeyValue.encryptPin(pin)!); Navigator.pop(context, pin); }, ), diff --git a/lib/widgets/window/menus.dart b/lib/widgets/window/menus.dart index 448184963c..e2604166a5 100644 --- a/lib/widgets/window/menus.dart +++ b/lib/widgets/window/menus.dart @@ -7,7 +7,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:window_manager/window_manager.dart'; -import '../../account/security_key_value.dart'; import '../../ui/home/conversation/conversation_hotkey.dart'; import '../../ui/provider/account/account_server_provider.dart'; import '../../ui/provider/menu_handle_provider.dart'; @@ -61,10 +60,10 @@ class _Menus extends HookConsumerWidget { ).data ?? false; - final hasPasscode = useMemoizedStream(signed - ? SecurityKeyValue.instance.watchHasPasscode - : () => Stream.value(false)) - .data ?? + final hasPasscode = useMemoizedStream( + () => handle?.hasPasscode ?? const Stream.empty(), + keys: [handle], + ).data ?? false; PlatformMenu buildConversationMenu() => PlatformMenu( diff --git a/pubspec.lock b/pubspec.lock index dd84cae0a2..0858e8be69 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1017,6 +1017,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: f38a2c91c12f31726ca13015fbab3d2e9440edcb7c17b8b36ed9b85ed6eee6a2 + url: "https://pub.dev" + source: hosted + version: "9.0.11" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "23770c69594f5260a79fe9d84e29f8b175d1b05d128e751c904b3cdf910e5dfc" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: b06739349ec2477e943055aea30172c5c7000225f79dad4702e2ec0eda79a6ff + url: "https://pub.dev" + source: hosted + version: "1.0.5" libphonenumber_platform_interface: dependency: transitive description: @@ -1829,10 +1853,10 @@ packages: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ui_device: dependency: "direct main" description: @@ -1970,6 +1994,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.0+2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: a13d5503b4facefc515c8c587ce3cf69577a7b064a9f1220e005449cf1f64aad + url: "https://pub.dev" + source: hosted + version: "12.0.0" watcher: dependency: "direct main" description: From 77803bd7d38fac4613cb4fe1d658c7dad0c5c7c4 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 7 Nov 2023 13:44:34 +0800 Subject: [PATCH 15/44] improve landing --- lib/app.dart | 8 +-- lib/ui/home/slide_page.dart | 5 +- lib/ui/landing/landing.dart | 55 +++++++++++++++++++ lib/ui/provider/transfer_provider.dart | 2 +- .../item/transfer/safe_transfer_dialog.dart | 2 +- macos/Podfile.lock | 2 +- pubspec.lock | 50 ++++++++--------- 7 files changed, 89 insertions(+), 35 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index dce4bc8925..28805fefe4 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -56,9 +56,9 @@ class App extends HookConsumerWidget { Widget child; if (authState == null) { - child = const _App(home: AppInitializingPage()); + child = const _App(home: LandingPage()); } else { - child = _LoginApp(authState: authState); + child = const _LoginApp(); } return FocusHelper(child: child); @@ -66,9 +66,7 @@ class App extends HookConsumerWidget { } class _LoginApp extends HookConsumerWidget { - const _LoginApp({required this.authState}); - - final AuthState authState; + const _LoginApp(); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/ui/home/slide_page.dart b/lib/ui/home/slide_page.dart index f21f5014ef..9852d543de 100644 --- a/lib/ui/home/slide_page.dart +++ b/lib/ui/home/slide_page.dart @@ -212,8 +212,9 @@ class _MultiAccountPopupButton extends HookConsumerWidget { title: 'Add Account', icon: Resources.assetsImagesIcAddSvg, onTap: () { - Navigator.of(context).push( - MaterialPageRoute(builder: (context) => const LandingPage()), + showDialog( + context: context, + builder: (context) => const LandingDialog(), ); }, ), diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index 51237b8214..33d66e77e8 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -10,6 +10,7 @@ import '../../utils/hive_key_values.dart'; import '../../utils/hook.dart'; import '../../utils/mixin_api_client.dart'; import '../../utils/system/package_info.dart'; +import '../../widgets/buttons.dart'; import '../../widgets/dialog.dart'; import '../../widgets/toast.dart'; import '../provider/account/account_server_provider.dart'; @@ -49,6 +50,60 @@ class LandingPage extends HookConsumerWidget { } } +/// LandingDialog for add account +class LandingDialog extends ConsumerWidget { + const LandingDialog({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final mode = ref.watch(_landingModeProvider); + Widget child; + switch (mode) { + case _LandingMode.qrcode: + child = const LandingQrCodeWidget(); + break; + case _LandingMode.mobile: + child = const LoginWithMobileWidget(); + break; + } + return Portal( + child: Scaffold( + backgroundColor: context.dynamicColor( + const Color(0xFFE5E5E5), + darkColor: const Color.fromRGBO(35, 39, 43, 1), + ), + resizeToAvoidBottomInset: false, + body: Stack( + children: [ + Center( + child: SizedBox( + width: 520, + height: 418, + child: Material( + color: context.theme.popUp, + borderRadius: const BorderRadius.all(Radius.circular(13)), + elevation: 10, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(13)), + child: child, + ), + ), + ), + ), + const Align( + alignment: Alignment.topRight, + child: Padding( + padding: EdgeInsets.all(20), + child: MixinCloseButton(), + ), + ) + ], + ), + ), + ); + } +} + class _LoginFailed extends HookConsumerWidget { const _LoginFailed(); diff --git a/lib/ui/provider/transfer_provider.dart b/lib/ui/provider/transfer_provider.dart index 8a8b923052..74690a854e 100644 --- a/lib/ui/provider/transfer_provider.dart +++ b/lib/ui/provider/transfer_provider.dart @@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../db/database_event_bus.dart'; import '../../db/mixin_database.dart'; import '../../utils/extension/extension.dart'; -import 'account_server_provider.dart'; +import 'account/account_server_provider.dart'; final tokenProvider = StreamProvider.autoDispose.family( (ref, assetId) { diff --git a/lib/widgets/message/item/transfer/safe_transfer_dialog.dart b/lib/widgets/message/item/transfer/safe_transfer_dialog.dart index 06928ff823..71e04fe689 100644 --- a/lib/widgets/message/item/transfer/safe_transfer_dialog.dart +++ b/lib/widgets/message/item/transfer/safe_transfer_dialog.dart @@ -6,7 +6,7 @@ import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart' show SnapshotType; import '../../../../db/database_event_bus.dart'; import '../../../../db/mixin_database.dart' hide Offset; -import '../../../../ui/provider/account_server_provider.dart'; +import '../../../../ui/provider/account/account_server_provider.dart'; import '../../../../ui/provider/transfer_provider.dart'; import '../../../../utils/extension/extension.dart'; import '../../../buttons.dart'; diff --git a/macos/Podfile.lock b/macos/Podfile.lock index ae51e14cd1..dc2cbc2b79 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -177,7 +177,7 @@ SPEC CHECKSUMS: sqlite3: e0a0623a33a20a47cb5921552aebc6e9e437dc91 sqlite3_flutter_libs: 9939d86d0f5a3f8f0e91feb4f333e01c9bb4cd89 super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3 - url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 + url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 diff --git a/pubspec.lock b/pubspec.lock index e95c4768c9..8f02ca0a4a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -417,10 +417,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659" + sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419" url: "https://pub.dev" source: hosted - version: "9.0.3" + version: "9.1.0" device_info_plus_platform_interface: dependency: transitive description: @@ -441,18 +441,18 @@ packages: dependency: "direct main" description: name: dio_smart_retry - sha256: "1a2d0cf73ab56bf5998b375cda2d51f45c77268e712e4073f232cdc7753a94b2" + sha256: "3d71450c19b4d91ef4c7d726a55a284bfc11eb3634f1f25006cdfab3f8595653" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" drift: dependency: "direct main" description: name: drift - sha256: ade0bbe936891c9da35ff5d9cb1f155bab357096c1506863e9ff7871857d3587 + sha256: ef2ddafe89c1f5f26767e5eada65d739de4e9d2820303f7249f15a005999d5fc url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" drift_dev: dependency: "direct dev" description: @@ -715,10 +715,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: bfc7cc3c75fe1282e8ce2e056d8fd1533f1a6848b65c379b4a5e7a9b623d3371 + sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c url: "https://pub.dev" source: hosted - version: "2.0.8" + version: "2.0.9" flutter_test: dependency: "direct dev" description: flutter @@ -1878,66 +1878,66 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" + sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba url: "https://pub.dev" source: hosted - version: "6.1.14" + version: "6.2.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd" + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" url: "https://pub.dev" source: hosted - version: "6.0.31" + version: "6.2.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.2.0" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.1.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.2.0" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.2.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" uuid: dependency: "direct main" description: @@ -2133,4 +2133,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.10.0" + flutter: ">=3.13.0" From 9472328b47c774f5f4450eaea16eede87affc32b Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:47:45 +0800 Subject: [PATCH 16/44] improve account server init --- .../account/account_server_provider.dart | 57 ++++++++++++++----- lib/ui/provider/setting_provider.dart | 5 +- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/lib/ui/provider/account/account_server_provider.dart b/lib/ui/provider/account/account_server_provider.dart index 78a22bc35f..196d824042 100644 --- a/lib/ui/provider/account/account_server_provider.dart +++ b/lib/ui/provider/account/account_server_provider.dart @@ -2,10 +2,13 @@ import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:mixin_logger/mixin_logger.dart'; import '../../../account/account_server.dart'; import '../../../blaze/blaze.dart'; import '../../../db/database.dart'; +import '../../../utils/rivepod.dart'; +import '../../../utils/synchronized.dart'; import '../conversation_provider.dart'; import '../database_provider.dart'; import '../hive_key_value_provider.dart'; @@ -14,15 +17,40 @@ import 'multi_auth_provider.dart'; typedef GetCurrentConversationId = String? Function(); -class AccountServerOpener extends AutoDisposeStreamNotifier { - AccountServerOpener._(); +class AccountServerOpener + extends DistinctStateNotifier> { + AccountServerOpener._(this.ref) : super(const AsyncValue.loading()) { + _subscription = ref.listen>( + _argsProvider, + (previous, next) { + _onNewArgs(next.valueOrNull); + }, + ); + } - @override - Stream build() async* { - final args = await ref.watch(_argsProvider.future); - if (args == null) { - return; - } + final AutoDisposeRef ref; + ProviderSubscription? _subscription; + + final _lock = Lock(); + + _Args? _previousArgs; + + Future _onNewArgs(_Args? args) => _lock.synchronized(() async { + if (_previousArgs == args) { + return; + } + _previousArgs = args; + if (args == null) { + state = const AsyncValue.loading(); + return; + } + state = await AsyncValue.guard( + () => _openAccountServer(args), + ); + }); + + Future _openAccountServer(_Args args) async { + d('create new account server'); final accountServer = AccountServer( multiAuthNotifier: args.multiAuthChangeNotifier, settingChangeNotifier: args.settingChangeNotifier, @@ -31,16 +59,19 @@ class AccountServerOpener extends AutoDisposeStreamNotifier { ref: ref, hiveKeyValues: args.hiveKeyValues, ); - await accountServer.initServer( args.userId, args.sessionId, args.identityNumber, args.privateKey, ); + return accountServer; + } - ref.onDispose(accountServer.stop); - yield accountServer; + @override + void dispose() { + _subscription?.close(); + super.dispose(); } } @@ -126,8 +157,8 @@ final _argsProvider = FutureProvider.autoDispose((ref) async { ); }); -final accountServerProvider = - StreamNotifierProvider.autoDispose( +final accountServerProvider = StateNotifierProvider.autoDispose< + AccountServerOpener, AsyncValue>( AccountServerOpener._, ); diff --git a/lib/ui/provider/setting_provider.dart b/lib/ui/provider/setting_provider.dart index c6f548c228..34d85494eb 100644 --- a/lib/ui/provider/setting_provider.dart +++ b/lib/ui/provider/setting_provider.dart @@ -298,10 +298,7 @@ class SettingChangeNotifier extends ChangeNotifier { @Deprecated('Use SettingChangeNotifier instead') const _kSettingCubitKey = 'SettingCubit'; -final settingProvider = - ChangeNotifierProvider.autoDispose((ref) { - ref.keepAlive(); - +final settingProvider = ChangeNotifierProvider((ref) { //migrate final oldJson = HydratedBloc.storage.read(_kSettingCubitKey); if (oldJson != null) { From 32ce8ccf586d056b3ec7af0b82ddd69333968490 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:38:35 +0800 Subject: [PATCH 17/44] fix account server didn't stop --- lib/ui/provider/account/account_server_provider.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ui/provider/account/account_server_provider.dart b/lib/ui/provider/account/account_server_provider.dart index 196d824042..5473b307b0 100644 --- a/lib/ui/provider/account/account_server_provider.dart +++ b/lib/ui/provider/account/account_server_provider.dart @@ -41,12 +41,15 @@ class AccountServerOpener } _previousArgs = args; if (args == null) { + unawaited(state.valueOrNull?.stop()); state = const AsyncValue.loading(); return; } + final before = state.valueOrNull; state = await AsyncValue.guard( () => _openAccountServer(args), ); + unawaited(before?.stop()); }); Future _openAccountServer(_Args args) async { @@ -71,6 +74,7 @@ class AccountServerOpener @override void dispose() { _subscription?.close(); + state.valueOrNull?.stop(); super.dispose(); } } From eab8dde4ce6d88ff76c0f0ef60dd15043facb6dc Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Wed, 8 Nov 2023 21:44:18 +0800 Subject: [PATCH 18/44] fix conversation provider --- lib/ui/home/home.dart | 1 + lib/ui/provider/conversation_provider.dart | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/ui/home/home.dart b/lib/ui/home/home.dart index 811860e201..e4a04a2bab 100644 --- a/lib/ui/home/home.dart +++ b/lib/ui/home/home.dart @@ -172,6 +172,7 @@ class _HomePage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + ref.watch(conversationProvider); // keep alive final maxWidth = constraints.maxWidth; final clampSlideWidth = (maxWidth - kResponsiveNavigationMinWidth) .clamp(kSlidePageMinWidth, kSlidePageMaxWidth); diff --git a/lib/ui/provider/conversation_provider.dart b/lib/ui/provider/conversation_provider.dart index e4516fe604..02c7b43ef0 100644 --- a/lib/ui/provider/conversation_provider.dart +++ b/lib/ui/provider/conversation_provider.dart @@ -402,10 +402,7 @@ class _LastConversationNotifier final conversationProvider = StateNotifierProvider.autoDispose< ConversationStateNotifier, ConversationState?>((ref) { - ref.keepAlive(); - final accountServerAsync = ref.watch(accountServerProvider); - if (!accountServerAsync.hasValue) { throw Exception('accountServer is not ready'); } @@ -413,11 +410,6 @@ final conversationProvider = StateNotifierProvider.autoDispose< final responsiveNavigatorNotifier = ref.watch(responsiveNavigatorProvider.notifier); - ref.listen( - accountServerProvider, - (previous, next) => ref.notifier.unselected(), - ); - return ConversationStateNotifier( accountServer: accountServerAsync.requireValue, responsiveNavigatorStateNotifier: responsiveNavigatorNotifier, From ad051eb473e130c1d55ec5b09f2ba5ab348e291d Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Thu, 9 Nov 2023 10:00:31 +0800 Subject: [PATCH 19/44] fix message font size setting --- lib/ui/setting/appearance_page.dart | 112 +++++++++--------- lib/widgets/app_bar.dart | 1 + .../message/item/action/action_message.dart | 3 +- .../item/action_card/action_message.dart | 4 +- lib/widgets/message/item/audio_message.dart | 3 +- .../message/item/contact_message_widget.dart | 9 +- lib/widgets/message/item/file_message.dart | 4 +- lib/widgets/message/item/pin_message.dart | 2 +- lib/widgets/message/item/post_message.dart | 3 +- lib/widgets/message/item/quote_message.dart | 14 ++- lib/widgets/message/item/recall_message.dart | 2 +- lib/widgets/message/item/secret_message.dart | 9 +- .../message/item/stranger_message.dart | 14 ++- lib/widgets/message/item/system_message.dart | 2 +- .../message/item/text/text_message.dart | 2 +- .../message/item/transcript_message.dart | 9 +- .../item/transfer/transfer_message.dart | 6 +- lib/widgets/message/item/unknown_message.dart | 9 +- lib/widgets/message/item/video_message.dart | 3 +- lib/widgets/message/item/waiting_message.dart | 4 +- lib/widgets/message/message.dart | 6 +- .../message/message_datetime_and_status.dart | 2 +- lib/widgets/message/message_day_time.dart | 2 +- lib/widgets/message/message_name.dart | 4 +- lib/widgets/message/message_style.dart | 9 ++ 25 files changed, 133 insertions(+), 105 deletions(-) diff --git a/lib/ui/setting/appearance_page.dart b/lib/ui/setting/appearance_page.dart index da1d7a214e..ab5e838493 100644 --- a/lib/ui/setting/appearance_page.dart +++ b/lib/ui/setting/appearance_page.dart @@ -30,9 +30,13 @@ class AppearancePage extends StatelessWidget { appBar: MixinAppBar( title: Text(context.l10n.appearance), ), - body: const Align( - alignment: Alignment.topCenter, - child: _Body(), + body: ConstrainedBox( + constraints: const BoxConstraints( + minWidth: double.infinity, + ), + child: const SingleChildScrollView( + child: _Body(), + ), ), ); } @@ -41,64 +45,62 @@ class _Body extends HookConsumerWidget { const _Body(); @override - Widget build(BuildContext context, WidgetRef ref) => SingleChildScrollView( - child: Container( - padding: const EdgeInsets.only(top: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10, bottom: 14), - child: Text( - context.l10n.theme, - style: TextStyle( - color: context.theme.secondaryText, - fontSize: 14, - ), + Widget build(BuildContext context, WidgetRef ref) => Container( + padding: const EdgeInsets.only(top: 20), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10, bottom: 14), + child: Text( + context.l10n.theme, + style: TextStyle( + color: context.theme.secondaryText, + fontSize: 14, ), ), - CellGroup( - cellBackgroundColor: context.theme.settingCellBackgroundColor, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CellItem( - title: RadioItem( - title: Text(context.l10n.followSystem), - groupValue: ref.watch(settingProvider).brightness, - onChanged: (value) => - context.settingChangeNotifier.brightness = value, - value: null, - ), - trailing: null, + ), + CellGroup( + cellBackgroundColor: context.theme.settingCellBackgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CellItem( + title: RadioItem( + title: Text(context.l10n.followSystem), + groupValue: ref.watch(settingProvider).brightness, + onChanged: (value) => + context.settingChangeNotifier.brightness = value, + value: null, ), - CellItem( - title: RadioItem( - title: Text(context.l10n.light), - groupValue: ref.watch(settingProvider).brightness, - onChanged: (value) => - context.settingChangeNotifier.brightness = value, - value: Brightness.light, - ), - trailing: null, + trailing: null, + ), + CellItem( + title: RadioItem( + title: Text(context.l10n.light), + groupValue: ref.watch(settingProvider).brightness, + onChanged: (value) => + context.settingChangeNotifier.brightness = value, + value: Brightness.light, ), - CellItem( - title: RadioItem( - title: Text(context.l10n.dark), - groupValue: ref.watch(settingProvider).brightness, - onChanged: (value) => - context.settingChangeNotifier.brightness = value, - value: Brightness.dark, - ), - trailing: null, + trailing: null, + ), + CellItem( + title: RadioItem( + title: Text(context.l10n.dark), + groupValue: ref.watch(settingProvider).brightness, + onChanged: (value) => + context.settingChangeNotifier.brightness = value, + value: Brightness.dark, ), - ], - ), + trailing: null, + ), + ], ), - const _MessageAvatarSetting(), - const _ChatTextSizeSetting(), - ], - ), + ), + const _MessageAvatarSetting(), + const _ChatTextSizeSetting(), + const SizedBox(height: 36), + ], ), ); } diff --git a/lib/widgets/app_bar.dart b/lib/widgets/app_bar.dart index 7ec3fd77bd..8a2ae9c7b5 100644 --- a/lib/widgets/app_bar.dart +++ b/lib/widgets/app_bar.dart @@ -50,6 +50,7 @@ class MixinAppBar extends StatelessWidget implements PreferredSizeWidget { elevation: 0, centerTitle: true, backgroundColor: backgroundColor ?? context.theme.primary, + scrolledUnderElevation: 0, leading: MoveWindowBarrier( child: Builder( builder: (context) => diff --git a/lib/widgets/message/item/action/action_message.dart b/lib/widgets/message/item/action/action_message.dart index 6fe5547962..1ee35560a9 100644 --- a/lib/widgets/message/item/action/action_message.dart +++ b/lib/widgets/message/item/action/action_message.dart @@ -70,7 +70,8 @@ class ActionMessage extends HookConsumerWidget { // ignore: avoid_dynamic_calls e.label, style: TextStyle( - fontSize: context.messageStyle.primaryFontSize, + fontSize: + ref.watch(messageStyleProvider).primaryFontSize, // ignore: avoid_dynamic_calls color: colorHex(e.color) ?? Colors.black, height: 1, diff --git a/lib/widgets/message/item/action_card/action_message.dart b/lib/widgets/message/item/action_card/action_message.dart index 616b92ce7b..1b25418850 100644 --- a/lib/widgets/message/item/action_card/action_message.dart +++ b/lib/widgets/message/item/action_card/action_message.dart @@ -93,7 +93,7 @@ class AppCardItem extends HookConsumerWidget { data.title, style: TextStyle( color: context.theme.text, - fontSize: context.messageStyle.secondaryFontSize, + fontSize: ref.watch(messageStyleProvider).secondaryFontSize, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -103,7 +103,7 @@ class AppCardItem extends HookConsumerWidget { maxLines: 1, style: TextStyle( color: context.theme.secondaryText, - fontSize: context.messageStyle.tertiaryFontSize, + fontSize: ref.watch(messageStyleProvider).tertiaryFontSize, ), ), ], diff --git a/lib/widgets/message/item/audio_message.dart b/lib/widgets/message/item/audio_message.dart index ee883e23e0..6dee14ab06 100644 --- a/lib/widgets/message/item/audio_message.dart +++ b/lib/widgets/message/item/audio_message.dart @@ -129,7 +129,8 @@ class AudioMessage extends HookConsumerWidget { Text( duration.asMinutesSeconds, style: TextStyle( - fontSize: context.messageStyle.tertiaryFontSize, + fontSize: + ref.watch(messageStyleProvider).tertiaryFontSize, color: context.theme.secondaryText, ), ), diff --git a/lib/widgets/message/item/contact_message_widget.dart b/lib/widgets/message/item/contact_message_widget.dart index 1d43d99bda..e74abfa07a 100644 --- a/lib/widgets/message/item/contact_message_widget.dart +++ b/lib/widgets/message/item/contact_message_widget.dart @@ -49,7 +49,7 @@ class ContactMessageWidget extends HookConsumerWidget { } } -class ContactItem extends StatelessWidget { +class ContactItem extends ConsumerWidget { const ContactItem({ super.key, required this.avatarUrl, @@ -68,7 +68,7 @@ class ContactItem extends StatelessWidget { final String identityNumber; @override - Widget build(BuildContext context) => Row( + Widget build(BuildContext context, WidgetRef ref) => Row( mainAxisSize: MainAxisSize.min, children: [ AvatarWidget( @@ -91,7 +91,8 @@ class ContactItem extends StatelessWidget { fullName?.overflow ?? '', style: TextStyle( color: context.theme.text, - fontSize: context.messageStyle.primaryFontSize, + fontSize: + ref.watch(messageStyleProvider).primaryFontSize, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -107,7 +108,7 @@ class ContactItem extends StatelessWidget { identityNumber, style: TextStyle( color: context.theme.secondaryText, - fontSize: context.messageStyle.secondaryFontSize, + fontSize: ref.watch(messageStyleProvider).secondaryFontSize, ), ), ], diff --git a/lib/widgets/message/item/file_message.dart b/lib/widgets/message/item/file_message.dart index 7e57dfaa09..38dddd5a4e 100644 --- a/lib/widgets/message/item/file_message.dart +++ b/lib/widgets/message/item/file_message.dart @@ -144,7 +144,7 @@ class MessageFile extends HookConsumerWidget { Text( mediaName.overflow, style: TextStyle( - fontSize: context.messageStyle.secondaryFontSize, + fontSize: ref.watch(messageStyleProvider).secondaryFontSize, color: context.theme.text, ), overflow: TextOverflow.ellipsis, @@ -153,7 +153,7 @@ class MessageFile extends HookConsumerWidget { Text( mediaSizeText, style: TextStyle( - fontSize: context.messageStyle.tertiaryFontSize, + fontSize: ref.watch(messageStyleProvider).tertiaryFontSize, color: context.theme.secondaryText, ), maxLines: 1, diff --git a/lib/widgets/message/item/pin_message.dart b/lib/widgets/message/item/pin_message.dart index 0ec9b6d6f3..c39bcfb5c6 100644 --- a/lib/widgets/message/item/pin_message.dart +++ b/lib/widgets/message/item/pin_message.dart @@ -93,7 +93,7 @@ class PinMessageWidget extends HookConsumerWidget { child: CustomText( text, style: TextStyle( - fontSize: context.messageStyle.secondaryFontSize, + fontSize: ref.watch(messageStyleProvider).secondaryFontSize, color: context.dynamicColor( const Color.fromRGBO(0, 0, 0, 1), ), diff --git a/lib/widgets/message/item/post_message.dart b/lib/widgets/message/item/post_message.dart index 12d69d0119..8b93f6bd45 100644 --- a/lib/widgets/message/item/post_message.dart +++ b/lib/widgets/message/item/post_message.dart @@ -32,7 +32,8 @@ class PostMessage extends HookConsumerWidget { child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), child: DefaultTextStyle.merge( - style: TextStyle(fontSize: context.messageStyle.primaryFontSize), + style: TextStyle( + fontSize: ref.watch(messageStyleProvider).primaryFontSize), child: MessagePost(showStatus: true, content: content), ), ), diff --git a/lib/widgets/message/item/quote_message.dart b/lib/widgets/message/item/quote_message.dart index 2174a3235a..089a73da55 100644 --- a/lib/widgets/message/item/quote_message.dart +++ b/lib/widgets/message/item/quote_message.dart @@ -390,7 +390,7 @@ class _QuoteImage extends HookWidget { } } -class _QuoteMessageBase extends StatelessWidget { +class _QuoteMessageBase extends ConsumerWidget { const _QuoteMessageBase({ required this.messageId, required this.quoteMessageId, @@ -414,7 +414,7 @@ class _QuoteMessageBase extends StatelessWidget { final VoidCallback? onTap; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final iterator = LineSplitter.split(description).iterator; final _description = '${iterator.moveNext() ? iterator.current : ''}${iterator.moveNext() ? '...' : ''}'; @@ -483,8 +483,9 @@ class _QuoteMessageBase extends StatelessWidget { child: CustomText( name!, style: TextStyle( - fontSize: - context.messageStyle.secondaryFontSize, + fontSize: ref + .watch(messageStyleProvider) + .secondaryFontSize, color: color, height: 1, ), @@ -503,8 +504,9 @@ class _QuoteMessageBase extends StatelessWidget { child: CustomText( _description, style: TextStyle( - fontSize: - context.messageStyle.tertiaryFontSize, + fontSize: ref + .watch(messageStyleProvider) + .tertiaryFontSize, color: context.theme.secondaryText, ), maxLines: 1, diff --git a/lib/widgets/message/item/recall_message.dart b/lib/widgets/message/item/recall_message.dart index 4bbf57d846..91632f01d2 100644 --- a/lib/widgets/message/item/recall_message.dart +++ b/lib/widgets/message/item/recall_message.dart @@ -52,7 +52,7 @@ class RecallMessage extends HookConsumerWidget { ), ]), style: TextStyle( - fontSize: context.messageStyle.primaryFontSize, + fontSize: ref.watch(messageStyleProvider).primaryFontSize, color: context.theme.text, ), ), diff --git a/lib/widgets/message/item/secret_message.dart b/lib/widgets/message/item/secret_message.dart index 4183d4ea9f..ea565e1bb0 100644 --- a/lib/widgets/message/item/secret_message.dart +++ b/lib/widgets/message/item/secret_message.dart @@ -1,15 +1,15 @@ import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../utils/extension/extension.dart'; import '../../../utils/uri_utils.dart'; - import '../message_style.dart'; -class SecretMessage extends StatelessWidget { +class SecretMessage extends ConsumerWidget { const SecretMessage({super.key}); @override - Widget build(BuildContext context) => Center( + Widget build(BuildContext context, WidgetRef ref) => Center( child: Padding( padding: const EdgeInsets.only(left: 8, right: 8, bottom: 4), child: MouseRegion( @@ -26,7 +26,8 @@ class SecretMessage extends StatelessWidget { child: Text( context.l10n.messageE2ee, style: TextStyle( - fontSize: context.messageStyle.secondaryFontSize, + fontSize: + ref.watch(messageStyleProvider).secondaryFontSize, color: context.dynamicColor( const Color.fromRGBO(0, 0, 0, 1), ), diff --git a/lib/widgets/message/item/stranger_message.dart b/lib/widgets/message/item/stranger_message.dart index ccd959e6f2..4e7e5a15af 100644 --- a/lib/widgets/message/item/stranger_message.dart +++ b/lib/widgets/message/item/stranger_message.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../enum/encrypt_category.dart'; import '../../../utils/extension/extension.dart'; @@ -9,11 +10,11 @@ import '../../toast.dart'; import '../message.dart'; import '../message_style.dart'; -class StrangerMessage extends StatelessWidget { +class StrangerMessage extends ConsumerWidget { const StrangerMessage({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final isBotConversation = useMessageConverter(converter: (state) => state.appId != null); @@ -24,7 +25,7 @@ class StrangerMessage extends StatelessWidget { ? context.l10n.chatBotReceptionTitle : context.l10n.strangerHint, style: TextStyle( - fontSize: context.messageStyle.primaryFontSize, + fontSize: ref.watch(messageStyleProvider).primaryFontSize, color: context.theme.text, ), ), @@ -73,7 +74,7 @@ class StrangerMessage extends StatelessWidget { } } -class _StrangerButton extends StatelessWidget { +class _StrangerButton extends ConsumerWidget { const _StrangerButton( this.text, { this.onTap, @@ -83,7 +84,8 @@ class _StrangerButton extends StatelessWidget { final VoidCallback? onTap; @override - Widget build(BuildContext context) => InteractiveDecoratedBox.color( + Widget build(BuildContext context, WidgetRef ref) => + InteractiveDecoratedBox.color( onTap: onTap, decoration: BoxDecoration( color: context.theme.primary, @@ -104,7 +106,7 @@ class _StrangerButton extends StatelessWidget { child: Text( text, style: TextStyle( - fontSize: context.messageStyle.primaryFontSize, + fontSize: ref.watch(messageStyleProvider).primaryFontSize, color: context.theme.accent, ), ), diff --git a/lib/widgets/message/item/system_message.dart b/lib/widgets/message/item/system_message.dart index c7370e75c3..d725dffb0e 100644 --- a/lib/widgets/message/item/system_message.dart +++ b/lib/widgets/message/item/system_message.dart @@ -55,7 +55,7 @@ class SystemMessage extends HookConsumerWidget { expireIn: int.tryParse(content ?? '0'), ), style: TextStyle( - fontSize: context.messageStyle.secondaryFontSize, + fontSize: ref.watch(messageStyleProvider).secondaryFontSize, color: context.dynamicColor( const Color.fromRGBO(0, 0, 0, 1), ), diff --git a/lib/widgets/message/item/text/text_message.dart b/lib/widgets/message/item/text/text_message.dart index 3da7962f2b..3a1cc7f93b 100644 --- a/lib/widgets/message/item/text/text_message.dart +++ b/lib/widgets/message/item/text/text_message.dart @@ -86,7 +86,7 @@ class TextMessage extends HookConsumerWidget { content: CustomText( content, style: TextStyle( - fontSize: context.messageStyle.primaryFontSize, + fontSize: ref.watch(messageStyleProvider).primaryFontSize, color: context.theme.text, ), textMatchers: [ diff --git a/lib/widgets/message/item/transcript_message.dart b/lib/widgets/message/item/transcript_message.dart index 3d6551f13b..4c1a7d1e70 100644 --- a/lib/widgets/message/item/transcript_message.dart +++ b/lib/widgets/message/item/transcript_message.dart @@ -131,7 +131,9 @@ class TranscriptMessageWidget extends HookConsumerWidget { context.l10n.transcript, style: TextStyle( color: context.theme.text, - fontSize: context.messageStyle.primaryFontSize, + fontSize: ref + .watch(messageStyleProvider) + .primaryFontSize, ), ), const Spacer(), @@ -168,8 +170,9 @@ class TranscriptMessageWidget extends HookConsumerWidget { text, style: TextStyle( color: context.theme.secondaryText, - fontSize: - context.messageStyle.tertiaryFontSize, + fontSize: ref + .watch(messageStyleProvider) + .tertiaryFontSize, ), maxLines: 1, ), diff --git a/lib/widgets/message/item/transfer/transfer_message.dart b/lib/widgets/message/item/transfer/transfer_message.dart index e401fa97ee..e0a584ca15 100644 --- a/lib/widgets/message/item/transfer/transfer_message.dart +++ b/lib/widgets/message/item/transfer/transfer_message.dart @@ -95,7 +95,9 @@ class TransferMessage extends HookConsumerWidget { snapshotAmount!.numberFormat(), style: TextStyle( color: context.theme.text, - fontSize: context.messageStyle.secondaryFontSize, + fontSize: ref + .watch(messageStyleProvider) + .secondaryFontSize, ), ); }), @@ -106,7 +108,7 @@ class TransferMessage extends HookConsumerWidget { assetSymbol, style: TextStyle( color: context.theme.secondaryText, - fontSize: context.messageStyle.tertiaryFontSize, + fontSize: ref.watch(messageStyleProvider).tertiaryFontSize, ), ), ], diff --git a/lib/widgets/message/item/unknown_message.dart b/lib/widgets/message/item/unknown_message.dart index 17084e95bd..c48594ca09 100644 --- a/lib/widgets/message/item/unknown_message.dart +++ b/lib/widgets/message/item/unknown_message.dart @@ -1,6 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../utils/extension/extension.dart'; import '../../../utils/uri_utils.dart'; @@ -9,16 +10,16 @@ import '../message_datetime_and_status.dart'; import '../message_layout.dart'; import '../message_style.dart'; -class UnknownMessage extends StatelessWidget { +class UnknownMessage extends ConsumerWidget { const UnknownMessage({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final content = RichText( text: TextSpan( text: context.l10n.messageNotSupport, style: TextStyle( - fontSize: context.messageStyle.primaryFontSize, + fontSize: ref.watch(messageStyleProvider).primaryFontSize, color: context.theme.text, ), children: [ @@ -27,7 +28,7 @@ class UnknownMessage extends StatelessWidget { mouseCursor: SystemMouseCursors.click, text: context.l10n.learnMore, style: TextStyle( - fontSize: context.messageStyle.primaryFontSize, + fontSize: ref.watch(messageStyleProvider).primaryFontSize, color: context.theme.accent, ), recognizer: TapGestureRecognizer() diff --git a/lib/widgets/message/item/video_message.dart b/lib/widgets/message/item/video_message.dart index 6314293e71..21a9aa89bc 100644 --- a/lib/widgets/message/item/video_message.dart +++ b/lib/widgets/message/item/video_message.dart @@ -210,7 +210,8 @@ class _VideoMessageOverlayInfo extends HookConsumerWidget { child: Text( durationText, style: TextStyle( - fontSize: context.messageStyle.tertiaryFontSize, + fontSize: + ref.watch(messageStyleProvider).tertiaryFontSize, color: Colors.white, ), ), diff --git a/lib/widgets/message/item/waiting_message.dart b/lib/widgets/message/item/waiting_message.dart index 6f146e0d72..5a78914c3c 100644 --- a/lib/widgets/message/item/waiting_message.dart +++ b/lib/widgets/message/item/waiting_message.dart @@ -30,7 +30,7 @@ class WaitingMessage extends HookConsumerWidget { : userFullName!, ), style: TextStyle( - fontSize: context.messageStyle.primaryFontSize, + fontSize: ref.watch(messageStyleProvider).primaryFontSize, color: context.theme.text, ), children: [ @@ -38,7 +38,7 @@ class WaitingMessage extends HookConsumerWidget { mouseCursor: SystemMouseCursors.click, text: context.l10n.learnMore, style: TextStyle( - fontSize: context.messageStyle.primaryFontSize, + fontSize: ref.watch(messageStyleProvider).primaryFontSize, color: context.theme.accent, ), recognizer: TapGestureRecognizer() diff --git a/lib/widgets/message/message.dart b/lib/widgets/message/message.dart index 5b56f47a99..11f5cac89b 100644 --- a/lib/widgets/message/message.dart +++ b/lib/widgets/message/message.dart @@ -855,11 +855,11 @@ class _MessageBubbleMargin extends HookConsumerWidget { } } -class _UnreadMessageBar extends StatelessWidget { +class _UnreadMessageBar extends ConsumerWidget { const _UnreadMessageBar(); @override - Widget build(BuildContext context) => Container( + Widget build(BuildContext context, WidgetRef ref) => Container( color: context.theme.background, padding: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 6), @@ -868,7 +868,7 @@ class _UnreadMessageBar extends StatelessWidget { context.l10n.unreadMessages, style: TextStyle( color: context.theme.secondaryText, - fontSize: context.messageStyle.secondaryFontSize, + fontSize: ref.watch(messageStyleProvider).secondaryFontSize, ), ), ); diff --git a/lib/widgets/message/message_datetime_and_status.dart b/lib/widgets/message/message_datetime_and_status.dart index b7960256d1..6a2fb5352d 100644 --- a/lib/widgets/message/message_datetime_and_status.dart +++ b/lib/widgets/message/message_datetime_and_status.dart @@ -144,7 +144,7 @@ class _MessageDatetime extends HookConsumerWidget { return Text( text, style: TextStyle( - fontSize: context.messageStyle.statusFontSize, + fontSize: ref.watch(messageStyleProvider).statusFontSize, color: color ?? context.dynamicColor( const Color.fromRGBO(131, 145, 158, 1), diff --git a/lib/widgets/message/message_day_time.dart b/lib/widgets/message/message_day_time.dart index 89eaa59c04..20100918fe 100644 --- a/lib/widgets/message/message_day_time.dart +++ b/lib/widgets/message/message_day_time.dart @@ -61,7 +61,7 @@ class _MessageDayTimeWidget extends HookConsumerWidget { dateTimeString, textAlign: TextAlign.center, style: TextStyle( - fontSize: context.messageStyle.secondaryFontSize, + fontSize: ref.watch(messageStyleProvider).secondaryFontSize, color: Colors.black, ), ), diff --git a/lib/widgets/message/message_name.dart b/lib/widgets/message/message_name.dart index 02b090e35d..7de77f1d9c 100644 --- a/lib/widgets/message/message_name.dart +++ b/lib/widgets/message/message_name.dart @@ -29,7 +29,7 @@ class MessageName extends ConsumerWidget { Widget widget = CustomText( userName, style: TextStyle( - fontSize: context.messageStyle.secondaryFontSize, + fontSize: ref.watch(messageStyleProvider).secondaryFontSize, color: getNameColorById(userId), ), ); @@ -44,7 +44,7 @@ class MessageName extends ConsumerWidget { child: Text( '@$userIdentityNumber', style: TextStyle( - fontSize: context.messageStyle.statusFontSize, + fontSize: ref.watch(messageStyleProvider).statusFontSize, color: context.theme.text.withOpacity(0.5), ), ), diff --git a/lib/widgets/message/message_style.dart b/lib/widgets/message/message_style.dart index fba0d0db08..e4cd9f2632 100644 --- a/lib/widgets/message/message_style.dart +++ b/lib/widgets/message/message_style.dart @@ -1,12 +1,21 @@ import 'package:flutter/cupertino.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../../ui/provider/setting_provider.dart'; import '../../utils/extension/extension.dart'; extension MessageStyleExt on BuildContext { + @Deprecated('message') MessageStyle get messageStyle => MessageStyle.defaultStyle + settingChangeNotifier.chatFontSizeDelta; } +final messageStyleProvider = Provider((ref) { + final chatFontSizeDelta = + ref.watch(settingProvider.select((value) => value.chatFontSizeDelta)); + return MessageStyle.defaultStyle + chatFontSizeDelta; +}); + class MessageStyle { const MessageStyle({ required this.primaryFontSize, From f518fbab4d1b57e931c55aca74441f4984e01533 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Thu, 9 Nov 2023 10:46:15 +0800 Subject: [PATCH 20/44] improve --- lib/account/account_server.dart | 5 +---- lib/ui/provider/hive_key_value_provider.dart | 11 +++++++++++ lib/ui/provider/transfer_provider.dart | 10 +++------- lib/utils/hive_key_values.dart | 4 ---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index e080e3dc36..82542e877f 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -37,7 +37,6 @@ import '../utils/app_lifecycle.dart'; import '../utils/attachment/attachment_util.dart'; import '../utils/extension/extension.dart'; import '../utils/file.dart'; -import '../utils/hive_key_values.dart'; import '../utils/logger.dart'; import '../utils/mixin_api_client.dart'; import '../utils/proxy.dart'; @@ -101,8 +100,6 @@ class AccountServer { this.identityNumber = identityNumber; this.privateKey = privateKey; - await initKeyValues(identityNumber); - await _initClient(); signalDatabase = await SignalDatabase.connect( @@ -336,7 +333,7 @@ class AccountServer { await Future.wait(jobSubscribers.map((s) => s.cancel())); jobSubscribers.clear(); - await clearKeyValues(); + await hiveKeyValues.clearAll(); try { await signalDatabase?.clear(); diff --git a/lib/ui/provider/hive_key_value_provider.dart b/lib/ui/provider/hive_key_value_provider.dart index d6b74c87dc..d705641336 100644 --- a/lib/ui/provider/hive_key_value_provider.dart +++ b/lib/ui/provider/hive_key_value_provider.dart @@ -104,6 +104,17 @@ class HiveKeyValues with EquatableMixin { showPinMessageKeyValue, scamWarningKeyValue, ]; + + Future clearAll() => Future.wait([ + accountKeyValue.delete(), + cryptoKeyValue.delete(), + sessionKeyValue.delete(), + privacyKeyValue.delete(), + downloadKeyValue.delete(), + securityKeyValue.delete(), + showPinMessageKeyValue.delete(), + scamWarningKeyValue.delete(), + ]); } final hiveKeyValueProvider = diff --git a/lib/ui/provider/transfer_provider.dart b/lib/ui/provider/transfer_provider.dart index 74690a854e..7b79818b4a 100644 --- a/lib/ui/provider/transfer_provider.dart +++ b/lib/ui/provider/transfer_provider.dart @@ -11,15 +11,13 @@ final tokenProvider = StreamProvider.autoDispose.family( return const Stream.empty(); } final database = ref.read(accountServerProvider).requireValue.database; - final stream = - database.tokenDao.tokenById(assetId).watchSingleOrNullWithStream( + return database.tokenDao.tokenById(assetId).watchSingleOrNullWithStream( eventStreams: [ DataBaseEventBus.instance.updateTokenStream .where((event) => event.contains(assetId)), ], duration: kDefaultThrottleDuration, ); - return stream; }, ); @@ -30,7 +28,7 @@ final safeSnapshotProvider = return const Stream.empty(); } final database = ref.read(accountServerProvider).requireValue.database; - final stream = database.safeSnapshotDao + return database.safeSnapshotDao .safeSnapshotById(snapshotId) .watchSingleOrNullWithStream( eventStreams: [ @@ -39,7 +37,6 @@ final safeSnapshotProvider = ], duration: kDefaultThrottleDuration, ); - return stream; }, ); @@ -49,13 +46,12 @@ final assetChainProvider = StreamProvider.autoDispose.family( return const Stream.empty(); } final database = ref.read(accountServerProvider).requireValue.database; - final stream = database.chainDao.chain(assetId).watchSingleOrNullWithStream( + return database.chainDao.chain(assetId).watchSingleOrNullWithStream( eventStreams: [ DataBaseEventBus.instance.updateAssetStream .where((event) => event.contains(assetId)), ], duration: kDefaultThrottleDuration, ); - return stream; }, ); diff --git a/lib/utils/hive_key_values.dart b/lib/utils/hive_key_values.dart index e582343eb3..2f644849f2 100644 --- a/lib/utils/hive_key_values.dart +++ b/lib/utils/hive_key_values.dart @@ -8,12 +8,8 @@ import 'package:path/path.dart' as p; import 'file.dart'; -Future initKeyValues(String identityNumber) => Future.wait([]); - Future clearKeyValues() => Future.wait([]); -Future disposeKeyValues() => Future.wait([]); - abstract class HiveKeyValue { HiveKeyValue(this.boxName); From d27460b7b2c94fb86f2f758675a2b933132acedf Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Thu, 9 Nov 2023 12:11:49 +0800 Subject: [PATCH 21/44] improve landing --- lib/ui/landing/landing.dart | 28 +- lib/ui/landing/landing_mobile.dart | 14 +- lib/ui/landing/landing_qrcode.dart | 319 +++++++++++-------- lib/ui/landing/landing_state.dart | 38 --- lib/ui/provider/hive_key_value_provider.dart | 23 +- lib/utils/hive_key_values.dart | 10 +- 6 files changed, 231 insertions(+), 201 deletions(-) delete mode 100644 lib/ui/landing/landing_state.dart diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index 33d66e77e8..3c3afd8079 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -6,7 +6,6 @@ import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import '../../crypto/signal/signal_database.dart'; import '../../utils/extension/extension.dart'; -import '../../utils/hive_key_values.dart'; import '../../utils/hook.dart'; import '../../utils/mixin_api_client.dart'; import '../../utils/system/package_info.dart'; @@ -31,6 +30,7 @@ class LandingPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + ref.watch(landingKeyValuesProvider); final accountServerHasError = ref.watch(accountServerProvider.select((value) => value.hasError)); final mode = ref.watch(_landingModeProvider); @@ -186,19 +186,22 @@ class _LoginFailed extends HookConsumerWidget { final authState = context.auth; if (authState == null) return; - final accountKeyValue = - await ref.read(currentAccountKeyValueProvider.future); + final hiveKeyValues = await ref.read(hiveKeyValueProvider( + authState.account.identityNumber, + ).future); + + final accountKeyValue = hiveKeyValues.accountKeyValue; await createClient( userId: authState.account.userId, sessionId: authState.account.sessionId, privateKey: authState.privateKey, loginByPhoneNumber: - accountKeyValue?.primarySessionId == null, + accountKeyValue.primarySessionId == null, ) .accountApi .logout(LogoutRequest(authState.account.sessionId)); - await clearKeyValues(); + await hiveKeyValues.clearAll(); final signalDb = await SignalDatabase.connect( identityNumber: authState.account.identityNumber, openForLogin: true, @@ -307,3 +310,18 @@ class LandingModeSwitchButton extends HookConsumerWidget { ); } } + +final landingIdentityNumberProvider = + StateProvider.autoDispose((ref) => null); + +final landingKeyValuesProvider = FutureProvider.autoDispose( + (ref) async { + final identityNumber = ref.watch(landingIdentityNumberProvider); + if (identityNumber == null) { + return null; + } + final hiveKeyValues = + ref.watch(hiveKeyValueProvider(identityNumber).future); + return hiveKeyValues; + }, +); diff --git a/lib/ui/landing/landing_mobile.dart b/lib/ui/landing/landing_mobile.dart index 1271e80b78..cfe97ca9b0 100644 --- a/lib/ui/landing/landing_mobile.dart +++ b/lib/ui/landing/landing_mobile.dart @@ -26,7 +26,6 @@ import '../../widgets/user/captcha_web_view_dialog.dart'; import '../../widgets/user/phone_number_input.dart'; import '../../widgets/user/verification_dialog.dart'; import '../provider/account/multi_auth_provider.dart'; -import '../provider/hive_key_value_provider.dart'; import 'landing.dart'; final _mobileClientProvider = Provider.autoDispose( @@ -170,16 +169,17 @@ class _CodeInputScene extends HookConsumerWidget { final identityNumber = response.data.identityNumber; await SignalProtocol.initSignal(identityNumber, registrationId, null); + ref.read(landingIdentityNumberProvider.notifier).state = identityNumber; final privateKey = base64Encode(sessionKey.privateKey.bytes); - final cryptoKeyValue = - await ref.read(cryptoKeyValueProvider(identityNumber).future); - cryptoKeyValue.localRegistrationId = registrationId; + final hiveKeyValues = await ref.read(landingKeyValuesProvider.future); + if (hiveKeyValues == null) { + throw Exception('hiveKeyValues is null'); + } - final sessionKeyValue = - await ref.read(sessionKeyValueProvider(identityNumber).future); - sessionKeyValue.pinToken = base64Encode(decryptPinToken( + hiveKeyValues.cryptoKeyValue.localRegistrationId = registrationId; + hiveKeyValues.sessionKeyValue.pinToken = base64Encode(decryptPinToken( response.data.pinToken, sessionKey.privateKey, )); diff --git a/lib/ui/landing/landing_qrcode.dart b/lib/ui/landing/landing_qrcode.dart index e897e35403..90c2cbd287 100644 --- a/lib/ui/landing/landing_qrcode.dart +++ b/lib/ui/landing/landing_qrcode.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; +import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -19,149 +20,8 @@ import '../../utils/platform.dart'; import '../../utils/system/package_info.dart'; import '../../widgets/qr_code.dart'; import '../provider/account/multi_auth_provider.dart'; -import '../provider/hive_key_value_provider.dart'; import 'landing.dart'; import 'landing_initialize.dart'; -import 'landing_state.dart'; - -class _QrCodeLoginNotifier extends StateNotifier { - _QrCodeLoginNotifier(this.ref) - : multiAuth = ref.read(multiAuthStateNotifierProvider.notifier), - super(const LandingState(status: LandingStatus.provisioning)) { - requestAuthUrl(); - } - - final MultiAuthStateNotifier multiAuth; - final client = createLandingClient(); - final Ref ref; - - final StreamController<(int, String, signal.ECKeyPair)> - periodicStreamController = - StreamController<(int, String, signal.ECKeyPair)>(); - - StreamSubscription? _periodicSubscription; - - void _cancelPeriodicSubscription() { - final periodicSubscription = _periodicSubscription; - _periodicSubscription = null; - unawaited(periodicSubscription?.cancel()); - } - - Future requestAuthUrl() async { - _cancelPeriodicSubscription(); - try { - final rsp = await client.provisioningApi - .getProvisioningId(Platform.operatingSystem); - final keyPair = signal.Curve.generateKeyPair(); - final pubKey = - Uri.encodeComponent(base64Encode(keyPair.publicKey.serialize())); - - state = state.copyWith( - authUrl: 'mixin://device/auth?id=${rsp.data.deviceId}&pub_key=$pubKey', - status: LandingStatus.ready, - ); - - _periodicSubscription = Stream.periodic( - const Duration(milliseconds: 1500), - (i) => i, - ) - .asyncBufferMap( - (event) => _checkLanding(event.last, rsp.data.deviceId, keyPair)) - .listen((event) {}); - } catch (error, stack) { - e('requestAuthUrl failed: $error $stack'); - state = state.needReload('Failed to request auth: $error'); - } - } - - Future _checkLanding( - int count, - String deviceId, - signal.ECKeyPair keyPair, - ) async { - if (_periodicSubscription == null) return; - - if (count > 40) { - _cancelPeriodicSubscription(); - state = state.needReload(Localization.current.qrCodeExpiredDesc); - return; - } - - String secret; - try { - secret = - (await client.provisioningApi.getProvisioning(deviceId)).data.secret; - } catch (e) { - return; - } - if (secret.isEmpty) return; - - _cancelPeriodicSubscription(); - state = state.copyWith(status: LandingStatus.provisioning); - - try { - final (acount, privateKey) = await _verify(secret, keyPair); - multiAuth.signIn(AuthState(account: acount, privateKey: privateKey)); - } catch (error, stack) { - state = state.needReload('Failed to verify: $error'); - e('_verify: $error $stack'); - } - } - - FutureOr<(Account, String)> _verify( - String secret, signal.ECKeyPair keyPair) async { - final result = - signal.decrypt(base64Encode(keyPair.privateKey.serialize()), secret); - final msg = - json.decode(String.fromCharCodes(result)) as Map; - - final edKeyPair = ed.generateKey(); - final private = base64.decode(msg['identity_key_private'] as String); - final registrationId = signal.generateRegistrationId(false); - - final sessionId = msg['session_id'] as String; - final info = await getPackageInfo(); - final appVersion = '${info.version}(${info.buildNumber})'; - final platformVersion = await getPlatformVersion(); - final rsp = await client.provisioningApi.verifyProvisioning( - ProvisioningRequest( - code: msg['provisioning_code'] as String, - userId: msg['user_id'] as String, - sessionId: sessionId, - purpose: 'SESSION', - sessionSecret: base64Encode(edKeyPair.publicKey.bytes), - appVersion: appVersion, - registrationId: registrationId, - platform: 'Desktop', - platformVersion: platformVersion, - ), - ); - - final identityNumber = rsp.data.identityNumber; - await SignalProtocol.initSignal(identityNumber, registrationId, private); - - final privateKey = base64Encode(edKeyPair.privateKey.bytes); - - final accountKeyValue = - await ref.read(accountKeyValueProvider(identityNumber).future); - accountKeyValue.primarySessionId = sessionId; - final cryptoKeyValue = - await ref.read(cryptoKeyValueProvider(identityNumber).future); - cryptoKeyValue.localRegistrationId = registrationId; - - return ( - rsp.data, - privateKey, - ); - } - - @override - Future dispose() async { - await _periodicSubscription?.cancel(); - await periodicStreamController.close(); - super.dispose(); - } -} final _qrCodeLoginProvider = StateNotifierProvider.autoDispose<_QrCodeLoginNotifier, LandingState>( @@ -341,3 +201,180 @@ class _Retry extends StatelessWidget { ), ); } + +enum LandingStatus { + needReload, + provisioning, + ready, +} + +class LandingState extends Equatable { + const LandingState({ + this.authUrl, + required this.status, + this.errorMessage, + }); + + final String? authUrl; + final LandingStatus status; + + final String? errorMessage; + + @override + List get props => [authUrl, status, errorMessage]; + + LandingState needReload(String errorMessage) => LandingState( + status: LandingStatus.needReload, + errorMessage: errorMessage, + authUrl: authUrl, + ); + + LandingState copyWith({ + String? authUrl, + LandingStatus? status, + }) => + LandingState( + authUrl: authUrl ?? this.authUrl, + status: status ?? this.status, + ); +} + +class _QrCodeLoginNotifier extends StateNotifier { + _QrCodeLoginNotifier(this.ref) + : multiAuth = ref.read(multiAuthStateNotifierProvider.notifier), + super(const LandingState(status: LandingStatus.provisioning)) { + requestAuthUrl(); + } + + final MultiAuthStateNotifier multiAuth; + final client = createLandingClient(); + final Ref ref; + + final StreamController<(int, String, signal.ECKeyPair)> + periodicStreamController = + StreamController<(int, String, signal.ECKeyPair)>(); + + StreamSubscription? _periodicSubscription; + + void _cancelPeriodicSubscription() { + final periodicSubscription = _periodicSubscription; + _periodicSubscription = null; + unawaited(periodicSubscription?.cancel()); + } + + Future requestAuthUrl() async { + _cancelPeriodicSubscription(); + try { + final rsp = await client.provisioningApi + .getProvisioningId(Platform.operatingSystem); + final keyPair = signal.Curve.generateKeyPair(); + final pubKey = + Uri.encodeComponent(base64Encode(keyPair.publicKey.serialize())); + + state = state.copyWith( + authUrl: 'mixin://device/auth?id=${rsp.data.deviceId}&pub_key=$pubKey', + status: LandingStatus.ready, + ); + + _periodicSubscription = Stream.periodic( + const Duration(milliseconds: 1500), + (i) => i, + ) + .asyncBufferMap( + (event) => _checkLanding(event.last, rsp.data.deviceId, keyPair)) + .listen((event) {}); + } catch (error, stack) { + e('requestAuthUrl failed: $error $stack'); + state = state.needReload('Failed to request auth: $error'); + } + } + + Future _checkLanding( + int count, + String deviceId, + signal.ECKeyPair keyPair, + ) async { + if (_periodicSubscription == null) return; + + if (count > 40) { + _cancelPeriodicSubscription(); + state = state.needReload(Localization.current.qrCodeExpiredDesc); + return; + } + + String secret; + try { + secret = + (await client.provisioningApi.getProvisioning(deviceId)).data.secret; + } catch (e) { + return; + } + if (secret.isEmpty) return; + + _cancelPeriodicSubscription(); + state = state.copyWith(status: LandingStatus.provisioning); + + try { + final (acount, privateKey) = await _verify(secret, keyPair); + multiAuth.signIn(AuthState(account: acount, privateKey: privateKey)); + } catch (error, stack) { + state = state.needReload('Failed to verify: $error'); + e('_verify: $error $stack'); + } + } + + FutureOr<(Account, String)> _verify( + String secret, signal.ECKeyPair keyPair) async { + final result = + signal.decrypt(base64Encode(keyPair.privateKey.serialize()), secret); + final msg = + json.decode(String.fromCharCodes(result)) as Map; + + final edKeyPair = ed.generateKey(); + final private = base64.decode(msg['identity_key_private'] as String); + final registrationId = signal.generateRegistrationId(false); + + final sessionId = msg['session_id'] as String; + final info = await getPackageInfo(); + final appVersion = '${info.version}(${info.buildNumber})'; + final platformVersion = await getPlatformVersion(); + final rsp = await client.provisioningApi.verifyProvisioning( + ProvisioningRequest( + code: msg['provisioning_code'] as String, + userId: msg['user_id'] as String, + sessionId: sessionId, + purpose: 'SESSION', + sessionSecret: base64Encode(edKeyPair.publicKey.bytes), + appVersion: appVersion, + registrationId: registrationId, + platform: 'Desktop', + platformVersion: platformVersion, + ), + ); + + final identityNumber = rsp.data.identityNumber; + await SignalProtocol.initSignal(identityNumber, registrationId, private); + ref.read(landingIdentityNumberProvider.notifier).state = identityNumber; + + final privateKey = base64Encode(edKeyPair.privateKey.bytes); + + final hiveKeyValues = await ref.read(landingKeyValuesProvider.future); + if (hiveKeyValues == null) { + throw Exception('can not init hiveKeyValues'); + } + hiveKeyValues.accountKeyValue.primarySessionId = sessionId; + hiveKeyValues.cryptoKeyValue.localRegistrationId = registrationId; + + return ( + rsp.data, + privateKey, + ); + } + + @override + Future dispose() async { + await _periodicSubscription?.cancel(); + await periodicStreamController.close(); + super.dispose(); + } +} diff --git a/lib/ui/landing/landing_state.dart b/lib/ui/landing/landing_state.dart deleted file mode 100644 index 44c8747ce4..0000000000 --- a/lib/ui/landing/landing_state.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:equatable/equatable.dart'; - -enum LandingStatus { - needReload, - provisioning, - ready, -} - -class LandingState extends Equatable { - const LandingState({ - this.authUrl, - required this.status, - this.errorMessage, - }); - - final String? authUrl; - final LandingStatus status; - - final String? errorMessage; - - @override - List get props => [authUrl, status, errorMessage]; - - LandingState needReload(String errorMessage) => LandingState( - status: LandingStatus.needReload, - errorMessage: errorMessage, - authUrl: authUrl, - ); - - LandingState copyWith({ - String? authUrl, - LandingStatus? status, - }) => - LandingState( - authUrl: authUrl ?? this.authUrl, - status: status ?? this.status, - ); -} diff --git a/lib/ui/provider/hive_key_value_provider.dart b/lib/ui/provider/hive_key_value_provider.dart index d705641336..ed0754cebc 100644 --- a/lib/ui/provider/hive_key_value_provider.dart +++ b/lib/ui/provider/hive_key_value_provider.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:mixin_logger/mixin_logger.dart'; import '../../account/account_key_value.dart'; import '../../account/scam_warning_key_value.dart'; @@ -106,20 +107,26 @@ class HiveKeyValues with EquatableMixin { ]; Future clearAll() => Future.wait([ - accountKeyValue.delete(), - cryptoKeyValue.delete(), - sessionKeyValue.delete(), - privacyKeyValue.delete(), - downloadKeyValue.delete(), - securityKeyValue.delete(), - showPinMessageKeyValue.delete(), - scamWarningKeyValue.delete(), + accountKeyValue.clear(), + cryptoKeyValue.clear(), + sessionKeyValue.clear(), + privacyKeyValue.clear(), + downloadKeyValue.clear(), + securityKeyValue.clear(), + showPinMessageKeyValue.clear(), + scamWarningKeyValue.clear(), ]); } final hiveKeyValueProvider = FutureProvider.autoDispose.family( (ref, identityNumber) async { + assert(() { + ref.onDispose(() { + w('hiveKeyValueProvider: dispose $identityNumber'); + }); + return true; + }()); final accountKeyValue = await ref.watch(accountKeyValueProvider(identityNumber).future); final cryptoKeyValue = diff --git a/lib/utils/hive_key_values.dart b/lib/utils/hive_key_values.dart index 2f644849f2..bba4a33142 100644 --- a/lib/utils/hive_key_values.dart +++ b/lib/utils/hive_key_values.dart @@ -8,8 +8,6 @@ import 'package:path/path.dart' as p; import 'file.dart'; -Future clearKeyValues() => Future.wait([]); - abstract class HiveKeyValue { HiveKeyValue(this.boxName); @@ -53,6 +51,14 @@ abstract class HiveKeyValue { _hasInit = false; } + Future clear() async { + if (!_hasInit) { + return; + } + i('HiveKeyValue: clear $boxName $_identityNumber'); + await box.clear(); + } + Future delete() async { if (!_hasInit) return; try { From 10ca32d974bb99af5db4e380137ab7927c2ab023 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Thu, 9 Nov 2023 16:19:39 +0800 Subject: [PATCH 22/44] fix hive cause logout error --- lib/account/account_key_value.dart | 6 ---- lib/account/account_server.dart | 6 ++-- lib/app.dart | 2 +- lib/ui/landing/landing.dart | 15 ++++++-- .../account/account_server_provider.dart | 26 +++++--------- .../provider/account/multi_auth_provider.dart | 16 ++++----- lib/ui/provider/hive_key_value_provider.dart | 36 +++++++++++++------ lib/ui/setting/account_delete_page.dart | 3 +- lib/ui/setting/setting_page.dart | 3 +- lib/utils/attachment/download_key_value.dart | 1 - lib/utils/hive_key_values.dart | 18 ++++------ 11 files changed, 66 insertions(+), 66 deletions(-) diff --git a/lib/account/account_key_value.dart b/lib/account/account_key_value.dart index a86f350ec4..10028e5285 100644 --- a/lib/account/account_key_value.dart +++ b/lib/account/account_key_value.dart @@ -61,10 +61,4 @@ class AccountKeyValue extends HiveKeyValue { } box.put(_keyRecentUsedEmoji, recentUsedEmoji); } - - @override - Future delete() { - _recentUsedEmoji = null; - return super.delete(); - } } diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 82542e877f..72ef434b4c 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -91,7 +91,7 @@ class AccountServer { ) async { if (sid == sessionId) return; - i('AccountServer init: $identityNumber ${database.hashCode}'); + i('AccountServer init: $identityNumber'); sid = sessionId; @@ -118,7 +118,7 @@ class AccountServer { } catch (error, stacktrace) { e('checkSignalKeys failed: $error $stacktrace'); await signOutAndClear(); - multiAuthNotifier.signOut(); + multiAuthNotifier.signOut(userId); rethrow; } @@ -145,7 +145,7 @@ class AccountServer { } } await signOutAndClear(); - multiAuthNotifier.signOut(); + multiAuthNotifier.signOut(userId); } } diff --git a/lib/app.dart b/lib/app.dart index 28805fefe4..42b50bb126 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -248,7 +248,7 @@ class _Home extends HookConsumerWidget { if (deviceId.toLowerCase() != currentDeviceId.toLowerCase()) { final multiAuthCubit = context.multiAuthChangeNotifier; await accountServer.signOutAndClear(); - multiAuthCubit.signOut(); + multiAuthCubit.signOut(context.accountServer.userId); } } catch (e) { w('checkDeviceId error: $e'); diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index 3c3afd8079..99ff80e594 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -7,6 +7,7 @@ import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import '../../crypto/signal/signal_database.dart'; import '../../utils/extension/extension.dart'; import '../../utils/hook.dart'; +import '../../utils/logger.dart'; import '../../utils/mixin_api_client.dart'; import '../../utils/system/package_info.dart'; import '../../widgets/buttons.dart'; @@ -209,7 +210,8 @@ class _LoginFailed extends HookConsumerWidget { ); await signalDb.clear(); await signalDb.close(); - context.multiAuthChangeNotifier.signOut(); + context.multiAuthChangeNotifier + .signOut(authState.account.userId); }, child: Text(context.l10n.retry), ), @@ -311,8 +313,15 @@ class LandingModeSwitchButton extends HookConsumerWidget { } } -final landingIdentityNumberProvider = - StateProvider.autoDispose((ref) => null); +final landingIdentityNumberProvider = StateProvider.autoDispose((ref) { + assert(() { + ref.onDispose(() { + w('landingIdentityNumberProvider dispose'); + }); + return true; + }()); + return null; +}); final landingKeyValuesProvider = FutureProvider.autoDispose( (ref) async { diff --git a/lib/ui/provider/account/account_server_provider.dart b/lib/ui/provider/account/account_server_provider.dart index 5473b307b0..fbd0486222 100644 --- a/lib/ui/provider/account/account_server_provider.dart +++ b/lib/ui/provider/account/account_server_provider.dart @@ -36,6 +36,7 @@ class AccountServerOpener _Args? _previousArgs; Future _onNewArgs(_Args? args) => _lock.synchronized(() async { + i('on new args: $args'); if (_previousArgs == args) { return; } @@ -53,7 +54,7 @@ class AccountServerOpener }); Future _openAccountServer(_Args args) async { - d('create new account server'); + d('create new account server: $args'); final accountServer = AccountServer( multiAuthNotifier: args.multiAuthChangeNotifier, settingChangeNotifier: args.settingChangeNotifier, @@ -128,17 +129,8 @@ final _argsProvider = FutureProvider.autoDispose((ref) async { if (database == null) { return null; } - final (userId, sessionId, identityNumber, privateKey) = - ref.watch(authProvider.select((value) => ( - value?.account.userId, - value?.account.sessionId, - value?.account.identityNumber, - value?.privateKey, - ))); - if (userId == null || - sessionId == null || - identityNumber == null || - privateKey == null) { + final auth = ref.watch(authProvider); + if (auth == null) { return null; } final multiAuthChangeNotifier = @@ -146,14 +138,14 @@ final _argsProvider = FutureProvider.autoDispose((ref) async { final settingChangeNotifier = ref.watch(settingProvider); final currentConversationId = ref.read(_currentConversationIdProvider); final hiveKeyValues = - await ref.watch(hiveKeyValueProvider(identityNumber).future); + await ref.watch(hiveKeyValueProvider(auth.account.identityNumber).future); return _Args( database: database, - userId: userId, - sessionId: sessionId, - identityNumber: identityNumber, - privateKey: privateKey, + userId: auth.userId, + sessionId: auth.account.sessionId, + identityNumber: auth.account.identityNumber, + privateKey: auth.privateKey, multiAuthChangeNotifier: multiAuthChangeNotifier, settingChangeNotifier: settingChangeNotifier, currentConversationId: currentConversationId, diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index 92e8381394..ddbc724e72 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -100,18 +100,13 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { state = MultiAuthState(auths: auths, activeUserId: state.activeUserId); } - void signOut() { - final currentUserId = state.activeUserId; - if (currentUserId == null) { - w('signOut: currentUserId is null'); - state = MultiAuthState(); - return; - } - i('sign out: $currentUserId'); + void signOut(String userId) { if (state.auths.isEmpty) return; final auths = state.auths.toList() - ..removeWhere((element) => element.userId == currentUserId); - final activeUserId = auths.lastOrNull?.userId; + ..removeWhere((element) => element.userId == userId); + final activeUserId = state.activeUserId == userId + ? auths.lastOrNull?.userId + : state.activeUserId; state = MultiAuthState(auths: auths, activeUserId: activeUserId); } @@ -121,6 +116,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { final hydratedJson = toHydratedJson(value.toJson()); HydratedBloc.storage.write(_kMultiAuthCubitKey, hydratedJson); super.state = value; + i('auth sate: $state'); } void active(String userId) { diff --git a/lib/ui/provider/hive_key_value_provider.dart b/lib/ui/provider/hive_key_value_provider.dart index ed0754cebc..c924335a1c 100644 --- a/lib/ui/provider/hive_key_value_provider.dart +++ b/lib/ui/provider/hive_key_value_provider.dart @@ -1,4 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:hive/hive.dart'; +import 'package:hive/src/hive_impl.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_logger/mixin_logger.dart'; @@ -13,15 +15,19 @@ import '../../utils/attachment/download_key_value.dart'; import '../../utils/hive_key_values.dart'; import 'account/multi_auth_provider.dart'; +final hiveProvider = + Provider.family((ref, identityNumber) => HiveImpl()); + FutureProviderFamily _createHiveKeyValueProvider( T Function() create, ) => FutureProviderFamily( (ref, identityNumber) async { + final hive = ref.watch(hiveProvider(identityNumber)); final keyValue = create(); ref.onDispose(keyValue.dispose); - await keyValue.init(identityNumber); + await keyValue.init(hive, identityNumber); return keyValue; }, ); @@ -75,6 +81,7 @@ final scamWarningKeyValueProvider = class HiveKeyValues with EquatableMixin { HiveKeyValues({ + required this.identityNumber, required this.accountKeyValue, required this.cryptoKeyValue, required this.sessionKeyValue, @@ -85,6 +92,8 @@ class HiveKeyValues with EquatableMixin { required this.scamWarningKeyValue, }); + final String identityNumber; + final AccountKeyValue accountKeyValue; final CryptoKeyValue cryptoKeyValue; final SessionKeyValue sessionKeyValue; @@ -96,6 +105,7 @@ class HiveKeyValues with EquatableMixin { @override List get props => [ + identityNumber, accountKeyValue, cryptoKeyValue, sessionKeyValue, @@ -106,16 +116,19 @@ class HiveKeyValues with EquatableMixin { scamWarningKeyValue, ]; - Future clearAll() => Future.wait([ - accountKeyValue.clear(), - cryptoKeyValue.clear(), - sessionKeyValue.clear(), - privacyKeyValue.clear(), - downloadKeyValue.clear(), - securityKeyValue.clear(), - showPinMessageKeyValue.clear(), - scamWarningKeyValue.clear(), - ]); + Future clearAll() { + i('clear hive key values: $identityNumber'); + return Future.wait([ + accountKeyValue.clear(), + cryptoKeyValue.clear(), + sessionKeyValue.clear(), + privacyKeyValue.clear(), + downloadKeyValue.clear(), + securityKeyValue.clear(), + showPinMessageKeyValue.clear(), + scamWarningKeyValue.clear(), + ]); + } } final hiveKeyValueProvider = @@ -144,6 +157,7 @@ final hiveKeyValueProvider = final scamWarningKeyValue = await ref.watch(scamWarningKeyValueProvider(identityNumber).future); return HiveKeyValues( + identityNumber: identityNumber, accountKeyValue: accountKeyValue, cryptoKeyValue: cryptoKeyValue, sessionKeyValue: sessionKeyValue, diff --git a/lib/ui/setting/account_delete_page.dart b/lib/ui/setting/account_delete_page.dart index f4c6f6f7d8..94557e264e 100644 --- a/lib/ui/setting/account_delete_page.dart +++ b/lib/ui/setting/account_delete_page.dart @@ -115,7 +115,8 @@ class AccountDeletePage extends ConsumerWidget { if (deleted) { w('account deleted'); await context.accountServer.signOutAndClear(); - context.multiAuthChangeNotifier.signOut(); + context.multiAuthChangeNotifier + .signOut(context.accountServer.userId); } } else { e('delete account no pin'); diff --git a/lib/ui/setting/setting_page.dart b/lib/ui/setting/setting_page.dart index 011851de5c..a58620f22f 100644 --- a/lib/ui/setting/setting_page.dart +++ b/lib/ui/setting/setting_page.dart @@ -153,7 +153,8 @@ class SettingPage extends HookConsumerWidget { context.accountServer.signOutAndClear(), ); if (!succeed) return; - context.multiAuthChangeNotifier.signOut(); + context.multiAuthChangeNotifier + .signOut(context.accountServer.userId); }, color: context.theme.red, trailing: const SizedBox(), diff --git a/lib/utils/attachment/download_key_value.dart b/lib/utils/attachment/download_key_value.dart index 733e92ab31..dc4c08fb8d 100644 --- a/lib/utils/attachment/download_key_value.dart +++ b/lib/utils/attachment/download_key_value.dart @@ -11,5 +11,4 @@ class DownloadKeyValue extends HiveKeyValue { Future removeMessageId(String messageId) => box.delete(messageId); - Future clear() => box.clear(); } diff --git a/lib/utils/hive_key_values.dart b/lib/utils/hive_key_values.dart index bba4a33142..e13991abb4 100644 --- a/lib/utils/hive_key_values.dart +++ b/lib/utils/hive_key_values.dart @@ -17,7 +17,7 @@ abstract class HiveKeyValue { String? _identityNumber; - Future init(String identityNumber) async { + Future init(HiveInterface hive, String identityNumber) async { if (_hasInit) { return; } @@ -34,9 +34,9 @@ abstract class HiveKeyValue { WidgetsFlutterBinding.ensureInitialized(); if (!kIsWeb) { - Hive.init(directory.absolute.path); + hive.init(directory.absolute.path); } - box = await Hive.openBox(boxName); + box = await hive.openBox(boxName); i('HiveKeyValue: open $boxName'); _identityNumber = identityNumber; _hasInit = true; @@ -59,13 +59,7 @@ abstract class HiveKeyValue { await box.clear(); } - Future delete() async { - if (!_hasInit) return; - try { - await Hive.deleteBoxFromDisk(boxName); - } catch (_) { - // ignore already deleted - } - _hasInit = false; - } + @override + String toString() => + 'HiveKeyValue{boxName: $boxName, _hasInit: $_hasInit, _identityNumber: $_identityNumber}'; } From 3fd11daecdaab7a3d8b82f9a6cda978088915efd Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:14:11 +0800 Subject: [PATCH 23/44] migrate hive crypto key value to db --- lib/account/account_server.dart | 11 +- lib/crypto/crypto_key_value.dart | 71 ++++++----- lib/crypto/signal/pre_key_util.dart | 22 ++-- lib/crypto/signal/signal_key_util.dart | 8 +- .../converter/property_group_converter.dart | 13 -- .../user_property_group_converter.dart | 15 +++ lib/db/dao/property_dao.dart | 66 +++++++++- lib/db/database.dart | 6 +- lib/db/mixin_database.dart | 2 +- lib/db/mixin_database.g.dart | 22 ++-- lib/db/moor/mixin.drift | 4 +- lib/enum/property_group.dart | 7 +- lib/ui/landing/landing.dart | 5 + lib/ui/landing/landing_mobile.dart | 1 - lib/ui/landing/landing_qrcode.dart | 1 - lib/ui/provider/database_provider.dart | 40 +++++- lib/ui/provider/hive_key_value_provider.dart | 12 +- lib/utils/db/lazy_db_key_value.dart | 118 ++++++++++++++++++ .../util => utils/db}/property_storage.dart | 10 +- lib/utils/db/user_crypto_key_value.dart | 38 ++++++ lib/utils/hive_key_values.dart | 9 -- lib/utils/property/setting_property.dart | 4 +- lib/workers/decrypt_message.dart | 3 +- test/db/property_storage_test.dart | 4 +- 24 files changed, 372 insertions(+), 120 deletions(-) delete mode 100644 lib/db/converter/property_group_converter.dart create mode 100644 lib/db/converter/user_property_group_converter.dart create mode 100644 lib/utils/db/lazy_db_key_value.dart rename lib/{db/util => utils/db}/property_storage.dart (94%) create mode 100644 lib/utils/db/user_crypto_key_value.dart diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 72ef434b4c..9f3f0fba47 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -16,7 +16,6 @@ import 'package:uuid/uuid.dart'; import '../blaze/blaze.dart'; import '../blaze/vo/pin_message_minimal.dart'; import '../constants/constants.dart'; -import '../crypto/crypto_key_value.dart'; import '../crypto/privacy_key_value.dart'; import '../crypto/signal/signal_database.dart'; import '../crypto/signal/signal_key_util.dart'; @@ -69,8 +68,6 @@ class AccountServer { final HiveKeyValues hiveKeyValues; - CryptoKeyValue get cryptoKeyValue => hiveKeyValues.cryptoKeyValue; - PrivacyKeyValue get privacyKeyValue => hiveKeyValues.privacyKeyValue; AccountKeyValue get accountKeyValue => hiveKeyValues.accountKeyValue; @@ -110,7 +107,7 @@ class AccountServer { checkSignalKeyTimer = Timer.periodic(const Duration(days: 1), (timer) async { i('refreshSignalKeys periodic'); - await checkSignalKey(client, signalDatabase!, cryptoKeyValue); + await checkSignalKey(client, signalDatabase!, database.cryptoKeyValue); }); try { @@ -334,6 +331,7 @@ class AccountServer { jobSubscribers.clear(); await hiveKeyValues.clearAll(); + await database.cryptoKeyValue.clear(); try { await signalDatabase?.clear(); @@ -742,6 +740,7 @@ class AccountServer { appActiveListener.removeListener(onActive); checkSignalKeyTimer?.cancel(); _sendEventToWorkerIsolate(MainIsolateEventType.exit); + } void release() { @@ -777,9 +776,9 @@ class AccountServer { Future checkSignalKeys() async { final hasPushSignalKeys = privacyKeyValue.hasPushSignalKeys; if (hasPushSignalKeys) { - unawaited(checkSignalKey(client, signalDatabase!, cryptoKeyValue)); + unawaited(checkSignalKey(client, signalDatabase!, database.cryptoKeyValue)); } else { - await refreshSignalKeys(client, signalDatabase!, cryptoKeyValue); + await refreshSignalKeys(client, signalDatabase!, database.cryptoKeyValue); privacyKeyValue.hasPushSignalKeys = true; } } diff --git a/lib/crypto/crypto_key_value.dart b/lib/crypto/crypto_key_value.dart index 44e84c5b87..d229b9c541 100644 --- a/lib/crypto/crypto_key_value.dart +++ b/lib/crypto/crypto_key_value.dart @@ -1,38 +1,51 @@ -// ignore: implementation_imports -import 'package:libsignal_protocol_dart/src/util/medium.dart'; +import 'dart:io'; -import '../utils/crypto_util.dart'; -import '../utils/hive_key_values.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:path/path.dart' as p; -class CryptoKeyValue extends HiveKeyValue { - CryptoKeyValue() : super(_hiveCrypto); +import '../utils/db/user_crypto_key_value.dart'; +import '../utils/file.dart'; + +class CryptoKeyValue { + CryptoKeyValue(); static const _hiveCrypto = 'crypto_box'; - static const _localRegistrationId = 'local_registration_id'; static const _nextPreKeyId = 'next_pre_key_id'; static const _nextSignedPreKeyId = 'next_signed_pre_key_id'; static const _activeSignedPreKeyId = 'active_signed_pre_key_id'; - int get localRegistrationId => - box.get(_localRegistrationId, defaultValue: 0)!; - - set localRegistrationId(int registrationId) => - box.put(_localRegistrationId, registrationId); - - int get nextPreKeyId => - box.get(_nextPreKeyId, defaultValue: generateRandomInt(maxValue))!; - - set nextPreKeyId(int preKeyId) => box.put(_nextPreKeyId, preKeyId); - - int get nextSignedPreKeyId => - box.get(_nextSignedPreKeyId, defaultValue: generateRandomInt(maxValue))!; - - set nextSignedPreKeyId(int preKeyId) => - box.put(_nextSignedPreKeyId, preKeyId); - - int get activeSignedPreKeyId => - box.get(_activeSignedPreKeyId, defaultValue: -1)!; - - set activeSignedPreKeyId(int preKeyId) => - box.put(_activeSignedPreKeyId, preKeyId); + Future migrateToNewCryptoKeyValue( + HiveInterface hive, + String identityNumber, + UserCryptoKeyValue cryptoKeyValue, + ) async { + final directory = Directory( + p.join(mixinDocumentsDirectory.path, identityNumber, _hiveCrypto)); + WidgetsFlutterBinding.ensureInitialized(); + if (!kIsWeb) { + hive.init(directory.absolute.path); + } + final exist = await hive.boxExists(_hiveCrypto); + if (!exist) { + return; + } + final box = await hive.openBox(_hiveCrypto); + final nextPreKeyId = box.get(_nextPreKeyId, defaultValue: null) as int?; + final nextSignedPreKeyId = + box.get(_nextSignedPreKeyId, defaultValue: null) as int?; + final activeSignedPreKeyId = + box.get(_activeSignedPreKeyId, defaultValue: null) as int?; + if (nextPreKeyId != null) { + await cryptoKeyValue.setNextPreKeyId(nextPreKeyId); + } + if (nextSignedPreKeyId != null) { + await cryptoKeyValue.setNextSignedPreKeyId(nextSignedPreKeyId); + } + if (activeSignedPreKeyId != null) { + await cryptoKeyValue.setActiveSignedPreKeyId(activeSignedPreKeyId); + } + await box.deleteFromDisk(); + } } diff --git a/lib/crypto/signal/pre_key_util.dart b/lib/crypto/signal/pre_key_util.dart index 16c807e988..bd335d8f59 100644 --- a/lib/crypto/signal/pre_key_util.dart +++ b/lib/crypto/signal/pre_key_util.dart @@ -2,36 +2,40 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; // ignore: implementation_imports import 'package:libsignal_protocol_dart/src/util/key_helper.dart' as helper; -import '../crypto_key_value.dart'; +import '../../utils/db/user_crypto_key_value.dart'; import 'signal_database.dart'; import 'storage/mixin_prekey_store.dart'; const batchSize = 700; Future> generatePreKeys( - SignalDatabase database, CryptoKeyValue cryptoKeyValue) async { + SignalDatabase database, UserCryptoKeyValue cryptoKeyValue) async { final preKeyStore = MixinPreKeyStore(database); - final preKeyIdOffset = cryptoKeyValue.nextPreKeyId; + final preKeyIdOffset = await cryptoKeyValue.getNextPreKeyId(); final records = helper.generatePreKeys(preKeyIdOffset, batchSize); final preKeys = []; for (final r in records) { preKeys.add(PrekeysCompanion.insert(prekeyId: r.id, record: r.serialize())); } await preKeyStore.storePreKeyList(preKeys); - cryptoKeyValue.nextPreKeyId = (preKeyIdOffset + batchSize + 1) % maxValue; + await cryptoKeyValue + .setNextPreKeyId((preKeyIdOffset + batchSize + 1) % maxValue); return records; } -Future generateSignedPreKey(IdentityKeyPair identityKeyPair, - bool active, SignalDatabase database, CryptoKeyValue cryptoKeyValue) async { +Future generateSignedPreKey( + IdentityKeyPair identityKeyPair, + bool active, + SignalDatabase database, + UserCryptoKeyValue cryptoKeyValue) async { final signedPreKeyStore = MixinPreKeyStore(database); - final signedPreKeyId = cryptoKeyValue.nextSignedPreKeyId; + final signedPreKeyId = await cryptoKeyValue.getNextSignedPreKeyId(); final record = helper.generateSignedPreKey(identityKeyPair, signedPreKeyId); await signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); - cryptoKeyValue.nextSignedPreKeyId = (signedPreKeyId + 1) % maxValue; + await cryptoKeyValue.setNextSignedPreKeyId((signedPreKeyId + 1) % maxValue); if (active) { - cryptoKeyValue.activeSignedPreKeyId = signedPreKeyId; + await cryptoKeyValue.setActiveSignedPreKeyId(signedPreKeyId); } return record; } diff --git a/lib/crypto/signal/signal_key_util.dart b/lib/crypto/signal/signal_key_util.dart index e4038c3849..fcc12d5a41 100644 --- a/lib/crypto/signal/signal_key_util.dart +++ b/lib/crypto/signal/signal_key_util.dart @@ -2,7 +2,7 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart' hide generateSignedPreKey, generatePreKeys; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; -import '../crypto_key_value.dart'; +import '../../utils/db/user_crypto_key_value.dart'; import 'identity_key_util.dart'; import 'pre_key_util.dart'; import 'signal_database.dart'; @@ -11,7 +11,7 @@ import 'signal_key_request.dart'; const int preKeyMinNum = 500; Future checkSignalKey(Client client, SignalDatabase signalDatabase, - CryptoKeyValue cryptoKeyValue) async { + UserCryptoKeyValue cryptoKeyValue) async { final response = await client.accountApi.getSignalKeyCount(); final availableKeyCount = response.data.preKeyCount; if (availableKeyCount > preKeyMinNum) { @@ -21,13 +21,13 @@ Future checkSignalKey(Client client, SignalDatabase signalDatabase, } Future> refreshSignalKeys(Client client, - SignalDatabase signalDatabase, CryptoKeyValue cryptoKeyValue) async { + SignalDatabase signalDatabase, UserCryptoKeyValue cryptoKeyValue) async { final keys = await generateKeys(signalDatabase, cryptoKeyValue); return client.accountApi.pushSignalKeys(keys.toJson()); } Future generateKeys( - SignalDatabase signalDatabase, CryptoKeyValue cryptoKeyValue) async { + SignalDatabase signalDatabase, UserCryptoKeyValue cryptoKeyValue) async { final identityKeyPair = await getIdentityKeyPair(signalDatabase); if (identityKeyPair == null) { throw InvalidKeyException('Local identity key pair is null!'); diff --git a/lib/db/converter/property_group_converter.dart b/lib/db/converter/property_group_converter.dart deleted file mode 100644 index aa1f6edca9..0000000000 --- a/lib/db/converter/property_group_converter.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:drift/drift.dart'; - -import '../../enum/property_group.dart'; - -class PropertyGroupConverter extends TypeConverter { - const PropertyGroupConverter(); - - @override - PropertyGroup fromSql(String fromDb) => PropertyGroup.values.byName(fromDb); - - @override - String toSql(PropertyGroup value) => value.name; -} diff --git a/lib/db/converter/user_property_group_converter.dart b/lib/db/converter/user_property_group_converter.dart new file mode 100644 index 0000000000..d830e37440 --- /dev/null +++ b/lib/db/converter/user_property_group_converter.dart @@ -0,0 +1,15 @@ +import 'package:drift/drift.dart'; + +import '../../enum/property_group.dart'; + +class UserPropertyGroupConverter + extends TypeConverter { + const UserPropertyGroupConverter(); + + @override + UserPropertyGroup fromSql(String fromDb) => + UserPropertyGroup.values.byName(fromDb); + + @override + String toSql(UserPropertyGroup value) => value.name; +} diff --git a/lib/db/dao/property_dao.dart b/lib/db/dao/property_dao.dart index 024c205e1a..e187bfb7c7 100644 --- a/lib/db/dao/property_dao.dart +++ b/lib/db/dao/property_dao.dart @@ -1,6 +1,7 @@ import 'package:drift/drift.dart'; import '../../enum/property_group.dart'; +import '../../utils/db/lazy_db_key_value.dart'; import '../mixin_database.dart'; part 'property_dao.g.dart'; @@ -9,31 +10,32 @@ part 'property_dao.g.dart'; include: {'../moor/dao/property.drift'}, ) class PropertyDao extends DatabaseAccessor - with _$PropertyDaoMixin { + with _$PropertyDaoMixin + implements KeyValueDao { PropertyDao(super.attachedDatabase); - Future getProperty(PropertyGroup group, String key) async { + Future getProperty(UserPropertyGroup group, String key) async { final result = await (select(properties) ..where((tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) .getSingleOrNull(); return result?.value; } - Future> getProperties(PropertyGroup group) async { + Future> getProperties(UserPropertyGroup group) async { final result = await (select(properties) ..where((tbl) => tbl.group.equalsValue(group))) .get(); return Map.fromEntries(result.map((e) => MapEntry(e.key, e.value))); } - Future removeProperty(PropertyGroup group, String key) async { + Future removeProperty(UserPropertyGroup group, String key) async { await (delete(properties) ..where((tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) .go(); } Future setProperty( - PropertyGroup group, + UserPropertyGroup group, String key, String value, ) async { @@ -46,6 +48,58 @@ class PropertyDao extends DatabaseAccessor ); } - Future clearProperties(PropertyGroup group) => + Future clearProperties(UserPropertyGroup group) => (delete(properties)..where((tbl) => tbl.group.equalsValue(group))).go(); + + @override + Future clear(UserPropertyGroup group) => + (delete(properties)..where((tbl) => tbl.group.equalsValue(group))).go(); + + @override + Future> getAll(UserPropertyGroup group) async { + final result = await (select(properties) + ..where((tbl) => tbl.group.equalsValue(group))) + .get(); + return Map.fromEntries(result.map((e) => MapEntry(e.key, e.value))); + } + + @override + Future getByKey(UserPropertyGroup group, String key) async { + final result = await (select(properties) + ..where((tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) + .getSingleOrNull(); + return result?.value; + } + + @override + Future set(UserPropertyGroup group, String key, String? value) async { + if (value != null) { + await into(properties).insertOnConflictUpdate( + PropertiesCompanion.insert( + group: group, + key: key, + value: value, + ), + ); + } else { + await (delete(properties) + ..where( + (tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) + .go(); + } + } + + @override + Stream> watchAll(UserPropertyGroup group) => + (select(properties)..where((tbl) => tbl.group.equalsValue(group))) + .watch() + .map((event) => + Map.fromEntries(event.map((e) => MapEntry(e.key, e.value)))); + + @override + Stream watchByKey(UserPropertyGroup group, String key) => (select( + properties) + ..where((tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) + .watchSingleOrNull() + .map((event) => event?.value); } diff --git a/lib/db/database.dart b/lib/db/database.dart index a539ac2bb6..2238779ef8 100644 --- a/lib/db/database.dart +++ b/lib/db/database.dart @@ -1,6 +1,7 @@ import 'dart:async'; import '../ui/provider/slide_category_provider.dart'; +import '../utils/db/user_crypto_key_value.dart'; import '../utils/extension/extension.dart'; import '../utils/logger.dart'; import '../utils/property/setting_property.dart'; @@ -35,7 +36,8 @@ import 'fts_database.dart'; import 'mixin_database.dart'; class Database { - Database(this.mixinDatabase, this.ftsDatabase) { + Database(this.mixinDatabase, this.ftsDatabase) + : cryptoKeyValue = UserCryptoKeyValue(mixinDatabase.propertyDao) { settingProperties = SettingPropertyStorage(mixinDatabase.propertyDao); } @@ -104,6 +106,8 @@ class Database { late final SettingPropertyStorage settingProperties; + final UserCryptoKeyValue cryptoKeyValue; + Future dispose() async { await mixinDatabase.close(); await ftsDatabase.close(); diff --git a/lib/db/mixin_database.dart b/lib/db/mixin_database.dart index b900d96fbf..fb50305d8f 100644 --- a/lib/db/mixin_database.dart +++ b/lib/db/mixin_database.dart @@ -12,9 +12,9 @@ import 'converter/media_status_type_converter.dart'; import 'converter/message_status_type_converter.dart'; import 'converter/millis_date_converter.dart'; import 'converter/participant_role_converter.dart'; -import 'converter/property_group_converter.dart'; import 'converter/safe_deposit_type_converter.dart'; import 'converter/safe_withdrawal_type_converter.dart'; +import 'converter/user_property_group_converter.dart'; import 'converter/user_relationship_converter.dart'; import 'dao/address_dao.dart'; import 'dao/app_dao.dart'; diff --git a/lib/db/mixin_database.g.dart b/lib/db/mixin_database.g.dart index 42c4cbeb5e..2598272618 100644 --- a/lib/db/mixin_database.g.dart +++ b/lib/db/mixin_database.g.dart @@ -14375,12 +14375,12 @@ class Properties extends Table with TableInfo { requiredDuringInsert: true, $customConstraints: 'NOT NULL'); static const VerificationMeta _groupMeta = const VerificationMeta('group'); - late final GeneratedColumnWithTypeConverter group = + late final GeneratedColumnWithTypeConverter group = GeneratedColumn('group', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true, $customConstraints: 'NOT NULL') - .withConverter(Properties.$convertergroup); + .withConverter(Properties.$convertergroup); static const VerificationMeta _valueMeta = const VerificationMeta('value'); late final GeneratedColumn value = GeneratedColumn( 'value', aliasedName, false, @@ -14435,8 +14435,8 @@ class Properties extends Table with TableInfo { return Properties(attachedDatabase, alias); } - static TypeConverter $convertergroup = - const PropertyGroupConverter(); + static TypeConverter $convertergroup = + const UserPropertyGroupConverter(); @override List get customConstraints => const ['PRIMARY KEY("key", "group")']; @override @@ -14445,7 +14445,7 @@ class Properties extends Table with TableInfo { class Propertie extends DataClass implements Insertable { final String key; - final PropertyGroup group; + final UserPropertyGroup group; final String value; const Propertie( {required this.key, required this.group, required this.value}); @@ -14474,7 +14474,7 @@ class Propertie extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return Propertie( key: serializer.fromJson(json['key']), - group: serializer.fromJson(json['group']), + group: serializer.fromJson(json['group']), value: serializer.fromJson(json['value']), ); } @@ -14483,12 +14483,12 @@ class Propertie extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return { 'key': serializer.toJson(key), - 'group': serializer.toJson(group), + 'group': serializer.toJson(group), 'value': serializer.toJson(value), }; } - Propertie copyWith({String? key, PropertyGroup? group, String? value}) => + Propertie copyWith({String? key, UserPropertyGroup? group, String? value}) => Propertie( key: key ?? this.key, group: group ?? this.group, @@ -14517,7 +14517,7 @@ class Propertie extends DataClass implements Insertable { class PropertiesCompanion extends UpdateCompanion { final Value key; - final Value group; + final Value group; final Value value; final Value rowid; const PropertiesCompanion({ @@ -14528,7 +14528,7 @@ class PropertiesCompanion extends UpdateCompanion { }); PropertiesCompanion.insert({ required String key, - required PropertyGroup group, + required UserPropertyGroup group, required String value, this.rowid = const Value.absent(), }) : key = Value(key), @@ -14550,7 +14550,7 @@ class PropertiesCompanion extends UpdateCompanion { PropertiesCompanion copyWith( {Value? key, - Value? group, + Value? group, Value? value, Value? rowid}) { return PropertiesCompanion( diff --git a/lib/db/moor/mixin.drift b/lib/db/moor/mixin.drift index a367cb0eb7..5f2d0354bc 100644 --- a/lib/db/moor/mixin.drift +++ b/lib/db/moor/mixin.drift @@ -6,7 +6,7 @@ import '../converter/message_status_type_converter.dart'; import '../converter/user_relationship_converter.dart'; import '../converter/participant_role_converter.dart'; import '../converter/millis_date_converter.dart'; -import '../converter/property_group_converter.dart'; +import '../converter/user_property_group_converter.dart'; import '../converter/safe_deposit_type_converter.dart'; import '../converter/safe_withdrawal_type_converter.dart'; import '../../enum/media_status.dart'; @@ -70,7 +70,7 @@ CREATE TABLE expired_messages (message_id TEXT NOT NULL, expire_in INTEGER NOT N CREATE TABLE chains (chain_id TEXT NOT NULL, name TEXT NOT NULL, symbol TEXT NOT NULL, icon_url TEXT NOT NULL, threshold INTEGER NOT NULL, PRIMARY KEY(chain_id)); -CREATE TABLE properties ("key" TEXT NOT NULL, "group" TEXT NOT NULL MAPPED BY `const PropertyGroupConverter()`, "value" TEXT NOT NULL, PRIMARY KEY("key", "group")); +CREATE TABLE properties ("key" TEXT NOT NULL, "group" TEXT NOT NULL MAPPED BY `const UserPropertyGroupConverter()`, "value" TEXT NOT NULL, PRIMARY KEY("key", "group")); CREATE TABLE safe_snapshots ( snapshot_id TEXT NOT NULL, diff --git a/lib/enum/property_group.dart b/lib/enum/property_group.dart index f92c07c9a7..eead21d61d 100644 --- a/lib/enum/property_group.dart +++ b/lib/enum/property_group.dart @@ -1,3 +1,8 @@ -enum PropertyGroup { +enum UserPropertyGroup { + setting, + crypto, +} + +enum AppPropertyGroup { setting, } diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index 99ff80e594..9cbf897b41 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -14,6 +14,7 @@ import '../../widgets/buttons.dart'; import '../../widgets/dialog.dart'; import '../../widgets/toast.dart'; import '../provider/account/account_server_provider.dart'; +import '../provider/database_provider.dart'; import '../provider/hive_key_value_provider.dart'; import 'landing_mobile.dart'; import 'landing_qrcode.dart'; @@ -210,6 +211,10 @@ class _LoginFailed extends HookConsumerWidget { ); await signalDb.clear(); await signalDb.close(); + final userDb = ref.read(databaseProvider).valueOrNull; + if (userDb != null) { + await userDb.cryptoKeyValue.clear(); + } context.multiAuthChangeNotifier .signOut(authState.account.userId); }, diff --git a/lib/ui/landing/landing_mobile.dart b/lib/ui/landing/landing_mobile.dart index cfe97ca9b0..514f49606e 100644 --- a/lib/ui/landing/landing_mobile.dart +++ b/lib/ui/landing/landing_mobile.dart @@ -178,7 +178,6 @@ class _CodeInputScene extends HookConsumerWidget { throw Exception('hiveKeyValues is null'); } - hiveKeyValues.cryptoKeyValue.localRegistrationId = registrationId; hiveKeyValues.sessionKeyValue.pinToken = base64Encode(decryptPinToken( response.data.pinToken, sessionKey.privateKey, diff --git a/lib/ui/landing/landing_qrcode.dart b/lib/ui/landing/landing_qrcode.dart index 90c2cbd287..584baf0ade 100644 --- a/lib/ui/landing/landing_qrcode.dart +++ b/lib/ui/landing/landing_qrcode.dart @@ -363,7 +363,6 @@ class _QrCodeLoginNotifier extends StateNotifier { throw Exception('can not init hiveKeyValues'); } hiveKeyValues.accountKeyValue.primarySessionId = sessionId; - hiveKeyValues.cryptoKeyValue.localRegistrationId = registrationId; return ( rsp.data, diff --git a/lib/ui/provider/database_provider.dart b/lib/ui/provider/database_provider.dart index 125cbda07f..12d9602fb1 100644 --- a/lib/ui/provider/database_provider.dart +++ b/lib/ui/provider/database_provider.dart @@ -1,12 +1,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_logger/mixin_logger.dart'; +import '../../crypto/crypto_key_value.dart'; import '../../db/database.dart'; import '../../db/fts_database.dart'; import '../../db/mixin_database.dart'; import '../../utils/rivepod.dart'; import '../../utils/synchronized.dart'; import 'account/multi_auth_provider.dart'; +import 'hive_key_value_provider.dart'; import 'slide_category_provider.dart'; final databaseProvider = @@ -15,9 +17,9 @@ final databaseProvider = final identityNumber = ref.watch(authAccountProvider.select((value) => value?.identityNumber)); - if (identityNumber == null) return DatabaseOpener(); + if (identityNumber == null) return DatabaseOpener(ref); - return DatabaseOpener.open(identityNumber); + return DatabaseOpener.open(identityNumber, ref); }, ); @@ -27,17 +29,21 @@ extension _DatabaseExt on MixinDatabase { } class DatabaseOpener extends DistinctStateNotifier> { - DatabaseOpener() : super(const AsyncValue.loading()); + DatabaseOpener(this.ref) : super(const AsyncValue.loading()); - DatabaseOpener.open(this.identityNumber) : super(const AsyncValue.loading()) { + DatabaseOpener.open(this.identityNumber, this.ref) + : super(const AsyncValue.loading()) { open(); } - late final String identityNumber; + String? identityNumber; + + final Ref ref; final Lock _lock = Lock(); Future open() => _lock.synchronized(() async { + final identityNumber = this.identityNumber!; d('connect to database: $identityNumber'); if (state.hasValue) { e('database already opened'); @@ -52,6 +58,11 @@ class DatabaseOpener extends DistinctStateNotifier> { ); // Do a database query, to ensure database has properly initialized. await mixinDatabase.doInitVerify(); + try { + await _onDatabaseOpenSucceed(db, identityNumber); + } catch (error, stacktrace) { + e('_onDatabaseOpenSucceed has error: $error, $stacktrace'); + } state = AsyncValue.data(db); } catch (error, stacktrace) { e('failed to open database: $error, $stacktrace'); @@ -59,6 +70,22 @@ class DatabaseOpener extends DistinctStateNotifier> { } }); + Future _onDatabaseOpenSucceed( + Database database, String identityNumber) async { + // migrate old crypto key value to new crypto key value + try { + final hive = ref.read(hiveProvider(identityNumber)); + final oldCryptoKeyValue = CryptoKeyValue(); + await oldCryptoKeyValue.migrateToNewCryptoKeyValue( + hive, + identityNumber, + database.cryptoKeyValue, + ); + } catch (error, stacktrace) { + e('migrateToNewCryptoKeyValue has error: $error, $stacktrace'); + } + } + @override Future dispose() async { await close(); @@ -66,6 +93,9 @@ class DatabaseOpener extends DistinctStateNotifier> { } Future close() async { + if (identityNumber != null) { + i('close database: $identityNumber'); + } await state.valueOrNull?.dispose(); state = const AsyncValue.loading(); } diff --git a/lib/ui/provider/hive_key_value_provider.dart b/lib/ui/provider/hive_key_value_provider.dart index c924335a1c..6268580168 100644 --- a/lib/ui/provider/hive_key_value_provider.dart +++ b/lib/ui/provider/hive_key_value_provider.dart @@ -1,3 +1,5 @@ +// ignore_for_file: implementation_imports + import 'package:equatable/equatable.dart'; import 'package:hive/hive.dart'; import 'package:hive/src/hive_impl.dart'; @@ -9,7 +11,6 @@ import '../../account/scam_warning_key_value.dart'; import '../../account/security_key_value.dart'; import '../../account/session_key_value.dart'; import '../../account/show_pin_message_key_value.dart'; -import '../../crypto/crypto_key_value.dart'; import '../../crypto/privacy_key_value.dart'; import '../../utils/attachment/download_key_value.dart'; import '../../utils/hive_key_values.dart'; @@ -32,8 +33,6 @@ FutureProviderFamily }, ); -final cryptoKeyValueProvider = _createHiveKeyValueProvider(CryptoKeyValue.new); - final accountKeyValueProvider = _createHiveKeyValueProvider(AccountKeyValue.new); @@ -83,7 +82,6 @@ class HiveKeyValues with EquatableMixin { HiveKeyValues({ required this.identityNumber, required this.accountKeyValue, - required this.cryptoKeyValue, required this.sessionKeyValue, required this.privacyKeyValue, required this.downloadKeyValue, @@ -95,7 +93,6 @@ class HiveKeyValues with EquatableMixin { final String identityNumber; final AccountKeyValue accountKeyValue; - final CryptoKeyValue cryptoKeyValue; final SessionKeyValue sessionKeyValue; final PrivacyKeyValue privacyKeyValue; final DownloadKeyValue downloadKeyValue; @@ -107,7 +104,6 @@ class HiveKeyValues with EquatableMixin { List get props => [ identityNumber, accountKeyValue, - cryptoKeyValue, sessionKeyValue, privacyKeyValue, downloadKeyValue, @@ -120,7 +116,6 @@ class HiveKeyValues with EquatableMixin { i('clear hive key values: $identityNumber'); return Future.wait([ accountKeyValue.clear(), - cryptoKeyValue.clear(), sessionKeyValue.clear(), privacyKeyValue.clear(), downloadKeyValue.clear(), @@ -142,8 +137,6 @@ final hiveKeyValueProvider = }()); final accountKeyValue = await ref.watch(accountKeyValueProvider(identityNumber).future); - final cryptoKeyValue = - await ref.watch(cryptoKeyValueProvider(identityNumber).future); final sessionKeyValue = await ref.watch(sessionKeyValueProvider(identityNumber).future); final privacyKeyValue = @@ -159,7 +152,6 @@ final hiveKeyValueProvider = return HiveKeyValues( identityNumber: identityNumber, accountKeyValue: accountKeyValue, - cryptoKeyValue: cryptoKeyValue, sessionKeyValue: sessionKeyValue, privacyKeyValue: privacyKeyValue, downloadKeyValue: downloadKeyValue, diff --git a/lib/utils/db/lazy_db_key_value.dart b/lib/utils/db/lazy_db_key_value.dart new file mode 100644 index 0000000000..6c8271337a --- /dev/null +++ b/lib/utils/db/lazy_db_key_value.dart @@ -0,0 +1,118 @@ +import 'dart:convert'; + +import 'package:mixin_logger/mixin_logger.dart'; + +import '../../enum/property_group.dart'; + +abstract class KeyValueDao { + Future getByKey(Group group, String key); + + Future> getAll(Group group); + + Future set(Group group, String key, String? value); + + Future clear(Group group); + + Stream> watchAll(Group group); + + Stream watchByKey(Group group, String key); +} + +T? _convertToType(String? value) { + if (value == null) { + return null; + } + try { + switch (T) { + case String: + return value as T; + case int: + return int.parse(value) as T; + case double: + return double.parse(value) as T; + case bool: + return (value == 'true') as T; + case Map: + throw Exception('use getMap instead'); + case List: + throw Exception('use getList instead'); + default: + return value as T; + } + } catch (error, stacktrace) { + e('failed to convert $value to type $T : $error, $stacktrace'); + return null; + } +} + +typedef BaseUserKeyValue = _BaseLazyDbKeyValue; +typedef BaseAppKeyValue = _BaseLazyDbKeyValue; + +class _BaseLazyDbKeyValue { + _BaseLazyDbKeyValue({ + required this.group, + required this.dao, + }); + + final G group; + final KeyValueDao dao; + + Stream watch(String key) => + dao.watchByKey(group, key).map(_convertToType); + + Future get(String key) async { + final value = await dao.getByKey(group, key); + return _convertToType(value); + } + + Future?> getMap(String key) async { + final value = await dao.getByKey(group, key); + if (value == null) { + return null; + } + try { + final map = jsonDecode(value) as Map; + return map.cast(); + } catch (error, stacktrace) { + e('getMap $key error: $error, $stacktrace'); + return null; + } + } + + Future?> getList(String key) async { + final value = await dao.getByKey(group, key); + if (value == null) { + return null; + } + try { + final list = jsonDecode(value) as List; + return list.cast(); + } catch (error, stacktrace) { + e('getList $key error: $error, $stacktrace'); + return null; + } + } + + Future set(String key, T? value) async { + if (value == null) { + await dao.set(group, key, null); + return; + } + switch (T) { + case String: + case int: + case double: + case bool: + await dao.set(group, key, value.toString()); + case Map: + case List: + await dao.set(group, key, jsonEncode(value)); + default: + await dao.set(group, key, value.toString()); + } + } + + Future clear() async { + await dao.clear(group); + } +} diff --git a/lib/db/util/property_storage.dart b/lib/utils/db/property_storage.dart similarity index 94% rename from lib/db/util/property_storage.dart rename to lib/utils/db/property_storage.dart index 9e7c795f31..116e3f9f7f 100644 --- a/lib/db/util/property_storage.dart +++ b/lib/utils/db/property_storage.dart @@ -3,15 +3,15 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; +import '../../db/dao/property_dao.dart'; import '../../enum/property_group.dart'; -import '../../utils/event_bus.dart'; -import '../../utils/logger.dart'; -import '../dao/property_dao.dart'; +import '../event_bus.dart'; +import '../logger.dart'; class _PropertyChangedEvent { _PropertyChangedEvent(this.group); - final PropertyGroup group; + final UserPropertyGroup group; } class PropertyStorage extends ChangeNotifier { @@ -40,7 +40,7 @@ class PropertyStorage extends ChangeNotifier { final Map _data = {}; - final PropertyGroup group; + final UserPropertyGroup group; final PropertyDao dao; diff --git a/lib/utils/db/user_crypto_key_value.dart b/lib/utils/db/user_crypto_key_value.dart new file mode 100644 index 0000000000..667fbcf835 --- /dev/null +++ b/lib/utils/db/user_crypto_key_value.dart @@ -0,0 +1,38 @@ +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; + +import '../../enum/property_group.dart'; +import '../crypto_util.dart'; +import 'lazy_db_key_value.dart'; + +class UserCryptoKeyValue extends BaseUserKeyValue { + UserCryptoKeyValue(KeyValueDao dao) + : super(group: UserPropertyGroup.crypto, dao: dao); + + static const _kNextPreKeyId = 'next_pre_key_id'; + static const _kLocalRegistrationId = 'local_registration_id'; + static const _kNextSignedPreKeyId = 'next_signed_pre_key_id'; + static const _kActiveSignedPreKeyId = 'active_signed_pre_key_id'; + + Future getNextPreKeyId() async => + (await get(_kNextPreKeyId)) ?? generateRandomInt(maxValue); + + Future setNextPreKeyId(int preKeyId) => set(_kNextPreKeyId, preKeyId); + + Future getLocalRegistrationId() async => + (await get(_kLocalRegistrationId)) ?? 0; + + Future setLocalRegistrationId(int registrationId) => + set(_kLocalRegistrationId, registrationId); + + Future getNextSignedPreKeyId() async => + (await get(_kNextSignedPreKeyId)) ?? generateRandomInt(maxValue); + + Future setNextSignedPreKeyId(int preKeyId) => + set(_kNextSignedPreKeyId, preKeyId); + + Future getActiveSignedPreKeyId() async => + (await get(_kActiveSignedPreKeyId)) ?? -1; + + Future setActiveSignedPreKeyId(int preKeyId) => + set(_kActiveSignedPreKeyId, preKeyId); +} diff --git a/lib/utils/hive_key_values.dart b/lib/utils/hive_key_values.dart index e13991abb4..c0470d37dd 100644 --- a/lib/utils/hive_key_values.dart +++ b/lib/utils/hive_key_values.dart @@ -22,16 +22,7 @@ abstract class HiveKeyValue { return; } final dbFolder = mixinDocumentsDirectory; - - final legacyBoxDirectory = Directory(p.join(dbFolder.path, boxName)); final directory = Directory(p.join(dbFolder.path, identityNumber, boxName)); - - if (legacyBoxDirectory.existsSync()) { - // copy legacy file to new file - if (directory.existsSync()) directory.deleteSync(recursive: true); - legacyBoxDirectory.renameSync(directory.path); - } - WidgetsFlutterBinding.ensureInitialized(); if (!kIsWeb) { hive.init(directory.absolute.path); diff --git a/lib/utils/property/setting_property.dart b/lib/utils/property/setting_property.dart index 5b6c512fe2..4725d75557 100644 --- a/lib/utils/property/setting_property.dart +++ b/lib/utils/property/setting_property.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'package:mixin_logger/mixin_logger.dart'; import '../../db/dao/property_dao.dart'; -import '../../db/util/property_storage.dart'; import '../../enum/property_group.dart'; +import '../db/property_storage.dart'; import '../extension/extension.dart'; import '../proxy.dart'; @@ -13,7 +13,7 @@ const _kSelectedProxyKey = 'selected_proxy'; const _kProxyListKey = 'proxy_list'; class SettingPropertyStorage extends PropertyStorage { - SettingPropertyStorage(PropertyDao dao) : super(PropertyGroup.setting, dao); + SettingPropertyStorage(PropertyDao dao) : super(UserPropertyGroup.setting, dao); bool get enableProxy => get(_kEnableProxyKey) ?? false; diff --git a/lib/workers/decrypt_message.dart b/lib/workers/decrypt_message.dart index e2e83c3142..c76d24b8d6 100644 --- a/lib/workers/decrypt_message.dart +++ b/lib/workers/decrypt_message.dart @@ -19,7 +19,6 @@ import '../blaze/vo/system_conversation_message.dart'; import '../blaze/vo/system_user_message.dart'; import '../blaze/vo/transcript_minimal.dart'; import '../constants/constants.dart'; -import '../crypto/crypto_key_value.dart'; import '../crypto/encrypted/encrypted_protocol.dart'; import '../crypto/signal/ratchet_status.dart'; import '../crypto/signal/signal_database.dart'; @@ -1281,7 +1280,7 @@ class DecryptMessage extends Injector { return; } final bm = createSyncSignalKeys(createSyncSignalKeysParam( - await generateKeys(_signalDatabase, CryptoKeyValue()))); + await generateKeys(_signalDatabase, database.cryptoKeyValue))); final result = await _sender.signalKeysChannel(bm); if (result == null) { i('Registering new pre keys...'); diff --git a/test/db/property_storage_test.dart b/test/db/property_storage_test.dart index 96df9600e1..991ce18b83 100644 --- a/test/db/property_storage_test.dart +++ b/test/db/property_storage_test.dart @@ -1,15 +1,15 @@ @TestOn('linux || mac-os') import 'package:drift/native.dart'; import 'package:flutter_app/db/mixin_database.dart'; -import 'package:flutter_app/db/util/property_storage.dart'; import 'package:flutter_app/enum/property_group.dart'; +import 'package:flutter_app/utils/db/property_storage.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('tet PropertyStorage', () async { final database = MixinDatabase(NativeDatabase.memory()); final storage = - PropertyStorage(PropertyGroup.setting, database.propertyDao); + PropertyStorage(UserPropertyGroup.setting, database.propertyDao); expect(storage.get('test_empty'), null); storage.set('test_empty', false); From aab17f98907bbf9032b8eb447f141fe352a8a42f Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:31:21 +0800 Subject: [PATCH 24/44] add migration test --- test/crypto/crypto_key_value_test.dart | 65 ++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/crypto/crypto_key_value_test.dart diff --git a/test/crypto/crypto_key_value_test.dart b/test/crypto/crypto_key_value_test.dart new file mode 100644 index 0000000000..7723dd9e9f --- /dev/null +++ b/test/crypto/crypto_key_value_test.dart @@ -0,0 +1,65 @@ +@TestOn('linux || mac-os') +import 'dart:io'; + +import 'package:drift/native.dart'; +import 'package:flutter_app/crypto/crypto_key_value.dart'; +import 'package:flutter_app/db/mixin_database.dart'; +import 'package:flutter_app/utils/db/user_crypto_key_value.dart'; +import 'package:flutter_app/utils/file.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hive/hive.dart'; +import 'package:hive/src/hive_impl.dart'; +import 'package:mixin_logger/mixin_logger.dart'; +import 'package:path/path.dart' as p; + +void main() { + late MixinDatabase database; + late HiveInterface hive; + + setUp(() { + database = MixinDatabase(NativeDatabase.memory()); + mixinDocumentsDirectory = Directory(p.join( + Directory.systemTemp.path, + 'mixin_test_hive', + )); + // remove all data + try { + if (mixinDocumentsDirectory.existsSync()) { + mixinDocumentsDirectory.deleteSync(recursive: true); + } + } catch (error, stackTrace) { + e('delete hive path error: $error, $stackTrace'); + } + mixinDocumentsDirectory.createSync(recursive: true); + hive = HiveImpl(); + }); + + tearDown(() async { + await database.close(); + }); + + test('no migration', () async { + final oldCryptoKeyValue = CryptoKeyValue(); + final cryptoKeyValue = UserCryptoKeyValue(database.propertyDao); + await oldCryptoKeyValue.migrateToNewCryptoKeyValue( + hive, 'test', cryptoKeyValue); + expect(await cryptoKeyValue.get('next_pre_key_id'), null); + expect(await cryptoKeyValue.get('next_signed_pre_key_id'), null); + expect(await cryptoKeyValue.get('active_signed_pre_key_id'), null); + }); + + test('migration', () async { + final oldCryptoKeyValue = CryptoKeyValue(); + final cryptoKeyValue = UserCryptoKeyValue(database.propertyDao); + hive.init(p.join(mixinDocumentsDirectory.path, 'test', 'crypto_box')); + final box = await hive.openBox('crypto_box'); + await box.put('next_pre_key_id', 1); + await box.put('next_signed_pre_key_id', 2); + await box.put('active_signed_pre_key_id', 3); + await oldCryptoKeyValue.migrateToNewCryptoKeyValue( + hive, 'test', cryptoKeyValue); + expect(await cryptoKeyValue.get('next_pre_key_id'), 1); + expect(await cryptoKeyValue.get('next_signed_pre_key_id'), 2); + expect(await cryptoKeyValue.get('active_signed_pre_key_id'), 3); + }); +} From 09d0783bf1ae53bc36b367b5a8a3afb06075bc2e Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Fri, 10 Nov 2023 16:13:45 +0800 Subject: [PATCH 25/44] add app database --- lib/db/app/app_database.dart | 35 +++ lib/db/app/app_database.g.dart | 244 ++++++++++++++++++ .../app_property_group_converter.dart | 15 ++ lib/db/app/drift/app.drift | 9 + lib/db/util/open_database.dart | 24 +- lib/main.dart | 10 +- lib/ui/provider/database_provider.dart | 4 + 7 files changed, 327 insertions(+), 14 deletions(-) create mode 100644 lib/db/app/app_database.dart create mode 100644 lib/db/app/app_database.g.dart create mode 100644 lib/db/app/converter/app_property_group_converter.dart create mode 100644 lib/db/app/drift/app.drift diff --git a/lib/db/app/app_database.dart b/lib/db/app/app_database.dart new file mode 100644 index 0000000000..a2f7ff2c29 --- /dev/null +++ b/lib/db/app/app_database.dart @@ -0,0 +1,35 @@ +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:path/path.dart' as p; + +import '../../enum/property_group.dart'; +import '../../utils/file.dart'; +import '../util/open_database.dart'; +import 'converter/app_property_group_converter.dart'; + +part 'app_database.g.dart'; + +/// The database for application, shared by all users. +@DriftDatabase( + include: {'drift/app.drift'}, +) +class AppDatabase extends _$AppDatabase { + AppDatabase(super.e); + + static Future connect({ + bool fromMainIsolate = false, + }) async { + final dbFilePath = p.join(mixinDocumentsDirectory.path, 'app.db'); + final queryExecutor = await createOrConnectDriftIsolate( + portName: 'one_mixin_drift_app', + debugName: 'isolate_drift_app', + fromMainIsolate: fromMainIsolate, + dbFile: File(dbFilePath), + ); + return AppDatabase(await queryExecutor.connect()); + } + + @override + int get schemaVersion => 1; +} diff --git a/lib/db/app/app_database.g.dart b/lib/db/app/app_database.g.dart new file mode 100644 index 0000000000..57c198b8e0 --- /dev/null +++ b/lib/db/app/app_database.g.dart @@ -0,0 +1,244 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_database.dart'; + +// ignore_for_file: type=lint +class Properties extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Properties(this.attachedDatabase, [this._alias]); + static const VerificationMeta _keyMeta = const VerificationMeta('key'); + late final GeneratedColumn key = GeneratedColumn( + 'key', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + static const VerificationMeta _groupMeta = const VerificationMeta('group'); + late final GeneratedColumnWithTypeConverter group = + GeneratedColumn('group', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL') + .withConverter(Properties.$convertergroup); + static const VerificationMeta _valueMeta = const VerificationMeta('value'); + late final GeneratedColumn value = GeneratedColumn( + 'value', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + @override + List get $columns => [key, group, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'properties'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('key')) { + context.handle( + _keyMeta, key.isAcceptableOrUnknown(data['key']!, _keyMeta)); + } else if (isInserting) { + context.missing(_keyMeta); + } + context.handle(_groupMeta, const VerificationResult.success()); + if (data.containsKey('value')) { + context.handle( + _valueMeta, value.isAcceptableOrUnknown(data['value']!, _valueMeta)); + } else if (isInserting) { + context.missing(_valueMeta); + } + return context; + } + + @override + Set get $primaryKey => {key, group}; + @override + Propertie map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Propertie( + key: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}key'])!, + group: Properties.$convertergroup.fromSql(attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group'])!), + value: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}value'])!, + ); + } + + @override + Properties createAlias(String alias) { + return Properties(attachedDatabase, alias); + } + + static TypeConverter $convertergroup = + const AppPropertyGroupConverter(); + @override + List get customConstraints => const ['PRIMARY KEY("key", "group")']; + @override + bool get dontWriteConstraints => true; +} + +class Propertie extends DataClass implements Insertable { + final String key; + final AppPropertyGroup group; + final String value; + const Propertie( + {required this.key, required this.group, required this.value}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + { + final converter = Properties.$convertergroup; + map['group'] = Variable(converter.toSql(group)); + } + map['value'] = Variable(value); + return map; + } + + PropertiesCompanion toCompanion(bool nullToAbsent) { + return PropertiesCompanion( + key: Value(key), + group: Value(group), + value: Value(value), + ); + } + + factory Propertie.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Propertie( + key: serializer.fromJson(json['key']), + group: serializer.fromJson(json['group']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'group': serializer.toJson(group), + 'value': serializer.toJson(value), + }; + } + + Propertie copyWith({String? key, AppPropertyGroup? group, String? value}) => + Propertie( + key: key ?? this.key, + group: group ?? this.group, + value: value ?? this.value, + ); + @override + String toString() { + return (StringBuffer('Propertie(') + ..write('key: $key, ') + ..write('group: $group, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, group, value); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Propertie && + other.key == this.key && + other.group == this.group && + other.value == this.value); +} + +class PropertiesCompanion extends UpdateCompanion { + final Value key; + final Value group; + final Value value; + final Value rowid; + const PropertiesCompanion({ + this.key = const Value.absent(), + this.group = const Value.absent(), + this.value = const Value.absent(), + this.rowid = const Value.absent(), + }); + PropertiesCompanion.insert({ + required String key, + required AppPropertyGroup group, + required String value, + this.rowid = const Value.absent(), + }) : key = Value(key), + group = Value(group), + value = Value(value); + static Insertable custom({ + Expression? key, + Expression? group, + Expression? value, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (group != null) 'group': group, + if (value != null) 'value': value, + if (rowid != null) 'rowid': rowid, + }); + } + + PropertiesCompanion copyWith( + {Value? key, + Value? group, + Value? value, + Value? rowid}) { + return PropertiesCompanion( + key: key ?? this.key, + group: group ?? this.group, + value: value ?? this.value, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (group.present) { + final converter = Properties.$convertergroup; + + map['group'] = Variable(converter.toSql(group.value)); + } + if (value.present) { + map['value'] = Variable(value.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PropertiesCompanion(') + ..write('key: $key, ') + ..write('group: $group, ') + ..write('value: $value, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + late final Properties properties = Properties(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [properties]; +} diff --git a/lib/db/app/converter/app_property_group_converter.dart b/lib/db/app/converter/app_property_group_converter.dart new file mode 100644 index 0000000000..2dab4f645b --- /dev/null +++ b/lib/db/app/converter/app_property_group_converter.dart @@ -0,0 +1,15 @@ +import 'package:drift/drift.dart'; + +import '../../../enum/property_group.dart'; + +class AppPropertyGroupConverter + extends TypeConverter { + const AppPropertyGroupConverter(); + + @override + AppPropertyGroup fromSql(String fromDb) => + AppPropertyGroup.values.byName(fromDb); + + @override + String toSql(AppPropertyGroup value) => value.name; +} diff --git a/lib/db/app/drift/app.drift b/lib/db/app/drift/app.drift new file mode 100644 index 0000000000..6a7efb5725 --- /dev/null +++ b/lib/db/app/drift/app.drift @@ -0,0 +1,9 @@ + +import '../converter/app_property_group_converter.dart'; + +CREATE TABLE properties ( + "key" TEXT NOT NULL, + "group" TEXT NOT NULL MAPPED BY `const AppPropertyGroupConverter()`, + "value" TEXT NOT NULL, + PRIMARY KEY("key", "group") +); diff --git a/lib/db/util/open_database.dart b/lib/db/util/open_database.dart index a1fca5b732..ae1a2203c5 100644 --- a/lib/db/util/open_database.dart +++ b/lib/db/util/open_database.dart @@ -45,23 +45,24 @@ Future openQueryExecutor({ final foregroundPortName = 'one_mixin_drift_foreground_${identityNumber}_$dbName'; - final writeIsolate = await _crateIsolate( - identityNumber: identityNumber, + final dbFile = File( + p.join(mixinDocumentsDirectory.path, identityNumber, '$dbName.db'), + ); + final writeIsolate = await createOrConnectDriftIsolate( portName: backgroundPortName, - dbName: dbName, + dbFile: dbFile, fromMainIsolate: fromMainIsolate, - debugName: 'isolate_drift_${dbName}_write', + debugName: 'isolate_drift_write_${identityNumber}_$dbName', ); final write = await writeIsolate.connect(); final reads = await Future.wait(List.generate(readCount, (i) async { - final isolate = await _crateIsolate( - identityNumber: identityNumber, + final isolate = await createOrConnectDriftIsolate( portName: '${foregroundPortName}_$i', - dbName: dbName, + dbFile: dbFile, fromMainIsolate: fromMainIsolate, - debugName: 'one_mixin_drift_read_$i', + debugName: 'isolate_drift_read_${identityNumber}_${dbName}_$i', ); return isolate.connect(); })); @@ -76,10 +77,9 @@ Future openQueryExecutor({ ); } -Future _crateIsolate({ - required String identityNumber, +Future createOrConnectDriftIsolate({ required String portName, - required String dbName, + required File dbFile, bool fromMainIsolate = false, required String? debugName, }) async { @@ -92,8 +92,6 @@ Future _crateIsolate({ if (existingIsolate == null) { assert(fromMainIsolate, 'Isolate should be created from main isolate'); - final dbFile = File( - p.join(mixinDocumentsDirectory.path, identityNumber, '$dbName.db')); final receivePort = ReceivePort(); await Isolate.spawn( _startBackground, diff --git a/lib/main.dart b/lib/main.dart index 94f4270039..8245cdc211 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,9 @@ import 'package:window_size/window_size.dart'; import 'app.dart'; import 'bloc/custom_bloc_observer.dart'; +import 'db/app/app_database.dart'; import 'ui/home/home.dart'; +import 'ui/provider/database_provider.dart'; import 'utils/app_lifecycle.dart'; import 'utils/event_bus.dart'; import 'utils/file.dart'; @@ -102,7 +104,13 @@ Future main(List args) async { Bloc.observer = CustomBlocObserver(); } - runApp(const ProviderScope(child: OverlaySupport.global(child: App()))); + final appDatabase = await AppDatabase.connect(fromMainIsolate: true); + runApp(ProviderScope( + overrides: [ + appDatabaseProvider.overrideWithValue(appDatabase), + ], + child: const OverlaySupport.global(child: App()), + )); if (kPlatformIsDesktop) { Size? windowSize; diff --git a/lib/ui/provider/database_provider.dart b/lib/ui/provider/database_provider.dart index 12d9602fb1..6431c08467 100644 --- a/lib/ui/provider/database_provider.dart +++ b/lib/ui/provider/database_provider.dart @@ -2,6 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mixin_logger/mixin_logger.dart'; import '../../crypto/crypto_key_value.dart'; +import '../../db/app/app_database.dart'; import '../../db/database.dart'; import '../../db/fts_database.dart'; import '../../db/mixin_database.dart'; @@ -11,6 +12,9 @@ import 'account/multi_auth_provider.dart'; import 'hive_key_value_provider.dart'; import 'slide_category_provider.dart'; +final appDatabaseProvider = + Provider((ref) => throw UnimplementedError()); + final databaseProvider = StateNotifierProvider.autoDispose>( (ref) { From 8529a062ee056f855ac85947d207805087493ece Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Sat, 11 Nov 2023 15:38:06 +0800 Subject: [PATCH 26/44] refactor setting property to app setting key value --- lib/account/account_server.dart | 14 +- lib/blaze/blaze.dart | 11 +- lib/db/app/app_database.dart | 8 + lib/db/app/app_database.g.dart | 2 + lib/db/app/dao/app_key_value_dao.dart | 72 +++++++++ lib/db/app/dao/app_key_value_dao.g.dart | 8 + lib/db/dao/property_dao.dart | 4 + lib/db/database.dart | 7 +- lib/enum/property_group.dart | 1 + lib/ui/provider/app_key_value_provider.dart | 7 + lib/ui/setting/proxy_page.dart | 55 ++++--- lib/utils/attachment/attachment_util.dart | 7 +- .../app_setting_key_value.dart} | 28 ++-- lib/utils/db/lazy_db_key_value.dart | 119 +++++++++++++-- lib/utils/db/property_storage.dart | 141 ------------------ lib/utils/db/user_crypto_key_value.dart | 2 +- lib/utils/proxy.dart | 10 +- lib/widgets/cache_image.dart | 11 +- lib/workers/message_worker_isolate.dart | 8 +- .../db_key_value_test.dart} | 38 +++-- 20 files changed, 308 insertions(+), 245 deletions(-) create mode 100644 lib/db/app/dao/app_key_value_dao.dart create mode 100644 lib/db/app/dao/app_key_value_dao.g.dart create mode 100644 lib/ui/provider/app_key_value_provider.dart rename lib/utils/{property/setting_property.dart => db/app_setting_key_value.dart} (68%) delete mode 100644 lib/utils/db/property_storage.dart rename test/{db/property_storage_test.dart => utils/db_key_value_test.dart} (61%) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 9f3f0fba47..11485513da 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -30,6 +30,7 @@ import '../enum/encrypt_category.dart'; import '../enum/message_category.dart'; import '../ui/provider/account/account_server_provider.dart'; import '../ui/provider/account/multi_auth_provider.dart'; +import '../ui/provider/app_key_value_provider.dart'; import '../ui/provider/hive_key_value_provider.dart'; import '../ui/provider/setting_provider.dart'; import '../utils/app_lifecycle.dart'; @@ -169,10 +170,15 @@ class AccountServer { }, ), ], - )..configProxySetting(database.settingProperties); + )..configProxySetting(ref.read(settingKeyValueProvider)); attachmentUtil = AttachmentUtil.init( - client, database, identityNumber, hiveKeyValues.downloadKeyValue); + client, + database, + identityNumber, + hiveKeyValues.downloadKeyValue, + ref.read(settingKeyValueProvider), + ); _sendMessageHelper = SendMessageHelper(database, attachmentUtil, addSendingJob); @@ -740,7 +746,6 @@ class AccountServer { appActiveListener.removeListener(onActive); checkSignalKeyTimer?.cancel(); _sendEventToWorkerIsolate(MainIsolateEventType.exit); - } void release() { @@ -776,7 +781,8 @@ class AccountServer { Future checkSignalKeys() async { final hasPushSignalKeys = privacyKeyValue.hasPushSignalKeys; if (hasPushSignalKeys) { - unawaited(checkSignalKey(client, signalDatabase!, database.cryptoKeyValue)); + unawaited( + checkSignalKey(client, signalDatabase!, database.cryptoKeyValue)); } else { await refreshSignalKeys(client, signalDatabase!, database.cryptoKeyValue); privacyKeyValue.hasPushSignalKeys = true; diff --git a/lib/blaze/blaze.dart b/lib/blaze/blaze.dart index 66ae1d96aa..b35e8ee289 100644 --- a/lib/blaze/blaze.dart +++ b/lib/blaze/blaze.dart @@ -12,6 +12,7 @@ import '../constants/constants.dart'; import '../db/database.dart'; import '../db/extension/job.dart'; import '../db/mixin_database.dart'; +import '../utils/db/app_setting_key_value.dart'; import '../utils/extension/extension.dart'; import '../utils/logger.dart'; import '../utils/proxy.dart'; @@ -42,9 +43,10 @@ class Blaze { this.userAgent, this.ackJob, this.floodJob, + this.settingKeyValue, ) { - database.settingProperties.addListener(_onProxySettingChanged); - proxyConfig = database.settingProperties.activatedProxy; + settingKeyValue.addListener(_onProxySettingChanged); + proxyConfig = settingKeyValue.activatedProxy; } final String userId; @@ -56,6 +58,7 @@ class Blaze { final FloodJob floodJob; final String? userAgent; + final AppSettingKeyValue settingKeyValue; ProxyConfig? proxyConfig; @@ -347,7 +350,7 @@ class Blaze { } void _onProxySettingChanged() { - final url = database.settingProperties.activatedProxy; + final url = settingKeyValue.activatedProxy; if (url == proxyConfig) { return; } @@ -357,7 +360,7 @@ class Blaze { } void dispose() { - database.settingProperties.removeListener(_onProxySettingChanged); + settingKeyValue.removeListener(_onProxySettingChanged); _disconnect(); _connectedStateBehaviorSubject.close(); } diff --git a/lib/db/app/app_database.dart b/lib/db/app/app_database.dart index a2f7ff2c29..58f0e20b72 100644 --- a/lib/db/app/app_database.dart +++ b/lib/db/app/app_database.dart @@ -4,15 +4,20 @@ import 'package:drift/drift.dart'; import 'package:path/path.dart' as p; import '../../enum/property_group.dart'; +import '../../utils/db/app_setting_key_value.dart'; import '../../utils/file.dart'; import '../util/open_database.dart'; import 'converter/app_property_group_converter.dart'; +import 'dao/app_key_value_dao.dart'; part 'app_database.g.dart'; /// The database for application, shared by all users. @DriftDatabase( include: {'drift/app.drift'}, + daos: [ + AppKeyValueDao, + ], ) class AppDatabase extends _$AppDatabase { AppDatabase(super.e); @@ -30,6 +35,9 @@ class AppDatabase extends _$AppDatabase { return AppDatabase(await queryExecutor.connect()); } + late final AppSettingKeyValue settingKeyValue = + AppSettingKeyValue(appKeyValueDao); + @override int get schemaVersion => 1; } diff --git a/lib/db/app/app_database.g.dart b/lib/db/app/app_database.g.dart index 57c198b8e0..22a8987afc 100644 --- a/lib/db/app/app_database.g.dart +++ b/lib/db/app/app_database.g.dart @@ -236,6 +236,8 @@ class PropertiesCompanion extends UpdateCompanion { abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); late final Properties properties = Properties(this); + late final AppKeyValueDao appKeyValueDao = + AppKeyValueDao(this as AppDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); diff --git a/lib/db/app/dao/app_key_value_dao.dart b/lib/db/app/dao/app_key_value_dao.dart new file mode 100644 index 0000000000..44af6c5816 --- /dev/null +++ b/lib/db/app/dao/app_key_value_dao.dart @@ -0,0 +1,72 @@ +import 'package:drift/drift.dart'; + +import '../../../enum/property_group.dart'; +import '../../../utils/db/lazy_db_key_value.dart'; +import '../app_database.dart'; + +part 'app_key_value_dao.g.dart'; + +@DriftAccessor( + include: {'../drift/app.drift'}, +) +class AppKeyValueDao extends DatabaseAccessor + with _$AppKeyValueDaoMixin + implements KeyValueDao { + AppKeyValueDao(super.attachedDatabase); + + @override + Future clear(AppPropertyGroup group) => + (delete(properties)..where((tbl) => tbl.group.equalsValue(group))).go(); + + @override + Future> getAll(AppPropertyGroup group) async { + final result = await (select(properties) + ..where((tbl) => tbl.group.equalsValue(group))) + .get(); + return Map.fromEntries(result.map((e) => MapEntry(e.key, e.value))); + } + + @override + Future getByKey(AppPropertyGroup group, String key) async { + final result = await (select(properties) + ..where((tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) + .getSingleOrNull(); + return result?.value; + } + + @override + Future set(AppPropertyGroup group, String key, String? value) async { + if (value != null) { + await into(properties).insertOnConflictUpdate( + PropertiesCompanion.insert( + group: group, + key: key, + value: value, + ), + ); + } else { + await (delete(properties) + ..where( + (tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) + .go(); + } + } + + @override + Stream> watchAll(AppPropertyGroup group) => + (select(properties)..where((tbl) => tbl.group.equalsValue(group))) + .watch() + .map((event) => + Map.fromEntries(event.map((e) => MapEntry(e.key, e.value)))); + + @override + Stream watchByKey(AppPropertyGroup group, String key) => (select( + properties) + ..where((tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) + .watchSingleOrNull() + .map((event) => event?.value); + + @override + Stream watchTableHasChanged(AppPropertyGroup group) => + db.tableUpdates(TableUpdateQuery.onTable(db.properties)); +} diff --git a/lib/db/app/dao/app_key_value_dao.g.dart b/lib/db/app/dao/app_key_value_dao.g.dart new file mode 100644 index 0000000000..f5413cb9e2 --- /dev/null +++ b/lib/db/app/dao/app_key_value_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_key_value_dao.dart'; + +// ignore_for_file: type=lint +mixin _$AppKeyValueDaoMixin on DatabaseAccessor { + Properties get properties => attachedDatabase.properties; +} diff --git a/lib/db/dao/property_dao.dart b/lib/db/dao/property_dao.dart index e187bfb7c7..9936d51895 100644 --- a/lib/db/dao/property_dao.dart +++ b/lib/db/dao/property_dao.dart @@ -102,4 +102,8 @@ class PropertyDao extends DatabaseAccessor ..where((tbl) => tbl.group.equalsValue(group) & tbl.key.equals(key))) .watchSingleOrNull() .map((event) => event?.value); + + @override + Stream watchTableHasChanged(UserPropertyGroup group) => + db.tableUpdates(TableUpdateQuery.onTable(db.properties)); } diff --git a/lib/db/database.dart b/lib/db/database.dart index 2238779ef8..9c698ed6db 100644 --- a/lib/db/database.dart +++ b/lib/db/database.dart @@ -4,7 +4,6 @@ import '../ui/provider/slide_category_provider.dart'; import '../utils/db/user_crypto_key_value.dart'; import '../utils/extension/extension.dart'; import '../utils/logger.dart'; -import '../utils/property/setting_property.dart'; import 'dao/app_dao.dart'; import 'dao/asset_dao.dart'; import 'dao/chain_dao.dart'; @@ -37,9 +36,7 @@ import 'mixin_database.dart'; class Database { Database(this.mixinDatabase, this.ftsDatabase) - : cryptoKeyValue = UserCryptoKeyValue(mixinDatabase.propertyDao) { - settingProperties = SettingPropertyStorage(mixinDatabase.propertyDao); - } + : cryptoKeyValue = UserCryptoKeyValue(mixinDatabase.propertyDao); final MixinDatabase mixinDatabase; @@ -104,8 +101,6 @@ class Database { ExpiredMessageDao get expiredMessageDao => mixinDatabase.expiredMessageDao; - late final SettingPropertyStorage settingProperties; - final UserCryptoKeyValue cryptoKeyValue; Future dispose() async { diff --git a/lib/enum/property_group.dart b/lib/enum/property_group.dart index eead21d61d..5d136425b8 100644 --- a/lib/enum/property_group.dart +++ b/lib/enum/property_group.dart @@ -1,4 +1,5 @@ enum UserPropertyGroup { + // Legacy setting which save proxy settings, unused now. setting, crypto, } diff --git a/lib/ui/provider/app_key_value_provider.dart b/lib/ui/provider/app_key_value_provider.dart new file mode 100644 index 0000000000..d9db793934 --- /dev/null +++ b/lib/ui/provider/app_key_value_provider.dart @@ -0,0 +1,7 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../utils/db/app_setting_key_value.dart'; +import 'database_provider.dart'; + +final settingKeyValueProvider = ChangeNotifierProvider( + (ref) => ref.watch(appDatabaseProvider).settingKeyValue); diff --git a/lib/ui/setting/proxy_page.dart b/lib/ui/setting/proxy_page.dart index 1a017038e1..36dd7f9f2f 100644 --- a/lib/ui/setting/proxy_page.dart +++ b/lib/ui/setting/proxy_page.dart @@ -8,14 +8,16 @@ import 'package:uuid/uuid.dart'; import '../../constants/constants.dart'; import '../../constants/resources.dart'; +import '../../utils/db/app_setting_key_value.dart'; import '../../utils/extension/extension.dart'; -import '../../utils/hook.dart'; import '../../utils/logger.dart'; import '../../utils/proxy.dart'; import '../../widgets/action_button.dart'; import '../../widgets/app_bar.dart'; import '../../widgets/cell.dart'; import '../../widgets/dialog.dart'; +import '../provider/app_key_value_provider.dart'; +import '../provider/database_provider.dart'; class ProxyPage extends StatelessWidget { const ProxyPage({super.key}); @@ -45,17 +47,11 @@ class _ProxySettingWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final enableProxy = useListenableConverter( - context.database.settingProperties, - converter: (settingProperties) => settingProperties.enableProxy, - ).data ?? - false; - final hasProxyConfig = useListenableConverter( - context.database.settingProperties, - converter: (settingProperties) => - settingProperties.proxyList.isNotEmpty, - ).data ?? - false; + final enableProxy = + ref.watch(settingKeyValueProvider.select((value) => value.enableProxy)); + final hasProxyConfig = ref.watch(settingKeyValueProvider.select( + (value) => value.proxyList.isNotEmpty, + )); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -70,8 +66,9 @@ class _ProxySettingWidget extends HookConsumerWidget { value: hasProxyConfig && enableProxy, onChanged: !hasProxyConfig ? null - : (bool value) => context - .database.settingProperties.enableProxy = value, + : (bool value) => ref + .read(settingKeyValueProvider.notifier) + .enableProxy = value, )), ), ), @@ -108,15 +105,10 @@ class _ProxyItemList extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final proxyList = useListenableConverter( - context.database.settingProperties, - converter: (settingProperties) => settingProperties.proxyList, - ).data ?? - const []; - final selectedProxyId = useListenableConverter( - context.database.settingProperties, - converter: (settingProperties) => settingProperties.selectedProxyId, - ).data ?? + final proxyList = + ref.watch(settingKeyValueProvider.select((value) => value.proxyList)); + final selectedProxyId = ref.watch( + settingKeyValueProvider.select((value) => value.selectedProxyId)) ?? proxyList.firstOrNull?.id; return Column( children: proxyList @@ -131,7 +123,7 @@ class _ProxyItemList extends HookConsumerWidget { } } -class _ProxyItemWidget extends StatelessWidget { +class _ProxyItemWidget extends ConsumerWidget { const _ProxyItemWidget({ required this.proxy, required this.selected, @@ -142,7 +134,7 @@ class _ProxyItemWidget extends StatelessWidget { final bool selected; @override - Widget build(BuildContext context) => Material( + Widget build(BuildContext context, WidgetRef ref) => Material( color: context.theme.settingCellBackgroundColor, child: ListTile( leading: SizedBox( @@ -175,10 +167,12 @@ class _ProxyItemWidget extends StatelessWidget { name: Resources.assetsImagesDeleteSvg, color: context.theme.icon, onTap: () { - context.database.settingProperties.removeProxy(proxy.id); + final settingKeyValue = ref.read(settingKeyValueProvider.notifier) + ..removeProxy(proxy.id); if (selected) { - context.database.settingProperties.selectedProxyId = null; - context.database.settingProperties.enableProxy = false; + settingKeyValue + ..selectedProxyId = null + ..enableProxy = false; } }, ), @@ -186,7 +180,8 @@ class _ProxyItemWidget extends StatelessWidget { if (selected) { return; } - context.database.settingProperties.selectedProxyId = proxy.id; + ref.read(settingKeyValueProvider.notifier).selectedProxyId = + proxy.id; }, ), ); @@ -278,7 +273,7 @@ class _ProxyAddDialog extends HookConsumerWidget { id: id, ); i('add proxy config: ${config.type} ${config.host}:${config.port}'); - context.database.settingProperties.addProxy(config); + ref.read(appDatabaseProvider).settingKeyValue.addProxy(config); Navigator.pop(context); }, child: Text(context.l10n.add), diff --git a/lib/utils/attachment/attachment_util.dart b/lib/utils/attachment/attachment_util.dart index 2622b02e2c..5f16a4e852 100644 --- a/lib/utils/attachment/attachment_util.dart +++ b/lib/utils/attachment/attachment_util.dart @@ -19,11 +19,11 @@ import '../../enum/media_status.dart'; import '../../widgets/message/send_message_dialog/attachment_extra.dart'; import '../../widgets/toast.dart'; import '../crypto_util.dart'; +import '../db/app_setting_key_value.dart'; import '../extension/extension.dart'; import '../file.dart'; import '../load_balancer_utils.dart'; import '../logger.dart'; -import '../property/setting_property.dart'; import '../proxy.dart'; import 'download_key_value.dart'; @@ -158,7 +158,7 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { final MessageDao _messageDao; final TranscriptMessageDao _transcriptMessageDao; final Client _client; - final SettingPropertyStorage _settingProperties; + final AppSettingKeyValue _settingProperties; final DownloadKeyValue downloadKeyValue; final _attachmentJob = {}; @@ -463,6 +463,7 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { Database database, String identityNumber, DownloadKeyValue downloadKeyValue, + AppSettingKeyValue settingKeyValue, ) { final documentDirectory = mixinDocumentsDirectory; final mediaDirectory = @@ -471,7 +472,7 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { client, database.messageDao, database.transcriptMessageDao, - database.settingProperties, + settingKeyValue, mediaDirectory.path, downloadKeyValue, ); diff --git a/lib/utils/property/setting_property.dart b/lib/utils/db/app_setting_key_value.dart similarity index 68% rename from lib/utils/property/setting_property.dart rename to lib/utils/db/app_setting_key_value.dart index 4725d75557..6194f866ec 100644 --- a/lib/utils/property/setting_property.dart +++ b/lib/utils/db/app_setting_key_value.dart @@ -1,20 +1,20 @@ -import 'dart:convert'; - import 'package:mixin_logger/mixin_logger.dart'; -import '../../db/dao/property_dao.dart'; import '../../enum/property_group.dart'; -import '../db/property_storage.dart'; import '../extension/extension.dart'; import '../proxy.dart'; +import 'lazy_db_key_value.dart'; + +class AppSettingKeyValue extends BaseAppKeyValue { + AppSettingKeyValue(KeyValueDao dao) + : super(group: AppPropertyGroup.setting, dao: dao); +} const _kEnableProxyKey = 'enable_proxy'; const _kSelectedProxyKey = 'selected_proxy'; const _kProxyListKey = 'proxy_list'; -class SettingPropertyStorage extends PropertyStorage { - SettingPropertyStorage(PropertyDao dao) : super(UserPropertyGroup.setting, dao); - +extension SettingProxy on AppSettingKeyValue { bool get enableProxy => get(_kEnableProxyKey) ?? false; set enableProxy(bool value) => set(_kEnableProxyKey, value); @@ -24,16 +24,12 @@ class SettingPropertyStorage extends PropertyStorage { set selectedProxyId(String? value) => set(_kSelectedProxyKey, value); List get proxyList { - final json = get(_kProxyListKey); - if (json == null || json.isEmpty) { + final list = getList>(_kProxyListKey); + if (list == null || list.isEmpty) { return []; } try { - final list = jsonDecode(json) as List; - return list - .cast>() - .map(ProxyConfig.fromJson) - .toList(); + return list.map(ProxyConfig.fromJson).toList(); } catch (error, stacktrace) { e('load proxyList error: $error, $stacktrace'); } @@ -56,11 +52,11 @@ class SettingPropertyStorage extends PropertyStorage { void addProxy(ProxyConfig config) { final list = [...proxyList, config]; - set(_kProxyListKey, jsonEncode(list)); + set(_kProxyListKey, list); } void removeProxy(String id) { final list = proxyList.where((element) => element.id != id).toList(); - set(_kProxyListKey, jsonEncode(list)); + set(_kProxyListKey, list); } } diff --git a/lib/utils/db/lazy_db_key_value.dart b/lib/utils/db/lazy_db_key_value.dart index 6c8271337a..de4c620403 100644 --- a/lib/utils/db/lazy_db_key_value.dart +++ b/lib/utils/db/lazy_db_key_value.dart @@ -1,5 +1,7 @@ +import 'dart:async'; import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:mixin_logger/mixin_logger.dart'; import '../../enum/property_group.dart'; @@ -16,6 +18,8 @@ abstract class KeyValueDao { Stream> watchAll(Group group); Stream watchByKey(Group group, String key); + + Stream watchTableHasChanged(Group group); } T? _convertToType(String? value) { @@ -45,16 +49,18 @@ T? _convertToType(String? value) { } } -typedef BaseUserKeyValue = _BaseLazyDbKeyValue; -typedef BaseAppKeyValue = _BaseLazyDbKeyValue; +typedef BaseLazyUserKeyValue = _BaseLazyDbKeyValue; +typedef BaseLazyAppKeyValue = _BaseLazyDbKeyValue; -class _BaseLazyDbKeyValue { +class _BaseLazyDbKeyValue with _DbKeyValueUpdate { _BaseLazyDbKeyValue({ required this.group, required this.dao, }); + @override final G group; + @override final KeyValueDao dao; Stream watch(String key) => @@ -92,23 +98,108 @@ class _BaseLazyDbKeyValue { return null; } } +} + +typedef BaseAppKeyValue = _BaseDbKeyValue; + +class _BaseDbKeyValue extends ChangeNotifier with _DbKeyValueUpdate { + _BaseDbKeyValue({required this.group, required this.dao}) { + _loadProperties().whenComplete(_initCompleter.complete); + _subscription = dao.watchTableHasChanged(group).listen((event) { + _loadProperties(); + }); + } + + @override + final G group; + @override + final KeyValueDao dao; + + final Map _data = {}; + + final Completer _initCompleter = Completer(); + StreamSubscription? _subscription; + + Future get initialized => _initCompleter.future; + + Future _loadProperties() async { + final properties = await dao.getAll(group); + _data + ..clear() + ..addAll(properties); + notifyListeners(); + } + + T? get(String key) => _convertToType(_data[key]); + + List? getList(String key) { + final value = _data[key]; + if (value == null) { + return null; + } + try { + final list = jsonDecode(value) as List; + return list.cast(); + } catch (error, stacktrace) { + e('getProperty error: $error, stacktrace: $stacktrace'); + return null; + } + } + + Map? getMap(String key) { + final value = _data[key]; + if (value == null) { + return null; + } + try { + final map = jsonDecode(value) as Map; + return map.cast(); + } catch (error, stacktrace) { + e('getProperty error: $error, stacktrace: $stacktrace'); + return null; + } + } + + @override + Future set(String key, T? value) { + if (value == null) { + _data.remove(key); + } else if (value is List || value is Map) { + _data[key] = jsonEncode(value); + } else { + _data[key] = value.toString(); + } + return super.set(key, value); + } + + @override + Future clear() { + _data.clear(); + return super.clear(); + } + + @override + void dispose() { + super.dispose(); + _subscription?.cancel(); + } +} + +mixin _DbKeyValueUpdate { + @protected + abstract final G group; + @protected + abstract final KeyValueDao dao; Future set(String key, T? value) async { if (value == null) { await dao.set(group, key, null); return; } - switch (T) { - case String: - case int: - case double: - case bool: - await dao.set(group, key, value.toString()); - case Map: - case List: - await dao.set(group, key, jsonEncode(value)); - default: - await dao.set(group, key, value.toString()); + if (value is List || value is Map) { + await dao.set(group, key, jsonEncode(value)); + } else { + await dao.set(group, key, value.toString()); } } diff --git a/lib/utils/db/property_storage.dart b/lib/utils/db/property_storage.dart deleted file mode 100644 index 116e3f9f7f..0000000000 --- a/lib/utils/db/property_storage.dart +++ /dev/null @@ -1,141 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; - -import '../../db/dao/property_dao.dart'; -import '../../enum/property_group.dart'; -import '../event_bus.dart'; -import '../logger.dart'; - -class _PropertyChangedEvent { - _PropertyChangedEvent(this.group); - - final UserPropertyGroup group; -} - -class PropertyStorage extends ChangeNotifier { - PropertyStorage(this.group, this.dao) { - _loadProperties(); - EventBus.instance.on - .where((event) => event is _PropertyChangedEvent) - .cast<_PropertyChangedEvent>() - .listen((event) { - if (event.group == group) { - _loadProperties(); - } - }); - } - - Future _loadProperties() async { - final properties = await dao.getProperties(group); - _data.addAll(properties); - notifyListeners(); - await onPropertiesLoaded(); - } - - @protected - @mustCallSuper - Future onPropertiesLoaded() async {} - - final Map _data = {}; - - final UserPropertyGroup group; - - final PropertyDao dao; - - void clear() { - _data.clear(); - notifyListeners(); - dao.clearProperties(group).whenComplete(() { - EventBus.instance.fire(_PropertyChangedEvent(group)); - }); - onPropertiesClear(); - } - - @protected - @mustCallSuper - Future onPropertiesClear() async {} - - void remove(String key) { - _data.remove(key); - notifyListeners(); - dao.removeProperty(group, key).whenComplete(() { - EventBus.instance.fire(_PropertyChangedEvent(group)); - }); - } - - void set(String key, T? value) { - if (value == null) { - remove(key); - return; - } - final String save; - if (value is Map || value is List) { - save = jsonEncode(value); - } else { - save = value is String ? value : value.toString(); - } - _data[key] = save; - notifyListeners(); - - dao.setProperty(group, key, save).whenComplete(() { - EventBus.instance.fire(_PropertyChangedEvent(group)); - }); - } - - T? get(String key) { - final value = _data[key]; - if (value == null) { - return null; - } - try { - switch (T) { - case String: - return value as T?; - case int: - return int.tryParse(value) as T?; - case double: - return double.tryParse(value) as T?; - case bool: - return (value == 'true') as T?; - case dynamic: - return value as T?; - default: - e('getProperty unknown type: $T'); - return null; - } - } catch (error, stacktrace) { - e('getProperty error: $error, stacktrace: $stacktrace'); - return null; - } - } - - List? getList(String key) { - final value = _data[key]; - if (value == null) { - return null; - } - try { - final list = jsonDecode(value) as List; - return list.cast(); - } catch (error, stacktrace) { - e('getProperty error: $error, stacktrace: $stacktrace'); - return null; - } - } - - Map? getMap(String key) { - final value = _data[key]; - if (value == null) { - return null; - } - try { - final map = jsonDecode(value) as Map; - return map.cast(); - } catch (error, stacktrace) { - e('getProperty error: $error, stacktrace: $stacktrace'); - return null; - } - } -} diff --git a/lib/utils/db/user_crypto_key_value.dart b/lib/utils/db/user_crypto_key_value.dart index 667fbcf835..26de0ceac8 100644 --- a/lib/utils/db/user_crypto_key_value.dart +++ b/lib/utils/db/user_crypto_key_value.dart @@ -4,7 +4,7 @@ import '../../enum/property_group.dart'; import '../crypto_util.dart'; import 'lazy_db_key_value.dart'; -class UserCryptoKeyValue extends BaseUserKeyValue { +class UserCryptoKeyValue extends BaseLazyUserKeyValue { UserCryptoKeyValue(KeyValueDao dao) : super(group: UserPropertyGroup.crypto, dao: dao); diff --git a/lib/utils/proxy.dart b/lib/utils/proxy.dart index 1f151bcd5b..5c1a08d806 100644 --- a/lib/utils/proxy.dart +++ b/lib/utils/proxy.dart @@ -7,8 +7,8 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:mixin_logger/mixin_logger.dart'; +import 'db/app_setting_key_value.dart'; import 'extension/extension.dart'; -import 'property/setting_property.dart'; part 'proxy.g.dart'; @@ -67,10 +67,10 @@ extension DioProxyExt on Dio { } extension ClientExt on Client { - void configProxySetting(SettingPropertyStorage settingProperties) { - var proxyConfig = settingProperties.activatedProxy; - settingProperties.addListener(() { - final config = settingProperties.activatedProxy; + void configProxySetting(AppSettingKeyValue settingKeyValue) { + var proxyConfig = settingKeyValue.activatedProxy; + settingKeyValue.addListener(() { + final config = settingKeyValue.activatedProxy; if (config != proxyConfig) { proxyConfig = config; dio.applyProxy(config); diff --git a/lib/widgets/cache_image.dart b/lib/widgets/cache_image.dart index 797c463d14..75ad3f1e86 100644 --- a/lib/widgets/cache_image.dart +++ b/lib/widgets/cache_image.dart @@ -7,11 +7,13 @@ import 'package:crypto/crypto.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:http_client_helper/http_client_helper.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; -import '../utils/extension/extension.dart'; +import '../ui/provider/app_key_value_provider.dart'; +import '../utils/db/app_setting_key_value.dart'; import '../utils/logger.dart'; import '../utils/proxy.dart'; @@ -19,7 +21,7 @@ typedef PlaceholderWidgetBuilder = Widget Function(); typedef LoadingErrorWidgetBuilder = Widget Function(); -class CacheImage extends StatelessWidget { +class CacheImage extends ConsumerWidget { const CacheImage( this.src, { this.width, @@ -41,8 +43,9 @@ class CacheImage extends StatelessWidget { final BoxFit fit; @override - Widget build(BuildContext context) { - final proxyUrl = context.database.settingProperties.activatedProxy; + Widget build(BuildContext context, WidgetRef ref) { + final proxyUrl = ref + .watch(settingKeyValueProvider.select((value) => value.activatedProxy)); return Image( image: MixinNetworkImageProvider( src, diff --git a/lib/workers/message_worker_isolate.dart b/lib/workers/message_worker_isolate.dart index 70f5828366..5ce6ad612c 100644 --- a/lib/workers/message_worker_isolate.dart +++ b/lib/workers/message_worker_isolate.dart @@ -16,6 +16,7 @@ import 'package:stream_channel/isolate_channel.dart'; import '../blaze/blaze.dart'; import '../crypto/signal/signal_database.dart'; import '../crypto/signal/signal_protocol.dart'; +import '../db/app/app_database.dart'; import '../db/database.dart'; import '../db/database_event_bus.dart'; import '../db/fts_database.dart'; @@ -128,6 +129,7 @@ class _MessageProcessRunner { late Blaze blaze; late Sender _sender; late SignalProtocol signalProtocol; + late AppDatabase appDatabase; late SendingJob _sendingJob; late AckJob _ackJob; @@ -143,6 +145,8 @@ class _MessageProcessRunner { Timer? _nextExpiredMessageRunner; Future init(IsolateInitParams initParams) async { + appDatabase = await AppDatabase.connect(); + database = Database( await connectToDatabase(identityNumber, readCount: 4), await FtsDatabase.connect(identityNumber), @@ -171,7 +175,7 @@ class _MessageProcessRunner { ), ], loginByPhoneNumber: initParams.loginByPhoneNumber, - )..configProxySetting(database.settingProperties); + )..configProxySetting(appDatabase.settingKeyValue); _ackJob = AckJob( database: database, @@ -192,6 +196,7 @@ class _MessageProcessRunner { await generateUserAgent(), _ackJob, _floodJob, + appDatabase.settingKeyValue, ); blaze.connectedStateStream.listen((event) { @@ -382,6 +387,7 @@ class _MessageProcessRunner { void dispose() { blaze.dispose(); database.dispose(); + appDatabase.close(); jobSubscribers.forEach((subscription) => subscription.cancel()); _deviceTransfer?.dispose(); } diff --git a/test/db/property_storage_test.dart b/test/utils/db_key_value_test.dart similarity index 61% rename from test/db/property_storage_test.dart rename to test/utils/db_key_value_test.dart index 991ce18b83..d38d222960 100644 --- a/test/db/property_storage_test.dart +++ b/test/utils/db_key_value_test.dart @@ -1,53 +1,59 @@ @TestOn('linux || mac-os') +import 'dart:async'; + import 'package:drift/native.dart'; -import 'package:flutter_app/db/mixin_database.dart'; +import 'package:flutter_app/db/app/app_database.dart'; import 'package:flutter_app/enum/property_group.dart'; -import 'package:flutter_app/utils/db/property_storage.dart'; +import 'package:flutter_app/utils/db/lazy_db_key_value.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - test('tet PropertyStorage', () async { - final database = MixinDatabase(NativeDatabase.memory()); - final storage = - PropertyStorage(UserPropertyGroup.setting, database.propertyDao); + test('tet key value', () async { + final database = AppDatabase(NativeDatabase.memory()); + final storage = BaseAppKeyValue( + group: AppPropertyGroup.setting, + dao: database.appKeyValueDao, + ); + + await storage.initialized; expect(storage.get('test_empty'), null); - storage.set('test_empty', false); + unawaited(storage.set('test_empty', false)); expect(storage.get('test_empty'), null); expect(storage.get('test_empty'), null); expect(storage.get('test_empty'), 'false'); expect(storage.getList('test_empty'), null); expect(storage.getMap('test_empty'), null); - storage.set('test_int', 12345); + unawaited(storage.set('test_int', 12345)); expect(storage.get('test_int'), 12345); - storage.set('test_int', null); + unawaited(storage.set('test_int', null)); expect(storage.get('test_int'), null); expect(storage.get('test_string'), null); - storage.set('test_string', '12345'); + unawaited(storage.set('test_string', '12345')); expect(storage.get('test_string'), '12345'); - storage.set('test_bool', true); + unawaited(storage.set('test_bool', true)); expect(storage.get('test_bool'), true); - storage.set('test_bool', false); + unawaited(storage.set('test_bool', false)); expect(storage.get('test_bool'), false); - storage.set('test_double', 12345.6789); + unawaited(storage.set('test_double', 12345.6789)); expect(storage.get('test_double'), 12345.6789); expect(storage.get('test_double'), '12345.6789'); expect(storage.get('test_double'), null); expect(storage.get('test_double'), null); - storage.set('test_map', {'a': 1, 'b': 2}); + unawaited(storage.set('test_map', {'a': 1, 'b': 2})); expect(storage.getMap('test_map'), {'a': 1, 'b': 2}); expect(storage.getMap('test_map'), {'a': 1, 'b': 2}); - storage.set('test_list', [1, 2, 3]); + unawaited(storage.set('test_list', [1, 2, 3])); expect(storage.getList('test_list'), [1, 2, 3]); expect(storage.getList('test_list'), [1, 2, 3]); - storage.set('test_list_string', ['1', '2', '3']); + unawaited(storage.set('test_list_string', ['1', '2', '3'])); expect(storage.getList('test_list_string'), ['1', '2', '3']); expect(storage.getList('test_list_string'), ['1', '2', '3']); }); From 15f8bb30d4b183c3e0d1aee18075b222ba1b34b7 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 13 Nov 2023 09:30:24 +0800 Subject: [PATCH 27/44] fix log --- lib/crypto/signal/pre_key_util.dart | 1 + lib/ui/provider/account/multi_auth_provider.dart | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/crypto/signal/pre_key_util.dart b/lib/crypto/signal/pre_key_util.dart index bd335d8f59..fe2d995e5a 100644 --- a/lib/crypto/signal/pre_key_util.dart +++ b/lib/crypto/signal/pre_key_util.dart @@ -1,4 +1,5 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; + // ignore: implementation_imports import 'package:libsignal_protocol_dart/src/util/key_helper.dart' as helper; diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index ddbc724e72..7ae04d9091 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -116,7 +116,6 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { final hydratedJson = toHydratedJson(value.toJson()); HydratedBloc.storage.write(_kMultiAuthCubitKey, hydratedJson); super.state = value; - i('auth sate: $state'); } void active(String userId) { From d8f00e52e283920090dc0e540e92772172b6cbe3 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 13 Nov 2023 10:52:43 +0800 Subject: [PATCH 28/44] refactor key value --- lib/db/app/dao/app_key_value_dao.dart | 2 +- lib/db/dao/property_dao.dart | 2 +- lib/utils/db/app_setting_key_value.dart | 4 +- lib/utils/db/db_key_value.dart | 148 +++++++++++++++++ lib/utils/db/lazy_db_key_value.dart | 209 ------------------------ lib/utils/db/user_crypto_key_value.dart | 2 +- test/utils/db_key_value_test.dart | 45 ++++- 7 files changed, 189 insertions(+), 223 deletions(-) create mode 100644 lib/utils/db/db_key_value.dart delete mode 100644 lib/utils/db/lazy_db_key_value.dart diff --git a/lib/db/app/dao/app_key_value_dao.dart b/lib/db/app/dao/app_key_value_dao.dart index 44af6c5816..600c5d9474 100644 --- a/lib/db/app/dao/app_key_value_dao.dart +++ b/lib/db/app/dao/app_key_value_dao.dart @@ -1,7 +1,7 @@ import 'package:drift/drift.dart'; import '../../../enum/property_group.dart'; -import '../../../utils/db/lazy_db_key_value.dart'; +import '../../../utils/db/db_key_value.dart'; import '../app_database.dart'; part 'app_key_value_dao.g.dart'; diff --git a/lib/db/dao/property_dao.dart b/lib/db/dao/property_dao.dart index 9936d51895..0f5fd63b1f 100644 --- a/lib/db/dao/property_dao.dart +++ b/lib/db/dao/property_dao.dart @@ -1,7 +1,7 @@ import 'package:drift/drift.dart'; import '../../enum/property_group.dart'; -import '../../utils/db/lazy_db_key_value.dart'; +import '../../utils/db/db_key_value.dart'; import '../mixin_database.dart'; part 'property_dao.g.dart'; diff --git a/lib/utils/db/app_setting_key_value.dart b/lib/utils/db/app_setting_key_value.dart index 6194f866ec..193d8bae52 100644 --- a/lib/utils/db/app_setting_key_value.dart +++ b/lib/utils/db/app_setting_key_value.dart @@ -3,7 +3,7 @@ import 'package:mixin_logger/mixin_logger.dart'; import '../../enum/property_group.dart'; import '../extension/extension.dart'; import '../proxy.dart'; -import 'lazy_db_key_value.dart'; +import 'db_key_value.dart'; class AppSettingKeyValue extends BaseAppKeyValue { AppSettingKeyValue(KeyValueDao dao) @@ -24,7 +24,7 @@ extension SettingProxy on AppSettingKeyValue { set selectedProxyId(String? value) => set(_kSelectedProxyKey, value); List get proxyList { - final list = getList>(_kProxyListKey); + final list = get>>(_kProxyListKey); if (list == null || list.isEmpty) { return []; } diff --git a/lib/utils/db/db_key_value.dart b/lib/utils/db/db_key_value.dart new file mode 100644 index 0000000000..321c08170e --- /dev/null +++ b/lib/utils/db/db_key_value.dart @@ -0,0 +1,148 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:mixin_logger/mixin_logger.dart'; + +import '../../enum/property_group.dart'; + +abstract class KeyValueDao { + Future getByKey(Group group, String key); + + Future> getAll(Group group); + + Future set(Group group, String key, String? value); + + Future clear(Group group); + + Stream> watchAll(Group group); + + Stream watchByKey(Group group, String key); + + Stream watchTableHasChanged(Group group); +} + +typedef BaseLazyUserKeyValue = _BaseLazyDbKeyValue; +typedef BaseLazyAppKeyValue = _BaseLazyDbKeyValue; + +class _BaseLazyDbKeyValue { + _BaseLazyDbKeyValue({ + required this.group, + required this.dao, + }); + + final G group; + final KeyValueDao dao; + + Stream watch(String key) => + dao.watchByKey(group, key).map(convertToType); + + Future get(String key) async { + final value = await dao.getByKey(group, key); + return convertToType(value); + } + + Future set(String key, T? value) => + dao.set(group, key, convertToString(value)); + + Future clear() => dao.clear(group); +} + +typedef BaseAppKeyValue = _BaseDbKeyValue; + +class _BaseDbKeyValue extends ChangeNotifier { + _BaseDbKeyValue({required this.group, required KeyValueDao dao}) + : _dao = dao { + _loadProperties().whenComplete(_initCompleter.complete); + _subscription = dao.watchTableHasChanged(group).listen((event) { + _loadProperties(); + }); + } + + @protected + final G group; + final KeyValueDao _dao; + + final Map _data = {}; + + final Completer _initCompleter = Completer(); + StreamSubscription? _subscription; + + Future get initialized => _initCompleter.future; + + Future _loadProperties() async { + final properties = await _dao.getAll(group); + _data + ..clear() + ..addAll(properties); + notifyListeners(); + } + + T? get(String key) => convertToType(_data[key]); + + Future set(String key, T? value) { + final converted = convertToString(value); + if (converted != null) { + _data[key] = converted; + } else { + _data.remove(key); + } + return _dao.set(group, key, converted); + } + + Future clear() { + _data.clear(); + return _dao.clear(group); + } + + @override + void dispose() { + super.dispose(); + _subscription?.cancel(); + } +} + +@visibleForTesting +T? convertToType(String? value) { + if (value == null) { + return null; + } + try { + switch (T) { + case const (String): + return value as T; + case const (int): + return int.parse(value) as T; + case const (double): + return double.parse(value) as T; + case const (bool): + return (value == 'true') as T; + case const (Map): + case const (Map): + case const (List): + return jsonDecode(value) as T; + case const (List): + return (jsonDecode(value) as List).cast() as T; + case const (List>): + return (jsonDecode(value) as List).cast>() as T; + case const (dynamic): + return value as T; + default: + throw UnsupportedError('unsupported type $T'); + } + } catch (error, stacktrace) { + e('failed to convert $value to type $T : $error\n$stacktrace'); + return null; + } +} + +@visibleForTesting +String? convertToString(dynamic value) { + if (value == null) { + return null; + } + if (value is String) { + return value; + } + return jsonEncode(value); +} diff --git a/lib/utils/db/lazy_db_key_value.dart b/lib/utils/db/lazy_db_key_value.dart deleted file mode 100644 index de4c620403..0000000000 --- a/lib/utils/db/lazy_db_key_value.dart +++ /dev/null @@ -1,209 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:mixin_logger/mixin_logger.dart'; - -import '../../enum/property_group.dart'; - -abstract class KeyValueDao { - Future getByKey(Group group, String key); - - Future> getAll(Group group); - - Future set(Group group, String key, String? value); - - Future clear(Group group); - - Stream> watchAll(Group group); - - Stream watchByKey(Group group, String key); - - Stream watchTableHasChanged(Group group); -} - -T? _convertToType(String? value) { - if (value == null) { - return null; - } - try { - switch (T) { - case String: - return value as T; - case int: - return int.parse(value) as T; - case double: - return double.parse(value) as T; - case bool: - return (value == 'true') as T; - case Map: - throw Exception('use getMap instead'); - case List: - throw Exception('use getList instead'); - default: - return value as T; - } - } catch (error, stacktrace) { - e('failed to convert $value to type $T : $error, $stacktrace'); - return null; - } -} - -typedef BaseLazyUserKeyValue = _BaseLazyDbKeyValue; -typedef BaseLazyAppKeyValue = _BaseLazyDbKeyValue; - -class _BaseLazyDbKeyValue with _DbKeyValueUpdate { - _BaseLazyDbKeyValue({ - required this.group, - required this.dao, - }); - - @override - final G group; - @override - final KeyValueDao dao; - - Stream watch(String key) => - dao.watchByKey(group, key).map(_convertToType); - - Future get(String key) async { - final value = await dao.getByKey(group, key); - return _convertToType(value); - } - - Future?> getMap(String key) async { - final value = await dao.getByKey(group, key); - if (value == null) { - return null; - } - try { - final map = jsonDecode(value) as Map; - return map.cast(); - } catch (error, stacktrace) { - e('getMap $key error: $error, $stacktrace'); - return null; - } - } - - Future?> getList(String key) async { - final value = await dao.getByKey(group, key); - if (value == null) { - return null; - } - try { - final list = jsonDecode(value) as List; - return list.cast(); - } catch (error, stacktrace) { - e('getList $key error: $error, $stacktrace'); - return null; - } - } -} - -typedef BaseAppKeyValue = _BaseDbKeyValue; - -class _BaseDbKeyValue extends ChangeNotifier with _DbKeyValueUpdate { - _BaseDbKeyValue({required this.group, required this.dao}) { - _loadProperties().whenComplete(_initCompleter.complete); - _subscription = dao.watchTableHasChanged(group).listen((event) { - _loadProperties(); - }); - } - - @override - final G group; - @override - final KeyValueDao dao; - - final Map _data = {}; - - final Completer _initCompleter = Completer(); - StreamSubscription? _subscription; - - Future get initialized => _initCompleter.future; - - Future _loadProperties() async { - final properties = await dao.getAll(group); - _data - ..clear() - ..addAll(properties); - notifyListeners(); - } - - T? get(String key) => _convertToType(_data[key]); - - List? getList(String key) { - final value = _data[key]; - if (value == null) { - return null; - } - try { - final list = jsonDecode(value) as List; - return list.cast(); - } catch (error, stacktrace) { - e('getProperty error: $error, stacktrace: $stacktrace'); - return null; - } - } - - Map? getMap(String key) { - final value = _data[key]; - if (value == null) { - return null; - } - try { - final map = jsonDecode(value) as Map; - return map.cast(); - } catch (error, stacktrace) { - e('getProperty error: $error, stacktrace: $stacktrace'); - return null; - } - } - - @override - Future set(String key, T? value) { - if (value == null) { - _data.remove(key); - } else if (value is List || value is Map) { - _data[key] = jsonEncode(value); - } else { - _data[key] = value.toString(); - } - return super.set(key, value); - } - - @override - Future clear() { - _data.clear(); - return super.clear(); - } - - @override - void dispose() { - super.dispose(); - _subscription?.cancel(); - } -} - -mixin _DbKeyValueUpdate { - @protected - abstract final G group; - @protected - abstract final KeyValueDao dao; - - Future set(String key, T? value) async { - if (value == null) { - await dao.set(group, key, null); - return; - } - if (value is List || value is Map) { - await dao.set(group, key, jsonEncode(value)); - } else { - await dao.set(group, key, value.toString()); - } - } - - Future clear() async { - await dao.clear(group); - } -} diff --git a/lib/utils/db/user_crypto_key_value.dart b/lib/utils/db/user_crypto_key_value.dart index 26de0ceac8..ba54be1639 100644 --- a/lib/utils/db/user_crypto_key_value.dart +++ b/lib/utils/db/user_crypto_key_value.dart @@ -2,7 +2,7 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import '../../enum/property_group.dart'; import '../crypto_util.dart'; -import 'lazy_db_key_value.dart'; +import 'db_key_value.dart'; class UserCryptoKeyValue extends BaseLazyUserKeyValue { UserCryptoKeyValue(KeyValueDao dao) diff --git a/test/utils/db_key_value_test.dart b/test/utils/db_key_value_test.dart index d38d222960..ee9182aedf 100644 --- a/test/utils/db_key_value_test.dart +++ b/test/utils/db_key_value_test.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'package:drift/native.dart'; import 'package:flutter_app/db/app/app_database.dart'; import 'package:flutter_app/enum/property_group.dart'; -import 'package:flutter_app/utils/db/lazy_db_key_value.dart'; +import 'package:flutter_app/utils/db/db_key_value.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -22,8 +22,8 @@ void main() { expect(storage.get('test_empty'), null); expect(storage.get('test_empty'), null); expect(storage.get('test_empty'), 'false'); - expect(storage.getList('test_empty'), null); - expect(storage.getMap('test_empty'), null); + expect(storage.get('test_empty'), null); + expect(storage.get('test_empty'), null); unawaited(storage.set('test_int', 12345)); expect(storage.get('test_int'), 12345); @@ -46,15 +46,42 @@ void main() { expect(storage.get('test_double'), null); unawaited(storage.set('test_map', {'a': 1, 'b': 2})); - expect(storage.getMap('test_map'), {'a': 1, 'b': 2}); - expect(storage.getMap('test_map'), {'a': 1, 'b': 2}); + expect(storage.get>('test_map'), {'a': 1, 'b': 2}); + expect(storage.get('test_map'), {'a': 1, 'b': 2}); unawaited(storage.set('test_list', [1, 2, 3])); - expect(storage.getList('test_list'), [1, 2, 3]); - expect(storage.getList('test_list'), [1, 2, 3]); + expect(storage.get>('test_list'), [1, 2, 3]); + expect(storage.get('test_list'), [1, 2, 3]); unawaited(storage.set('test_list_string', ['1', '2', '3'])); - expect(storage.getList('test_list_string'), ['1', '2', '3']); - expect(storage.getList('test_list_string'), ['1', '2', '3']); + expect(storage.get>('test_list_string'), ['1', '2', '3']); + expect(storage.get('test_list_string'), ['1', '2', '3']); + }); + + test('convert to string', () { + expect(convertToString(123), '123'); + expect(convertToString(123.456), '123.456'); + expect(convertToString(true), 'true'); + expect(convertToString(false), 'false'); + expect(convertToString(null), null); + expect(convertToString('123'), '123'); + expect(convertToString({'a': 1, 'b': 2}), '{"a":1,"b":2}'); + expect(convertToString([1, 2, 3]), '[1,2,3]'); + }); + + test('convert to type', () { + expect(convertToType('123'), '123'); + expect(convertToType('123'), 123); + expect(convertToType('123.456'), 123.456); + expect(convertToType('true'), true); + expect(convertToType('false'), false); + expect( + convertToType>('{"a":1,"b":2}'), {'a': 1, 'b': 2}); + expect(convertToType>('[1,2,3]'), [1, 2, 3]); + expect(convertToType('[1,2,3]'), [1, 2, 3]); + expect(convertToType('["1","2","3"]'), ['1', '2', '3']); + expect(convertToType>>('[{"a":1,"b":2}]'), [ + {'a': 1, 'b': 2} + ]); }); } From 7854ee99c8c45e8515c53c76ce0db8cc5acecec2 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:42:28 +0800 Subject: [PATCH 29/44] refactor auth to database key value --- lib/app.dart | 9 ++ lib/db/app/app_database.dart | 20 +++-- lib/enum/property_group.dart | 1 + lib/main.dart | 10 +-- .../provider/account/multi_auth_provider.dart | 90 +++++++++++++++---- lib/ui/provider/database_provider.dart | 2 +- lib/utils/db/app_setting_key_value.dart | 2 +- lib/utils/db/db_key_value.dart | 8 +- lib/workers/message_worker_isolate.dart | 2 +- test/utils/db_key_value_test.dart | 2 +- 10 files changed, 107 insertions(+), 39 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 42b50bb126..d2aee94b75 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -28,6 +28,7 @@ import 'ui/provider/mention_cache_provider.dart'; import 'ui/provider/setting_provider.dart'; import 'ui/provider/slide_category_provider.dart'; import 'utils/extension/extension.dart'; +import 'utils/hook.dart'; import 'utils/logger.dart'; import 'utils/platform.dart'; import 'utils/system/system_fonts.dart'; @@ -52,6 +53,14 @@ class App extends HookConsumerWidget { precacheImage( const AssetImage(Resources.assetsImagesChatBackgroundPng), context); + final initialized = useMemoizedFuture( + () => ref.read(multiAuthStateNotifierProvider.notifier).initialized, + null); + + if (initialized.connectionState == ConnectionState.waiting) { + return const _App(home: AppInitializingPage()); + } + final authState = ref.watch(authProvider); Widget child; diff --git a/lib/db/app/app_database.dart b/lib/db/app/app_database.dart index 58f0e20b72..9fdf940bd3 100644 --- a/lib/db/app/app_database.dart +++ b/lib/db/app/app_database.dart @@ -22,17 +22,19 @@ part 'app_database.g.dart'; class AppDatabase extends _$AppDatabase { AppDatabase(super.e); - static Future connect({ + factory AppDatabase.connect({ bool fromMainIsolate = false, - }) async { + }) { final dbFilePath = p.join(mixinDocumentsDirectory.path, 'app.db'); - final queryExecutor = await createOrConnectDriftIsolate( - portName: 'one_mixin_drift_app', - debugName: 'isolate_drift_app', - fromMainIsolate: fromMainIsolate, - dbFile: File(dbFilePath), - ); - return AppDatabase(await queryExecutor.connect()); + return AppDatabase(LazyDatabase(() async { + final queryExecutor = await createOrConnectDriftIsolate( + portName: 'one_mixin_drift_app', + debugName: 'isolate_drift_app', + fromMainIsolate: fromMainIsolate, + dbFile: File(dbFilePath), + ); + return queryExecutor.connect(); + })); } late final AppSettingKeyValue settingKeyValue = diff --git a/lib/enum/property_group.dart b/lib/enum/property_group.dart index 5d136425b8..e86addd1da 100644 --- a/lib/enum/property_group.dart +++ b/lib/enum/property_group.dart @@ -6,4 +6,5 @@ enum UserPropertyGroup { enum AppPropertyGroup { setting, + auth, } diff --git a/lib/main.dart b/lib/main.dart index 8245cdc211..94f4270039 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,9 +21,7 @@ import 'package:window_size/window_size.dart'; import 'app.dart'; import 'bloc/custom_bloc_observer.dart'; -import 'db/app/app_database.dart'; import 'ui/home/home.dart'; -import 'ui/provider/database_provider.dart'; import 'utils/app_lifecycle.dart'; import 'utils/event_bus.dart'; import 'utils/file.dart'; @@ -104,13 +102,7 @@ Future main(List args) async { Bloc.observer = CustomBlocObserver(); } - final appDatabase = await AppDatabase.connect(fromMainIsolate: true); - runApp(ProviderScope( - overrides: [ - appDatabaseProvider.overrideWithValue(appDatabase), - ], - child: const OverlaySupport.global(child: App()), - )); + runApp(const ProviderScope(child: OverlaySupport.global(child: App()))); if (kPlatformIsDesktop) { Size? windowSize; diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index 7ae04d9091..b3076480c3 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -1,14 +1,18 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; -import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:mixin_logger/mixin_logger.dart'; +import '../../../enum/property_group.dart'; +import '../../../utils/db/db_key_value.dart'; import '../../../utils/extension/extension.dart'; import '../../../utils/hydrated_bloc.dart'; import '../../../utils/rivepod.dart'; +import '../database_provider.dart'; part 'multi_auth_provider.g.dart'; @@ -71,7 +75,32 @@ class MultiAuthState extends Equatable { } class MultiAuthStateNotifier extends DistinctStateNotifier { - MultiAuthStateNotifier(super.state); + MultiAuthStateNotifier(this._multiAuthKeyValue) : super(MultiAuthState()) { + _init().whenComplete(_initialized.complete); + } + + final _initialized = Completer(); + + Future get initialized => _initialized.future; + + Future _init() async { + await _multiAuthKeyValue.initialized; + final auths = _multiAuthKeyValue.authList; + final activeUserId = _multiAuthKeyValue.activeUserId; + if (auths.isEmpty) { + // check if old auths exist. + final oldAuthState = _getLegacyMultiAuthState(); + if (oldAuthState != null) { + i('migrate legacy auths'); + state = oldAuthState; + return; + } + } + _removeLegacyMultiAuthState(); + super.state = MultiAuthState(auths: auths, activeUserId: activeUserId); + } + + final MultiAuthKeyValue _multiAuthKeyValue; AuthState? get current => state.current; @@ -111,10 +140,10 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { } @override - @protected set state(MultiAuthState value) { - final hydratedJson = toHydratedJson(value.toJson()); - HydratedBloc.storage.write(_kMultiAuthCubitKey, hydratedJson); + _multiAuthKeyValue + ..setAuthList(value.auths) + ..setActiveUserId(value.activeUserId); super.state = value; } @@ -134,22 +163,53 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { const _kMultiAuthCubitKey = 'MultiAuthCubit'; -final multiAuthStateNotifierProvider = - StateNotifierProvider((ref) { +MultiAuthState? _getLegacyMultiAuthState() { final oldJson = HydratedBloc.storage.read(_kMultiAuthCubitKey); - if (oldJson != null) { - final multiAuthState = fromHydratedJson(oldJson, MultiAuthState.fromJson); - if (multiAuthState == null) { - return MultiAuthStateNotifier(MultiAuthState()); - } - - return MultiAuthStateNotifier(multiAuthState); + if (oldJson == null) { + return null; } + final multiAuthState = fromHydratedJson(oldJson, MultiAuthState.fromJson); + return multiAuthState; +} - return MultiAuthStateNotifier(MultiAuthState()); +void _removeLegacyMultiAuthState() { + HydratedBloc.storage.delete(_kMultiAuthCubitKey); +} + +final multiAuthStateNotifierProvider = + StateNotifierProvider((ref) { + final multiAuthKeyValue = ref.watch(multiAuthKeyValueProvider); + return MultiAuthStateNotifier(multiAuthKeyValue); }); final authProvider = multiAuthStateNotifierProvider.select((value) => value.current); final authAccountProvider = authProvider.select((value) => value?.account); + +const _keyAuths = 'auths'; +const _keyActiveUserId = 'active_user_id'; + +final multiAuthKeyValueProvider = Provider((ref) { + final dao = ref.watch(appDatabaseProvider).appKeyValueDao; + return MultiAuthKeyValue(dao: dao); +}); + +class MultiAuthKeyValue extends AppKeyValue { + MultiAuthKeyValue({required super.dao}) : super(group: AppPropertyGroup.auth); + + List get authList { + final json = get>>(_keyAuths); + if (json == null) { + return []; + } + return json.map(AuthState.fromJson).toList(); + } + + String? get activeUserId => get(_keyActiveUserId); + + Future setAuthList(List auths) => + set(_keyAuths, auths.map((e) => e.toJson()).toList()); + + Future setActiveUserId(String? userId) => set(_keyActiveUserId, userId); +} diff --git a/lib/ui/provider/database_provider.dart b/lib/ui/provider/database_provider.dart index 6431c08467..e23029d6f1 100644 --- a/lib/ui/provider/database_provider.dart +++ b/lib/ui/provider/database_provider.dart @@ -13,7 +13,7 @@ import 'hive_key_value_provider.dart'; import 'slide_category_provider.dart'; final appDatabaseProvider = - Provider((ref) => throw UnimplementedError()); + Provider((ref) => AppDatabase.connect(fromMainIsolate: true)); final databaseProvider = StateNotifierProvider.autoDispose>( diff --git a/lib/utils/db/app_setting_key_value.dart b/lib/utils/db/app_setting_key_value.dart index 193d8bae52..53c64ffb92 100644 --- a/lib/utils/db/app_setting_key_value.dart +++ b/lib/utils/db/app_setting_key_value.dart @@ -5,7 +5,7 @@ import '../extension/extension.dart'; import '../proxy.dart'; import 'db_key_value.dart'; -class AppSettingKeyValue extends BaseAppKeyValue { +class AppSettingKeyValue extends AppKeyValue { AppSettingKeyValue(KeyValueDao dao) : super(group: AppPropertyGroup.setting, dao: dao); } diff --git a/lib/utils/db/db_key_value.dart b/lib/utils/db/db_key_value.dart index 321c08170e..5622d582c5 100644 --- a/lib/utils/db/db_key_value.dart +++ b/lib/utils/db/db_key_value.dart @@ -45,10 +45,13 @@ class _BaseLazyDbKeyValue { Future set(String key, T? value) => dao.set(group, key, convertToString(value)); - Future clear() => dao.clear(group); + Future clear() { + i('clear key value: $group'); + return dao.clear(group); + } } -typedef BaseAppKeyValue = _BaseDbKeyValue; +typedef AppKeyValue = _BaseDbKeyValue; class _BaseDbKeyValue extends ChangeNotifier { _BaseDbKeyValue({required this.group, required KeyValueDao dao}) @@ -91,6 +94,7 @@ class _BaseDbKeyValue extends ChangeNotifier { } Future clear() { + i('clear key value: $group'); _data.clear(); return _dao.clear(group); } diff --git a/lib/workers/message_worker_isolate.dart b/lib/workers/message_worker_isolate.dart index 5ce6ad612c..ae1e36893b 100644 --- a/lib/workers/message_worker_isolate.dart +++ b/lib/workers/message_worker_isolate.dart @@ -145,7 +145,7 @@ class _MessageProcessRunner { Timer? _nextExpiredMessageRunner; Future init(IsolateInitParams initParams) async { - appDatabase = await AppDatabase.connect(); + appDatabase = AppDatabase.connect(); database = Database( await connectToDatabase(identityNumber, readCount: 4), diff --git a/test/utils/db_key_value_test.dart b/test/utils/db_key_value_test.dart index ee9182aedf..2eed18a5c5 100644 --- a/test/utils/db_key_value_test.dart +++ b/test/utils/db_key_value_test.dart @@ -10,7 +10,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { test('tet key value', () async { final database = AppDatabase(NativeDatabase.memory()); - final storage = BaseAppKeyValue( + final storage = AppKeyValue( group: AppPropertyGroup.setting, dao: database.appKeyValueDao, ); From f05255753d0474e96acf66ca24b560d370756cff Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:06:42 +0800 Subject: [PATCH 30/44] migrate setting to db key value --- lib/account/account_server.dart | 14 +- lib/account/notification_service.dart | 3 +- lib/app.dart | 6 +- lib/blaze/blaze.dart | 2 +- lib/db/app/app_database.dart | 2 +- lib/main.dart | 11 +- lib/ui/home/slide_page.dart | 3 +- lib/ui/landing/landing.dart | 4 +- .../account/account_server_provider.dart | 7 - .../provider/account/multi_auth_provider.dart | 3 +- lib/ui/provider/app_key_value_provider.dart | 7 - lib/ui/provider/database_provider.dart | 2 +- lib/ui/provider/setting_provider.dart | 448 +++++++++--------- lib/ui/setting/appearance_page.dart | 115 ++--- lib/ui/setting/notification_page.dart | 2 +- lib/ui/setting/proxy_page.dart | 22 +- lib/ui/setting/storage_page.dart | 14 +- lib/utils/attachment/attachment_util.dart | 5 +- lib/utils/attachment/download_key_value.dart | 1 - lib/utils/db/app_setting_key_value.dart | 62 --- lib/utils/db/db_key_value.dart | 8 +- lib/utils/extension/extension.dart | 13 +- lib/utils/extension/src/provider.dart | 6 +- lib/utils/proxy.dart | 2 +- lib/utils/web_view/web_view_desktop.dart | 7 +- lib/widgets/brightness_observer.dart | 9 +- lib/widgets/cache_image.dart | 7 +- lib/widgets/markdown.dart | 5 +- lib/widgets/message/message_style.dart | 8 - 29 files changed, 371 insertions(+), 427 deletions(-) delete mode 100644 lib/ui/provider/app_key_value_provider.dart delete mode 100644 lib/utils/db/app_setting_key_value.dart diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 11485513da..5e8d161708 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -30,7 +30,6 @@ import '../enum/encrypt_category.dart'; import '../enum/message_category.dart'; import '../ui/provider/account/account_server_provider.dart'; import '../ui/provider/account/multi_auth_provider.dart'; -import '../ui/provider/app_key_value_provider.dart'; import '../ui/provider/hive_key_value_provider.dart'; import '../ui/provider/setting_provider.dart'; import '../utils/app_lifecycle.dart'; @@ -53,7 +52,6 @@ class AccountServer { this.userAgent, this.deviceId, required this.multiAuthNotifier, - required this.settingChangeNotifier, required this.database, required this.currentConversationId, required this.ref, @@ -63,7 +61,6 @@ class AccountServer { static String? sid; final MultiAuthStateNotifier multiAuthNotifier; - final SettingChangeNotifier settingChangeNotifier; final Database database; final GetCurrentConversationId currentConversationId; @@ -170,14 +167,14 @@ class AccountServer { }, ), ], - )..configProxySetting(ref.read(settingKeyValueProvider)); + )..configProxySetting(ref.read(settingProvider)); attachmentUtil = AttachmentUtil.init( client, database, identityNumber, hiveKeyValues.downloadKeyValue, - ref.read(settingKeyValueProvider), + ref.read(settingProvider), ); _sendMessageHelper = SendMessageHelper(database, attachmentUtil, addSendingJob); @@ -285,12 +282,13 @@ class AccountServer { AttachmentRequest request, ) async { bool needDownload(String category) { + final settings = ref.read(settingProvider); if (category.isImage) { - return settingChangeNotifier.photoAutoDownload; + return settings.photoAutoDownload; } else if (category.isVideo) { - return settingChangeNotifier.videoAutoDownload; + return settings.videoAutoDownload; } else if (category.isData) { - return settingChangeNotifier.fileAutoDownload; + return settings.fileAutoDownload; } return true; } diff --git a/lib/account/notification_service.dart b/lib/account/notification_service.dart index bee0217e0f..a4ed6de964 100644 --- a/lib/account/notification_service.dart +++ b/lib/account/notification_service.dart @@ -12,6 +12,7 @@ import '../enum/message_category.dart'; import '../generated/l10n.dart'; import '../ui/provider/conversation_provider.dart'; import '../ui/provider/mention_cache_provider.dart'; +import '../ui/provider/setting_provider.dart'; import '../ui/provider/slide_category_provider.dart'; import '../utils/app_lifecycle.dart'; import '../utils/extension/extension.dart'; @@ -97,7 +98,7 @@ class NotificationService { ); String? body; - if (context.settingChangeNotifier.messagePreview) { + if (ref.read(settingProvider).messagePreview) { final mentionCache = ref.read(mentionCacheProvider); if (event.type == MessageCategory.systemConversation) { diff --git a/lib/app.dart b/lib/app.dart index d2aee94b75..560f3e0e53 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -188,13 +188,15 @@ class _App extends HookConsumerWidget { ), useMaterial3: true, ).withFallbackFonts(), - themeMode: ref.watch(settingProvider).themeMode, + themeMode: + ref.watch(settingProvider.select((value) => value.themeMode)), builder: (context, child) { final mediaQueryData = MediaQuery.of(context); return BrightnessObserver( lightThemeData: lightBrightnessThemeData, darkThemeData: darkBrightnessThemeData, - forceBrightness: ref.watch(settingProvider).brightness, + forceBrightness: ref + .watch(settingProvider.select((value) => value.brightness)), child: MediaQuery( data: mediaQueryData.copyWith( // Different linux distro change the value, e.g. 1.2 diff --git a/lib/blaze/blaze.dart b/lib/blaze/blaze.dart index b35e8ee289..4480427da1 100644 --- a/lib/blaze/blaze.dart +++ b/lib/blaze/blaze.dart @@ -12,7 +12,7 @@ import '../constants/constants.dart'; import '../db/database.dart'; import '../db/extension/job.dart'; import '../db/mixin_database.dart'; -import '../utils/db/app_setting_key_value.dart'; +import '../ui/provider/setting_provider.dart'; import '../utils/extension/extension.dart'; import '../utils/logger.dart'; import '../utils/proxy.dart'; diff --git a/lib/db/app/app_database.dart b/lib/db/app/app_database.dart index 9fdf940bd3..4136e96e8b 100644 --- a/lib/db/app/app_database.dart +++ b/lib/db/app/app_database.dart @@ -4,7 +4,7 @@ import 'package:drift/drift.dart'; import 'package:path/path.dart' as p; import '../../enum/property_group.dart'; -import '../../utils/db/app_setting_key_value.dart'; +import '../../ui/provider/setting_provider.dart'; import '../../utils/file.dart'; import '../util/open_database.dart'; import 'converter/app_property_group_converter.dart'; diff --git a/lib/main.dart b/lib/main.dart index 94f4270039..5d08ba13a8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,9 @@ import 'package:window_size/window_size.dart'; import 'app.dart'; import 'bloc/custom_bloc_observer.dart'; +import 'db/app/app_database.dart'; import 'ui/home/home.dart'; +import 'ui/provider/database_provider.dart'; import 'utils/app_lifecycle.dart'; import 'utils/event_bus.dart'; import 'utils/file.dart'; @@ -102,7 +104,14 @@ Future main(List args) async { Bloc.observer = CustomBlocObserver(); } - runApp(const ProviderScope(child: OverlaySupport.global(child: App()))); + final appDatabase = AppDatabase.connect(fromMainIsolate: true); + await appDatabase.settingKeyValue.initialized; + runApp(ProviderScope( + overrides: [ + appDatabaseProvider.overrideWithValue(appDatabase), + ], + child: const OverlaySupport.global(child: App()), + )); if (kPlatformIsDesktop) { Size? windowSize; diff --git a/lib/ui/home/slide_page.dart b/lib/ui/home/slide_page.dart index 9852d543de..c10148a25d 100644 --- a/lib/ui/home/slide_page.dart +++ b/lib/ui/home/slide_page.dart @@ -118,7 +118,8 @@ class SlidePage extends StatelessWidget { ), ), title: Text(context.l10n.collapse), - onTap: () => context.settingChangeNotifier + onTap: () => ref + .read(settingProvider) .collapsedSidebar = !collapse, ); }), diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index 9cbf897b41..b104ecc9f1 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -334,8 +334,6 @@ final landingKeyValuesProvider = FutureProvider.autoDispose( if (identityNumber == null) { return null; } - final hiveKeyValues = - ref.watch(hiveKeyValueProvider(identityNumber).future); - return hiveKeyValues; + return ref.watch(hiveKeyValueProvider(identityNumber).future); }, ); diff --git a/lib/ui/provider/account/account_server_provider.dart b/lib/ui/provider/account/account_server_provider.dart index fbd0486222..ac941e417f 100644 --- a/lib/ui/provider/account/account_server_provider.dart +++ b/lib/ui/provider/account/account_server_provider.dart @@ -12,7 +12,6 @@ import '../../../utils/synchronized.dart'; import '../conversation_provider.dart'; import '../database_provider.dart'; import '../hive_key_value_provider.dart'; -import '../setting_provider.dart'; import 'multi_auth_provider.dart'; typedef GetCurrentConversationId = String? Function(); @@ -57,7 +56,6 @@ class AccountServerOpener d('create new account server: $args'); final accountServer = AccountServer( multiAuthNotifier: args.multiAuthChangeNotifier, - settingChangeNotifier: args.settingChangeNotifier, database: args.database, currentConversationId: args.currentConversationId, ref: ref, @@ -89,7 +87,6 @@ class _Args extends Equatable { required this.identityNumber, required this.privateKey, required this.multiAuthChangeNotifier, - required this.settingChangeNotifier, required this.currentConversationId, required this.hiveKeyValues, }); @@ -100,7 +97,6 @@ class _Args extends Equatable { final String identityNumber; final String privateKey; final MultiAuthStateNotifier multiAuthChangeNotifier; - final SettingChangeNotifier settingChangeNotifier; final GetCurrentConversationId currentConversationId; final HiveKeyValues hiveKeyValues; @@ -112,7 +108,6 @@ class _Args extends Equatable { identityNumber, privateKey, multiAuthChangeNotifier, - settingChangeNotifier, currentConversationId, hiveKeyValues, ]; @@ -135,7 +130,6 @@ final _argsProvider = FutureProvider.autoDispose((ref) async { } final multiAuthChangeNotifier = ref.watch(multiAuthStateNotifierProvider.notifier); - final settingChangeNotifier = ref.watch(settingProvider); final currentConversationId = ref.read(_currentConversationIdProvider); final hiveKeyValues = await ref.watch(hiveKeyValueProvider(auth.account.identityNumber).future); @@ -147,7 +141,6 @@ final _argsProvider = FutureProvider.autoDispose((ref) async { identityNumber: auth.account.identityNumber, privateKey: auth.privateKey, multiAuthChangeNotifier: multiAuthChangeNotifier, - settingChangeNotifier: settingChangeNotifier, currentConversationId: currentConversationId, hiveKeyValues: hiveKeyValues, ); diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index b3076480c3..d923c6360f 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -168,8 +168,7 @@ MultiAuthState? _getLegacyMultiAuthState() { if (oldJson == null) { return null; } - final multiAuthState = fromHydratedJson(oldJson, MultiAuthState.fromJson); - return multiAuthState; + return fromHydratedJson(oldJson, MultiAuthState.fromJson); } void _removeLegacyMultiAuthState() { diff --git a/lib/ui/provider/app_key_value_provider.dart b/lib/ui/provider/app_key_value_provider.dart deleted file mode 100644 index d9db793934..0000000000 --- a/lib/ui/provider/app_key_value_provider.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -import '../../utils/db/app_setting_key_value.dart'; -import 'database_provider.dart'; - -final settingKeyValueProvider = ChangeNotifierProvider( - (ref) => ref.watch(appDatabaseProvider).settingKeyValue); diff --git a/lib/ui/provider/database_provider.dart b/lib/ui/provider/database_provider.dart index e23029d6f1..6431c08467 100644 --- a/lib/ui/provider/database_provider.dart +++ b/lib/ui/provider/database_provider.dart @@ -13,7 +13,7 @@ import 'hive_key_value_provider.dart'; import 'slide_category_provider.dart'; final appDatabaseProvider = - Provider((ref) => AppDatabase.connect(fromMainIsolate: true)); + Provider((ref) => throw UnimplementedError()); final databaseProvider = StateNotifierProvider.autoDispose>( diff --git a/lib/ui/provider/setting_provider.dart b/lib/ui/provider/setting_provider.dart index 34d85494eb..9b453f6217 100644 --- a/lib/ui/provider/setting_provider.dart +++ b/lib/ui/provider/setting_provider.dart @@ -1,15 +1,236 @@ -// ignore_for_file: deprecated_consistency -// ignore_for_file: deprecated_member_use_from_same_package - import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:mixin_logger/mixin_logger.dart'; +import '../../enum/property_group.dart'; +import '../../utils/db/db_key_value.dart'; +import '../../utils/extension/extension.dart'; import '../../utils/hydrated_bloc.dart'; -import '../../utils/logger.dart'; +import '../../utils/proxy.dart'; +import 'database_provider.dart'; + +final settingProvider = ChangeNotifierProvider( + (ref) => ref.watch(appDatabaseProvider).settingKeyValue); + +class AppSettingKeyValue extends AppKeyValue { + AppSettingKeyValue(KeyValueDao dao) + : super(group: AppPropertyGroup.setting, dao: dao) { + _migration(); + } +} + +const _kEnableProxyKey = 'enable_proxy'; +const _kSelectedProxyKey = 'selected_proxy'; +const _kProxyListKey = 'proxy_list'; + +extension ProxySetting on AppSettingKeyValue { + bool get enableProxy => get(_kEnableProxyKey) ?? false; + + set enableProxy(bool value) => set(_kEnableProxyKey, value); + + String? get selectedProxyId => get(_kSelectedProxyKey); + + set selectedProxyId(String? value) => set(_kSelectedProxyKey, value); + + List get proxyList { + final list = get>>(_kProxyListKey); + if (list == null || list.isEmpty) { + return []; + } + try { + return list.map(ProxyConfig.fromJson).toList(); + } catch (error, stacktrace) { + e('load proxyList error: $error, $stacktrace'); + } + return []; + } + + ProxyConfig? get activatedProxy { + if (!enableProxy) { + return null; + } + final list = proxyList; + if (list.isEmpty) { + return null; + } + if (selectedProxyId == null) { + return list.first; + } + return list.firstWhereOrNull((element) => element.id == selectedProxyId); + } + + void addProxy(ProxyConfig config) { + final list = [...proxyList, config]; + set(_kProxyListKey, list); + } + + void removeProxy(String id) { + final list = proxyList.where((element) => element.id != id).toList(); + set(_kProxyListKey, list); + } +} + +const _keyChatFontSizeDelta = 'chatFontSizeDelta'; +const _keyMessageShowIdentityNumber = 'messageShowIdentityNumber'; +const _keyMessageShowAvatar = 'messageShowAvatar'; + +extension ChatSetting on AppSettingKeyValue { + double get chatFontSizeDelta => get(_keyChatFontSizeDelta) ?? 0.0; + + set chatFontSizeDelta(double value) => set(_keyChatFontSizeDelta, value); + + bool get messageShowIdentityNumber => + get(_keyMessageShowIdentityNumber) ?? false; + + set messageShowIdentityNumber(bool value) => + set(_keyMessageShowIdentityNumber, value); + + bool get messageShowAvatar => get(_keyMessageShowAvatar) ?? true; + + set messageShowAvatar(bool value) => set(_keyMessageShowAvatar, value); +} + +const _keyBrightness = 'brightness'; +const _keyCollapsedSidebar = 'collapsedSidebar'; + +extension ApperenceSetting on AppSettingKeyValue { + /// [brightness] null to follow system. + set brightness(Brightness? value) { + switch (value) { + case Brightness.dark: + _brightness = 1; + break; + case Brightness.light: + _brightness = 2; + break; + case null: + _brightness = 0; + break; + } + } + + Brightness? get brightness { + switch (_brightness) { + case 0: + case null: + return null; + case 1: + return Brightness.dark; + case 2: + return Brightness.light; + default: + w('invalid value for brightness. $_brightness'); + return null; + } + } + + ThemeMode get themeMode { + switch (brightness) { + case Brightness.dark: + return ThemeMode.dark; + case Brightness.light: + return ThemeMode.light; + case null: + return ThemeMode.system; + } + } + + int? get _brightness => get(_keyBrightness); + + set _brightness(int? value) => set(_keyBrightness, value); + + bool get collapsedSidebar => get(_keyCollapsedSidebar) ?? false; + + set collapsedSidebar(bool value) => set(_keyCollapsedSidebar, value); +} + +const _keyMessagePreview = 'messagePreview'; + +extension NotificationSetting on AppSettingKeyValue { + bool get messagePreview => get(_keyMessagePreview) ?? true; + + set messagePreview(bool value) => set(_keyMessagePreview, value); +} + +const _keyPhotoAutoDownload = 'photoAutoDownload'; +const _keyVideoAutoDownload = 'videoAutoDownload'; +const _keyFileAutoDownload = 'fileAutoDownload'; + +extension AutoDownloadSetting on AppSettingKeyValue { + bool get photoAutoDownload => get(_keyPhotoAutoDownload) ?? true; + + set photoAutoDownload(bool value) => set(_keyPhotoAutoDownload, value); + + bool get videoAutoDownload => get(_keyVideoAutoDownload) ?? true; + + set videoAutoDownload(bool value) => set(_keyVideoAutoDownload, value); + + bool get fileAutoDownload => get(_keyFileAutoDownload) ?? true; + + set fileAutoDownload(bool value) => set(_keyFileAutoDownload, value); +} + +const _keySettingHasMigratedFromHive = 'settingHasMigratedFromHive'; + +extension SettingMigration on AppSettingKeyValue { + bool get settingHasMigratedFromHive => + get(_keySettingHasMigratedFromHive) ?? false; + + set settingHasMigratedFromHive(bool value) => + set(_keySettingHasMigratedFromHive, value); + + Future _migration() async { + await initialized; + if (settingHasMigratedFromHive) { + return; + } + settingHasMigratedFromHive = true; + final oldJson = HydratedBloc.storage.read(_kSettingCubitKey); + if (oldJson == null) { + return; + } + // we have not necessary to delete the old key + // unawaited(HydratedBloc.storage.delete(_kSettingCubitKey)); + final settingState = fromHydratedJson(oldJson, _SettingState.fromMap); + if (settingState == null) { + return; + } + if (settingState._brightness != null) { + _brightness = settingState._brightness; + } + if (settingState._messageShowAvatar != null) { + messageShowAvatar = settingState._messageShowAvatar!; + } + if (settingState._messagePreview != null) { + messagePreview = settingState._messagePreview!; + } + if (settingState._photoAutoDownload != null) { + photoAutoDownload = settingState._photoAutoDownload!; + } + if (settingState._videoAutoDownload != null) { + videoAutoDownload = settingState._videoAutoDownload!; + } + if (settingState._fileAutoDownload != null) { + fileAutoDownload = settingState._fileAutoDownload!; + } + if (settingState._collapsedSidebar != null) { + collapsedSidebar = settingState._collapsedSidebar!; + } + if (settingState._chatFontSizeDelta != null) { + chatFontSizeDelta = settingState._chatFontSizeDelta!; + } + if (settingState._messageShowIdentityNumber != null) { + messageShowIdentityNumber = settingState._messageShowIdentityNumber!; + } + } +} + +// setting cubit key in legacy hive +const _kSettingCubitKey = 'SettingCubit'; -@Deprecated('Use settingProvider instead') +// setting cubit object in legacy hive class _SettingState extends Equatable { const _SettingState({ int? brightness, @@ -53,24 +274,6 @@ class _SettingState extends Equatable { final double? _chatFontSizeDelta; final bool? _messageShowIdentityNumber; - int get brightness => _brightness ?? 0; - - bool get messageShowAvatar => _messageShowAvatar ?? false; - - bool get messagePreview => _messagePreview ?? true; - - bool get photoAutoDownload => _photoAutoDownload ?? true; - - bool get videoAutoDownload => _videoAutoDownload ?? true; - - bool get fileAutoDownload => _fileAutoDownload ?? true; - - bool get collapsedSidebar => _collapsedSidebar ?? false; - - double get chatFontSizeDelta => _chatFontSizeDelta ?? 0; - - bool get messageShowIdentityNumber => _messageShowIdentityNumber ?? false; - @override List get props => [ _brightness, @@ -120,202 +323,3 @@ class _SettingState extends Equatable { messageShowIdentityNumber ?? _messageShowIdentityNumber, ); } - -class SettingChangeNotifier extends ChangeNotifier { - SettingChangeNotifier({ - int? brightness, - bool? messageShowAvatar, - bool? messagePreview, - bool? photoAutoDownload, - bool? videoAutoDownload, - bool? fileAutoDownload, - bool? collapsedSidebar, - double? chatFontSizeDelta, - bool? messageShowIdentityNumber, - }) : _brightness = brightness, - _messageShowAvatar = messageShowAvatar, - _messagePreview = messagePreview, - _photoAutoDownload = photoAutoDownload, - _videoAutoDownload = videoAutoDownload, - _fileAutoDownload = fileAutoDownload, - _collapsedSidebar = collapsedSidebar, - _chatFontSizeDelta = chatFontSizeDelta, - _messageShowIdentityNumber = messageShowIdentityNumber; - - /// The brightness of theme. - /// 0 : follow system - /// 1 : dark - /// 2 : light - /// - /// The reason [int] instead of [Brightness] enum is that Hive has limited - /// support for custom data class. - /// https://docs.hivedb.dev/#/custom-objects/type_adapters?id=register-adapter - /// https://github.com/hivedb/hive/issues/525 - /// https://github.com/hivedb/hive/issues/518 - int? _brightness; - bool? _messageShowAvatar; - bool? _messageShowIdentityNumber; - bool? _messagePreview; - bool? _photoAutoDownload; - bool? _videoAutoDownload; - bool? _fileAutoDownload; - bool? _collapsedSidebar; - double? _chatFontSizeDelta; - - /// [brightness] null to follow system. - set brightness(Brightness? value) { - switch (value) { - case Brightness.dark: - _brightness = 1; - break; - case Brightness.light: - _brightness = 2; - break; - case null: - _brightness = 0; - break; - } - notifyListeners(); - } - - Brightness? get brightness { - switch (_brightness) { - case 0: - case null: - return null; - case 1: - return Brightness.dark; - case 2: - return Brightness.light; - default: - w('invalid value for brightness. $_brightness'); - return null; - } - } - - ThemeMode get themeMode { - switch (brightness) { - case Brightness.dark: - return ThemeMode.dark; - case Brightness.light: - return ThemeMode.light; - case null: - return ThemeMode.system; - } - } - - set messageShowAvatar(bool value) { - if (_messageShowAvatar == value) return; - - _messageShowAvatar = value; - notifyListeners(); - } - - bool get messageShowAvatar => _messageShowAvatar ?? false; - - set messageShowIdentityNumber(bool value) { - if (_messageShowIdentityNumber == value) return; - - _messageShowIdentityNumber = value; - notifyListeners(); - } - - bool get messageShowIdentityNumber => _messageShowIdentityNumber ?? false; - - set messagePreview(bool value) { - if (_messagePreview == value) return; - - _messagePreview = value; - notifyListeners(); - } - - bool get messagePreview => _messagePreview ?? true; - - set photoAutoDownload(bool value) { - if (_photoAutoDownload == value) return; - - _photoAutoDownload = value; - notifyListeners(); - } - - bool get photoAutoDownload => _photoAutoDownload ?? true; - - set videoAutoDownload(bool value) { - if (_videoAutoDownload == value) return; - - _videoAutoDownload = value; - notifyListeners(); - } - - bool get videoAutoDownload => _videoAutoDownload ?? true; - - set fileAutoDownload(bool value) { - if (_fileAutoDownload == value) return; - - _fileAutoDownload = value; - notifyListeners(); - } - - bool get fileAutoDownload => _fileAutoDownload ?? true; - - set collapsedSidebar(bool value) { - if (_collapsedSidebar == value) return; - - _collapsedSidebar = value; - notifyListeners(); - } - - bool get collapsedSidebar => _collapsedSidebar ?? false; - - set chatFontSizeDelta(double value) { - if (_chatFontSizeDelta == value) return; - - _chatFontSizeDelta = value; - notifyListeners(); - } - - double get chatFontSizeDelta => _chatFontSizeDelta ?? 0; - - @override - void notifyListeners() { - HydratedBloc.storage.write( - _kSettingCubitKey, - _SettingState( - brightness: _brightness, - messageShowAvatar: _messageShowAvatar, - messagePreview: _messagePreview, - photoAutoDownload: _photoAutoDownload, - videoAutoDownload: _videoAutoDownload, - fileAutoDownload: _fileAutoDownload, - collapsedSidebar: _collapsedSidebar, - chatFontSizeDelta: _chatFontSizeDelta, - messageShowIdentityNumber: _messageShowIdentityNumber, - ).toMap()); - super.notifyListeners(); - } -} - -@Deprecated('Use SettingChangeNotifier instead') -const _kSettingCubitKey = 'SettingCubit'; - -final settingProvider = ChangeNotifierProvider((ref) { - //migrate - final oldJson = HydratedBloc.storage.read(_kSettingCubitKey); - if (oldJson != null) { - final settingState = fromHydratedJson(oldJson, _SettingState.fromMap); - if (settingState == null) return SettingChangeNotifier(); - return SettingChangeNotifier( - brightness: settingState.brightness, - messageShowAvatar: settingState.messageShowAvatar, - messagePreview: settingState.messagePreview, - photoAutoDownload: settingState.photoAutoDownload, - videoAutoDownload: settingState.videoAutoDownload, - fileAutoDownload: settingState.fileAutoDownload, - collapsedSidebar: settingState.collapsedSidebar, - chatFontSizeDelta: settingState.chatFontSizeDelta, - messageShowIdentityNumber: settingState.messageShowIdentityNumber, - ); - } - - return SettingChangeNotifier(); -}); diff --git a/lib/ui/setting/appearance_page.dart b/lib/ui/setting/appearance_page.dart index ab5e838493..2f7179f634 100644 --- a/lib/ui/setting/appearance_page.dart +++ b/lib/ui/setting/appearance_page.dart @@ -45,64 +45,68 @@ class _Body extends HookConsumerWidget { const _Body(); @override - Widget build(BuildContext context, WidgetRef ref) => Container( - padding: const EdgeInsets.only(top: 20), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(left: 10, bottom: 14), - child: Text( - context.l10n.theme, - style: TextStyle( - color: context.theme.secondaryText, - fontSize: 14, - ), + Widget build(BuildContext context, WidgetRef ref) { + final brightness = + ref.watch(settingProvider.select((value) => value.brightness)); + return Container( + padding: const EdgeInsets.only(top: 20), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10, bottom: 14), + child: Text( + context.l10n.theme, + style: TextStyle( + color: context.theme.secondaryText, + fontSize: 14, ), ), - CellGroup( - cellBackgroundColor: context.theme.settingCellBackgroundColor, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CellItem( - title: RadioItem( - title: Text(context.l10n.followSystem), - groupValue: ref.watch(settingProvider).brightness, - onChanged: (value) => - context.settingChangeNotifier.brightness = value, - value: null, - ), - trailing: null, + ), + CellGroup( + cellBackgroundColor: context.theme.settingCellBackgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CellItem( + title: RadioItem( + title: Text(context.l10n.followSystem), + groupValue: brightness, + onChanged: (value) => + ref.read(settingProvider).brightness = value, + value: null, ), - CellItem( - title: RadioItem( - title: Text(context.l10n.light), - groupValue: ref.watch(settingProvider).brightness, - onChanged: (value) => - context.settingChangeNotifier.brightness = value, - value: Brightness.light, - ), - trailing: null, + trailing: null, + ), + CellItem( + title: RadioItem( + title: Text(context.l10n.light), + groupValue: brightness, + onChanged: (value) => + ref.read(settingProvider).brightness = value, + value: Brightness.light, ), - CellItem( - title: RadioItem( - title: Text(context.l10n.dark), - groupValue: ref.watch(settingProvider).brightness, - onChanged: (value) => - context.settingChangeNotifier.brightness = value, - value: Brightness.dark, - ), - trailing: null, + trailing: null, + ), + CellItem( + title: RadioItem( + title: Text(context.l10n.dark), + groupValue: brightness, + onChanged: (value) => + ref.read(settingProvider).brightness = value, + value: Brightness.dark, ), - ], - ), + trailing: null, + ), + ], ), - const _MessageAvatarSetting(), - const _ChatTextSizeSetting(), - const SizedBox(height: 36), - ], - ), - ); + ), + const _MessageAvatarSetting(), + const _ChatTextSizeSetting(), + const SizedBox(height: 36), + ], + ), + ); + } } class _MessageAvatarSetting extends HookConsumerWidget { @@ -141,7 +145,7 @@ class _MessageAvatarSetting extends HookConsumerWidget { activeColor: context.theme.accent, value: showAvatar, onChanged: (bool value) => - context.settingChangeNotifier.messageShowAvatar = value, + ref.read(settingProvider).messageShowAvatar = value, ), ), ), @@ -152,7 +156,8 @@ class _MessageAvatarSetting extends HookConsumerWidget { child: CupertinoSwitch( activeColor: context.theme.accent, value: showIdentityNumber, - onChanged: (bool value) => context.settingChangeNotifier + onChanged: (bool value) => ref + .read(settingProvider) .messageShowIdentityNumber = value, ), ), @@ -218,7 +223,7 @@ class _ChatTextSizeSetting extends HookConsumerWidget { max: 4, onChanged: (value) { debugPrint('fontSize: $value'); - context.settingChangeNotifier.chatFontSizeDelta = value; + ref.read(settingProvider).chatFontSizeDelta = value; }, ), ), diff --git a/lib/ui/setting/notification_page.dart b/lib/ui/setting/notification_page.dart index fc3c05222a..e212088cd4 100644 --- a/lib/ui/setting/notification_page.dart +++ b/lib/ui/setting/notification_page.dart @@ -50,7 +50,7 @@ class NotificationPage extends HookConsumerWidget { activeColor: context.theme.accent, value: currentMessagePreview, onChanged: (bool value) => - context.settingChangeNotifier.messagePreview = value, + ref.read(settingProvider).messagePreview = value, ), ), ), diff --git a/lib/ui/setting/proxy_page.dart b/lib/ui/setting/proxy_page.dart index 36dd7f9f2f..06f9904382 100644 --- a/lib/ui/setting/proxy_page.dart +++ b/lib/ui/setting/proxy_page.dart @@ -8,7 +8,6 @@ import 'package:uuid/uuid.dart'; import '../../constants/constants.dart'; import '../../constants/resources.dart'; -import '../../utils/db/app_setting_key_value.dart'; import '../../utils/extension/extension.dart'; import '../../utils/logger.dart'; import '../../utils/proxy.dart'; @@ -16,8 +15,8 @@ import '../../widgets/action_button.dart'; import '../../widgets/app_bar.dart'; import '../../widgets/cell.dart'; import '../../widgets/dialog.dart'; -import '../provider/app_key_value_provider.dart'; import '../provider/database_provider.dart'; +import '../provider/setting_provider.dart'; class ProxyPage extends StatelessWidget { const ProxyPage({super.key}); @@ -48,8 +47,8 @@ class _ProxySettingWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final enableProxy = - ref.watch(settingKeyValueProvider.select((value) => value.enableProxy)); - final hasProxyConfig = ref.watch(settingKeyValueProvider.select( + ref.watch(settingProvider.select((value) => value.enableProxy)); + final hasProxyConfig = ref.watch(settingProvider.select( (value) => value.proxyList.isNotEmpty, )); return Column( @@ -67,7 +66,7 @@ class _ProxySettingWidget extends HookConsumerWidget { onChanged: !hasProxyConfig ? null : (bool value) => ref - .read(settingKeyValueProvider.notifier) + .read(settingProvider.notifier) .enableProxy = value, )), ), @@ -106,10 +105,10 @@ class _ProxyItemList extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final proxyList = - ref.watch(settingKeyValueProvider.select((value) => value.proxyList)); - final selectedProxyId = ref.watch( - settingKeyValueProvider.select((value) => value.selectedProxyId)) ?? - proxyList.firstOrNull?.id; + ref.watch(settingProvider.select((value) => value.proxyList)); + final selectedProxyId = + ref.watch(settingProvider.select((value) => value.selectedProxyId)) ?? + proxyList.firstOrNull?.id; return Column( children: proxyList .map( @@ -167,7 +166,7 @@ class _ProxyItemWidget extends ConsumerWidget { name: Resources.assetsImagesDeleteSvg, color: context.theme.icon, onTap: () { - final settingKeyValue = ref.read(settingKeyValueProvider.notifier) + final settingKeyValue = ref.read(settingProvider.notifier) ..removeProxy(proxy.id); if (selected) { settingKeyValue @@ -180,8 +179,7 @@ class _ProxyItemWidget extends ConsumerWidget { if (selected) { return; } - ref.read(settingKeyValueProvider.notifier).selectedProxyId = - proxy.id; + ref.read(settingProvider.notifier).selectedProxyId = proxy.id; }, ), ); diff --git a/lib/ui/setting/storage_page.dart b/lib/ui/setting/storage_page.dart index b8708e236d..ef4a8532db 100644 --- a/lib/ui/setting/storage_page.dart +++ b/lib/ui/setting/storage_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; - import '../../utils/extension/extension.dart'; import '../../widgets/app_bar.dart'; import '../../widgets/cell.dart'; @@ -44,8 +43,8 @@ class StoragePage extends HookConsumerWidget { child: CupertinoSwitch( activeColor: context.theme.accent, value: photoAutoDownload, - onChanged: (bool value) => context - .settingChangeNotifier + onChanged: (bool value) => ref + .read(settingProvider) .photoAutoDownload = value, )), ), @@ -56,8 +55,8 @@ class StoragePage extends HookConsumerWidget { child: CupertinoSwitch( activeColor: context.theme.accent, value: videoAutoDownload, - onChanged: (bool value) => context - .settingChangeNotifier + onChanged: (bool value) => ref + .read(settingProvider) .videoAutoDownload = value, )), ), @@ -68,8 +67,9 @@ class StoragePage extends HookConsumerWidget { child: CupertinoSwitch( activeColor: context.theme.accent, value: fileAutoDownload, - onChanged: (bool value) => context - .settingChangeNotifier.fileAutoDownload = value, + onChanged: (bool value) => ref + .read(settingProvider) + .fileAutoDownload = value, )), ), ], diff --git a/lib/utils/attachment/attachment_util.dart b/lib/utils/attachment/attachment_util.dart index 5f16a4e852..0065c84af5 100644 --- a/lib/utils/attachment/attachment_util.dart +++ b/lib/utils/attachment/attachment_util.dart @@ -16,10 +16,10 @@ import '../../db/database.dart'; import '../../db/mixin_database.dart'; import '../../db/util/util.dart'; import '../../enum/media_status.dart'; +import '../../ui/provider/setting_provider.dart'; import '../../widgets/message/send_message_dialog/attachment_extra.dart'; import '../../widgets/toast.dart'; import '../crypto_util.dart'; -import '../db/app_setting_key_value.dart'; import '../extension/extension.dart'; import '../file.dart'; import '../load_balancer_utils.dart'; @@ -28,6 +28,7 @@ import '../proxy.dart'; import 'download_key_value.dart'; part 'attachment_download_job.dart'; + part 'attachment_upload_job.dart'; final _dio = (() { @@ -463,7 +464,7 @@ class AttachmentUtil extends AttachmentUtilBase with ChangeNotifier { Database database, String identityNumber, DownloadKeyValue downloadKeyValue, - AppSettingKeyValue settingKeyValue, + AppSettingKeyValue settingKeyValue, ) { final documentDirectory = mixinDocumentsDirectory; final mediaDirectory = diff --git a/lib/utils/attachment/download_key_value.dart b/lib/utils/attachment/download_key_value.dart index dc4c08fb8d..97c31fc211 100644 --- a/lib/utils/attachment/download_key_value.dart +++ b/lib/utils/attachment/download_key_value.dart @@ -10,5 +10,4 @@ class DownloadKeyValue extends HiveKeyValue { Future addMessageId(String messageId) => box.put(messageId, messageId); Future removeMessageId(String messageId) => box.delete(messageId); - } diff --git a/lib/utils/db/app_setting_key_value.dart b/lib/utils/db/app_setting_key_value.dart deleted file mode 100644 index 53c64ffb92..0000000000 --- a/lib/utils/db/app_setting_key_value.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:mixin_logger/mixin_logger.dart'; - -import '../../enum/property_group.dart'; -import '../extension/extension.dart'; -import '../proxy.dart'; -import 'db_key_value.dart'; - -class AppSettingKeyValue extends AppKeyValue { - AppSettingKeyValue(KeyValueDao dao) - : super(group: AppPropertyGroup.setting, dao: dao); -} - -const _kEnableProxyKey = 'enable_proxy'; -const _kSelectedProxyKey = 'selected_proxy'; -const _kProxyListKey = 'proxy_list'; - -extension SettingProxy on AppSettingKeyValue { - bool get enableProxy => get(_kEnableProxyKey) ?? false; - - set enableProxy(bool value) => set(_kEnableProxyKey, value); - - String? get selectedProxyId => get(_kSelectedProxyKey); - - set selectedProxyId(String? value) => set(_kSelectedProxyKey, value); - - List get proxyList { - final list = get>>(_kProxyListKey); - if (list == null || list.isEmpty) { - return []; - } - try { - return list.map(ProxyConfig.fromJson).toList(); - } catch (error, stacktrace) { - e('load proxyList error: $error, $stacktrace'); - } - return []; - } - - ProxyConfig? get activatedProxy { - if (!enableProxy) { - return null; - } - final list = proxyList; - if (list.isEmpty) { - return null; - } - if (selectedProxyId == null) { - return list.first; - } - return list.firstWhereOrNull((element) => element.id == selectedProxyId); - } - - void addProxy(ProxyConfig config) { - final list = [...proxyList, config]; - set(_kProxyListKey, list); - } - - void removeProxy(String id) { - final list = proxyList.where((element) => element.id != id).toList(); - set(_kProxyListKey, list); - } -} diff --git a/lib/utils/db/db_key_value.dart b/lib/utils/db/db_key_value.dart index 5622d582c5..a40ff67a8b 100644 --- a/lib/utils/db/db_key_value.dart +++ b/lib/utils/db/db_key_value.dart @@ -34,10 +34,10 @@ class _BaseLazyDbKeyValue { final G group; final KeyValueDao dao; - Stream watch(String key) => + Stream watch(String key) => dao.watchByKey(group, key).map(convertToType); - Future get(String key) async { + Future get(String key) async { final value = await dao.getByKey(group, key); return convertToType(value); } @@ -81,7 +81,7 @@ class _BaseDbKeyValue extends ChangeNotifier { notifyListeners(); } - T? get(String key) => convertToType(_data[key]); + T? get(String key) => convertToType(_data[key]); Future set(String key, T? value) { final converted = convertToString(value); @@ -107,7 +107,7 @@ class _BaseDbKeyValue extends ChangeNotifier { } @visibleForTesting -T? convertToType(String? value) { +T? convertToType(String? value) { if (value == null) { return null; } diff --git a/lib/utils/extension/extension.dart b/lib/utils/extension/extension.dart index 3c56c96425..1d2a55402f 100644 --- a/lib/utils/extension/extension.dart +++ b/lib/utils/extension/extension.dart @@ -28,7 +28,6 @@ import '../../ui/provider/account/account_server_provider.dart'; import '../../ui/provider/account/multi_auth_provider.dart'; import '../../ui/provider/database_provider.dart'; import '../../ui/provider/hive_key_value_provider.dart'; -import '../../ui/provider/setting_provider.dart'; import '../../widgets/brightness_observer.dart'; import '../audio_message_player/audio_message_service.dart'; import '../platform.dart'; @@ -56,17 +55,29 @@ export 'src/file.dart'; export 'src/platforms.dart'; part 'src/db.dart'; + part 'src/duration.dart'; + part 'src/image.dart'; + part 'src/info.dart'; + part 'src/iterable.dart'; + part 'src/key_event.dart'; + part 'src/markdown.dart'; + part 'src/number.dart'; + part 'src/provider.dart'; + part 'src/regexp.dart'; + part 'src/stream.dart'; + part 'src/string.dart'; + part 'src/ui.dart'; void importExtension() {} diff --git a/lib/utils/extension/src/provider.dart b/lib/utils/extension/src/provider.dart index 9cfa0b3991..899e8c5edb 100644 --- a/lib/utils/extension/src/provider.dart +++ b/lib/utils/extension/src/provider.dart @@ -8,9 +8,6 @@ extension ProviderExtension on BuildContext { Account? get account => providerContainer.read(authAccountProvider); - SettingChangeNotifier get settingChangeNotifier => - providerContainer.read(settingProvider); - AccountServer get accountServer => providerContainer.read(accountServerProvider.select((value) { if (!value.hasValue) throw Exception('AccountServerProvider not ready'); @@ -34,8 +31,7 @@ extension ProviderExtension on BuildContext { double get brightnessValue => BrightnessData.of(this); - Brightness get brightness => - settingChangeNotifier.brightness ?? MediaQuery.platformBrightnessOf(this); + Brightness get brightness => BrightnessData.brightnessOf(this); Color dynamicColor( Color color, { diff --git a/lib/utils/proxy.dart b/lib/utils/proxy.dart index 5c1a08d806..d83e19949e 100644 --- a/lib/utils/proxy.dart +++ b/lib/utils/proxy.dart @@ -7,7 +7,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:mixin_logger/mixin_logger.dart'; -import 'db/app_setting_key_value.dart'; +import '../ui/provider/setting_provider.dart'; import 'extension/extension.dart'; part 'proxy.g.dart'; diff --git a/lib/utils/web_view/web_view_desktop.dart b/lib/utils/web_view/web_view_desktop.dart index 9f10b78d99..f99d53d290 100644 --- a/lib/utils/web_view/web_view_desktop.dart +++ b/lib/utils/web_view/web_view_desktop.dart @@ -16,7 +16,6 @@ import '../extension/extension.dart'; import '../file.dart'; import '../system/package_info.dart'; import '../uri_utils.dart'; - import 'web_view_interface.dart'; class DesktopMixinWebView extends MixinWebView { @@ -41,8 +40,7 @@ class DesktopMixinWebView extends MixinWebView { ) async { assert(context.auth != null); - final mode = context.settingChangeNotifier.brightness ?? - MediaQuery.platformBrightnessOf(context); + final mode = context.brightness; final info = await getPackageInfo(); debugPrint( 'info: appName: ${info.appName} packageName: ${info.packageName} version: ${info.version} buildNumber: ${info.buildNumber} buildSignature: ${info.buildSignature} '); @@ -74,7 +72,7 @@ class DesktopMixinWebView extends MixinWebView { App? app, AppCardData? appCardData, }) async { - final brightness = context.settingChangeNotifier.brightness; + final brightness = context.brightness; final packageInfo = await getPackageInfo(); final webView = await WebviewWindow.create( configuration: CreateConfiguration( @@ -110,6 +108,7 @@ bool runWebViewNavigationBar(List args) => runWebViewTitleBarWidget( builder: (context) => const BrightnessData( brightnessThemeData: lightBrightnessThemeData, value: 1, + brightness: Brightness.light, child: WebViewNavigationBar(), ), backgroundColor: const Color(0xFFF0E7EA), diff --git a/lib/widgets/brightness_observer.dart b/lib/widgets/brightness_observer.dart index fc4d27e83e..044b081383 100644 --- a/lib/widgets/brightness_observer.dart +++ b/lib/widgets/brightness_observer.dart @@ -82,6 +82,7 @@ class BrightnessObserver extends HookConsumerWidget { value: value, brightnessThemeData: BrightnessThemeData.lerp(lightThemeData, darkThemeData, value), + brightness: currentBrightness, child: child!, ), child: child, @@ -95,15 +96,18 @@ class BrightnessData extends InheritedWidget { required super.child, super.key, required this.brightnessThemeData, + required this.brightness, }); final double value; final BrightnessThemeData brightnessThemeData; + final Brightness brightness; @override bool updateShouldNotify(covariant BrightnessData oldWidget) => value != oldWidget.value || - brightnessThemeData != oldWidget.brightnessThemeData; + brightnessThemeData != oldWidget.brightnessThemeData || + brightness != oldWidget.brightness; static double of(BuildContext context) => context.dependOnInheritedWidgetOfExactType()!.value; @@ -112,6 +116,9 @@ class BrightnessData extends InheritedWidget { .dependOnInheritedWidgetOfExactType()! .brightnessThemeData; + static Brightness brightnessOf(BuildContext context) => + context.dependOnInheritedWidgetOfExactType()!.brightness; + static Color dynamicColor( BuildContext context, Color color, { diff --git a/lib/widgets/cache_image.dart b/lib/widgets/cache_image.dart index 75ad3f1e86..8a364ca042 100644 --- a/lib/widgets/cache_image.dart +++ b/lib/widgets/cache_image.dart @@ -12,8 +12,7 @@ import 'package:http_client_helper/http_client_helper.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; -import '../ui/provider/app_key_value_provider.dart'; -import '../utils/db/app_setting_key_value.dart'; +import '../ui/provider/setting_provider.dart'; import '../utils/logger.dart'; import '../utils/proxy.dart'; @@ -44,8 +43,8 @@ class CacheImage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final proxyUrl = ref - .watch(settingKeyValueProvider.select((value) => value.activatedProxy)); + final proxyUrl = + ref.watch(settingProvider.select((value) => value.activatedProxy)); return Image( image: MixinNetworkImageProvider( src, diff --git a/lib/widgets/markdown.dart b/lib/widgets/markdown.dart index 95a49d861b..e8ffde72d9 100644 --- a/lib/widgets/markdown.dart +++ b/lib/widgets/markdown.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:html/dom.dart' as h; import 'package:html/dom_parsing.dart'; import 'package:html/parser.dart'; @@ -42,7 +43,7 @@ class MarkdownColumn extends StatelessWidget { } } -class Markdown extends StatelessWidget { +class Markdown extends ConsumerWidget { const Markdown({ super.key, required this.data, @@ -55,7 +56,7 @@ class Markdown extends StatelessWidget { final ScrollPhysics? physics; @override - Widget build(BuildContext context) => DefaultTextStyle.merge( + Widget build(BuildContext context, WidgetRef ref) => DefaultTextStyle.merge( style: TextStyle(color: context.theme.text), child: MarkdownWidget( data: data, diff --git a/lib/widgets/message/message_style.dart b/lib/widgets/message/message_style.dart index e4cd9f2632..19340e0595 100644 --- a/lib/widgets/message/message_style.dart +++ b/lib/widgets/message/message_style.dart @@ -1,14 +1,6 @@ -import 'package:flutter/cupertino.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../ui/provider/setting_provider.dart'; -import '../../utils/extension/extension.dart'; - -extension MessageStyleExt on BuildContext { - @Deprecated('message') - MessageStyle get messageStyle => - MessageStyle.defaultStyle + settingChangeNotifier.chatFontSizeDelta; -} final messageStyleProvider = Provider((ref) { final chatFontSizeDelta = From f29c7cdea5947454ddd52d7176adc89bc27c8151 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:12:37 +0800 Subject: [PATCH 31/44] remove log --- lib/crypto/signal/signal_database.dart | 1 - lib/ui/provider/account/account_server_provider.dart | 3 --- lib/utils/proxy.dart | 4 ++-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/crypto/signal/signal_database.dart b/lib/crypto/signal/signal_database.dart index f967c09484..489dc4db65 100644 --- a/lib/crypto/signal/signal_database.dart +++ b/lib/crypto/signal/signal_database.dart @@ -103,7 +103,6 @@ class SignalDatabase extends _$SignalDatabase { required bool openForLogin, required bool fromMainIsolate, }) async { - i('connect to signal database: $identityNumber'); if (openForLogin) { // delete old database file await _removeLegacySignalDatabase(); diff --git a/lib/ui/provider/account/account_server_provider.dart b/lib/ui/provider/account/account_server_provider.dart index ac941e417f..e9b85f9e74 100644 --- a/lib/ui/provider/account/account_server_provider.dart +++ b/lib/ui/provider/account/account_server_provider.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:mixin_logger/mixin_logger.dart'; import '../../../account/account_server.dart'; import '../../../blaze/blaze.dart'; @@ -35,7 +34,6 @@ class AccountServerOpener _Args? _previousArgs; Future _onNewArgs(_Args? args) => _lock.synchronized(() async { - i('on new args: $args'); if (_previousArgs == args) { return; } @@ -53,7 +51,6 @@ class AccountServerOpener }); Future _openAccountServer(_Args args) async { - d('create new account server: $args'); final accountServer = AccountServer( multiAuthNotifier: args.multiAuthChangeNotifier, database: args.database, diff --git a/lib/utils/proxy.dart b/lib/utils/proxy.dart index d83e19949e..3962179efc 100644 --- a/lib/utils/proxy.dart +++ b/lib/utils/proxy.dart @@ -55,12 +55,12 @@ class ProxyConfig with EquatableMixin { extension DioProxyExt on Dio { void applyProxy(ProxyConfig? config) { if (config != null) { - i('apply client proxy $config'); + i('with dio with client proxy $config'); httpClientAdapter = IOHttpClientAdapter(); (httpClientAdapter as IOHttpClientAdapter).createHttpClient = () => HttpClient()..setProxy(config); } else { - i('remove client proxy'); + i('create dio without client proxy'); httpClientAdapter = IOHttpClientAdapter(); } } From 1b3fb8b45fdf06f086db9823e58cdad981a8b2ca Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:18:13 +0800 Subject: [PATCH 32/44] fix test --- lib/utils/db/db_key_value.dart | 1 + test/utils/db_key_value_test.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils/db/db_key_value.dart b/lib/utils/db/db_key_value.dart index a40ff67a8b..5dc37623c9 100644 --- a/lib/utils/db/db_key_value.dart +++ b/lib/utils/db/db_key_value.dart @@ -130,6 +130,7 @@ T? convertToType(String? value) { case const (List>): return (jsonDecode(value) as List).cast>() as T; case const (dynamic): + case const (Object): return value as T; default: throw UnsupportedError('unsupported type $T'); diff --git a/test/utils/db_key_value_test.dart b/test/utils/db_key_value_test.dart index 2eed18a5c5..846039fa91 100644 --- a/test/utils/db_key_value_test.dart +++ b/test/utils/db_key_value_test.dart @@ -54,7 +54,7 @@ void main() { expect(storage.get('test_list'), [1, 2, 3]); unawaited(storage.set('test_list_string', ['1', '2', '3'])); - expect(storage.get>('test_list_string'), ['1', '2', '3']); + expect(storage.get>('test_list_string'), null); expect(storage.get('test_list_string'), ['1', '2', '3']); }); From be236d85c74cccd32c66105bd6f4b28a4a226900 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:38:53 +0800 Subject: [PATCH 33/44] fix conversation provider error --- lib/ui/provider/conversation_provider.dart | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/ui/provider/conversation_provider.dart b/lib/ui/provider/conversation_provider.dart index 02c7b43ef0..f62fd4b990 100644 --- a/lib/ui/provider/conversation_provider.dart +++ b/lib/ui/provider/conversation_provider.dart @@ -137,10 +137,9 @@ EncryptCategory _getEncryptCategory(App? app) { class ConversationStateNotifier extends DistinctStateNotifier with SubscriberMixin { ConversationStateNotifier({ - required AccountServer accountServer, + required this.ref, required ResponsiveNavigatorStateNotifier responsiveNavigatorStateNotifier, }) : _responsiveNavigatorStateNotifier = responsiveNavigatorStateNotifier, - _accountServer = accountServer, super(null) { addSubscription(stream .map((event) => event?.conversationId) @@ -211,7 +210,10 @@ class ConversationStateNotifier appActiveListener.addListener(onListen); } - final AccountServer _accountServer; + final Ref ref; + + AccountServer get _accountServer => + ref.read(accountServerProvider).valueOrNull!; final ResponsiveNavigatorStateNotifier _responsiveNavigatorStateNotifier; late final Database _database = _accountServer.database; late final String _currentUserId = _accountServer.userId; @@ -402,16 +404,12 @@ class _LastConversationNotifier final conversationProvider = StateNotifierProvider.autoDispose< ConversationStateNotifier, ConversationState?>((ref) { - final accountServerAsync = ref.watch(accountServerProvider); - if (!accountServerAsync.hasValue) { - throw Exception('accountServer is not ready'); - } - + // refresh when accountServerProvider changed + ref.watch(accountServerProvider); final responsiveNavigatorNotifier = ref.watch(responsiveNavigatorProvider.notifier); - return ConversationStateNotifier( - accountServer: accountServerAsync.requireValue, + ref: ref, responsiveNavigatorStateNotifier: responsiveNavigatorNotifier, ); }); From 9dfef43b90898227272a3f50c959100ba1dbab09 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:10:04 +0800 Subject: [PATCH 34/44] refactor security key value --- lib/account/security_key_value.dart | 90 ------------------- lib/app.dart | 5 +- lib/enum/property_group.dart | 1 + lib/main.dart | 2 +- lib/ui/home/chat/chat_page.dart | 32 +++++-- .../provider/account/multi_auth_provider.dart | 2 +- .../account/security_key_value_provider.dart | 32 +++++++ lib/ui/provider/hive_key_value_provider.dart | 11 --- lib/ui/provider/setting_provider.dart | 2 +- lib/ui/setting/security_page.dart | 30 +++---- lib/utils/db/db_key_value.dart | 4 +- lib/widgets/auth.dart | 32 +++---- test/utils/db_key_value_test.dart | 2 +- 13 files changed, 99 insertions(+), 146 deletions(-) delete mode 100644 lib/account/security_key_value.dart create mode 100644 lib/ui/provider/account/security_key_value_provider.dart diff --git a/lib/account/security_key_value.dart b/lib/account/security_key_value.dart deleted file mode 100644 index 25e250455e..0000000000 --- a/lib/account/security_key_value.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:mixin_logger/mixin_logger.dart'; -import 'package:rxdart/rxdart.dart'; - -import '../utils/hive_key_values.dart'; - -class SecurityKeyValue extends HiveKeyValue { - SecurityKeyValue() : super(_hiveSecurity); - - static const _hiveSecurity = 'security_box'; - static const _passcode = 'passcode'; - static const _biometric = 'biometric'; - static const _lockDuration = 'lockDuration'; - - String? get passcode { - try { - return box.get(_passcode) as String?; - } catch (e, s) { - i('[SecurityKeyValue] passcode error: $e, $s'); - return null; - } - } - - set passcode(String? value) { - if (value != null && value.length != 6) { - throw ArgumentError('Passcode must be 6 digits'); - } - box.put(_passcode, value); - if (value == null) { - lockDuration = null; - biometric = false; - } - } - - bool get biometric { - try { - return box.get(_biometric, defaultValue: false) as bool; - } catch (e, s) { - i('[SecurityKeyValue] biometric error: $e, $s'); - return false; - } - } - - set biometric(bool value) => box.put(_biometric, value); - - bool get hasPasscode => passcode != null; - - // must be return non-null value - Duration? get lockDuration { - dynamic minutes; - try { - minutes = box.get(_lockDuration); - } catch (_) {} - if (minutes == null) return const Duration(minutes: 1); - return Duration(minutes: minutes as int); - } - - set lockDuration(Duration? value) => box.put(_lockDuration, value?.inMinutes); - - Stream watchHasPasscode() { - try { - return box - .watch(key: _passcode) - .map((event) => event.value != null) - .startWith(passcode != null) - .onErrorReturn(false); - } catch (e, s) { - i('[SecurityKeyValue] watchHasPasscode error: $e, $s'); - return Stream.value(false); - } - } - - Stream watchLockDuration() => - box.watch(key: _lockDuration).map((event) { - final minutes = event.value; - if (minutes == null) return const Duration(minutes: 1); - return Duration(minutes: minutes as int); - }); - - Stream watchBiometric() { - try { - return box - .watch(key: _biometric) - .map((event) => (event.value ?? false) as bool) - .onErrorReturn(false); - } catch (e, s) { - i('[SecurityKeyValue] watchBiometric error: $e, $s'); - return Stream.value(false); - } - } -} diff --git a/lib/app.dart b/lib/app.dart index 560f3e0e53..d0b33000be 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -54,8 +54,9 @@ class App extends HookConsumerWidget { const AssetImage(Resources.assetsImagesChatBackgroundPng), context); final initialized = useMemoizedFuture( - () => ref.read(multiAuthStateNotifierProvider.notifier).initialized, - null); + () => ref.read(multiAuthStateNotifierProvider.notifier).initialized, + null, + ); if (initialized.connectionState == ConnectionState.waiting) { return const _App(home: AppInitializingPage()); diff --git a/lib/enum/property_group.dart b/lib/enum/property_group.dart index e86addd1da..9048b8d0b7 100644 --- a/lib/enum/property_group.dart +++ b/lib/enum/property_group.dart @@ -7,4 +7,5 @@ enum UserPropertyGroup { enum AppPropertyGroup { setting, auth, + security, } diff --git a/lib/main.dart b/lib/main.dart index 5d08ba13a8..3c5aec4048 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -105,7 +105,7 @@ Future main(List args) async { } final appDatabase = AppDatabase.connect(fromMainIsolate: true); - await appDatabase.settingKeyValue.initialized; + await appDatabase.settingKeyValue.initialize; runApp(ProviderScope( overrides: [ appDatabaseProvider.overrideWithValue(appDatabase), diff --git a/lib/ui/home/chat/chat_page.dart b/lib/ui/home/chat/chat_page.dart index 2b54ad4d90..4714775d44 100644 --- a/lib/ui/home/chat/chat_page.dart +++ b/lib/ui/home/chat/chat_page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:desktop_drop/desktop_drop.dart'; @@ -30,6 +31,7 @@ import '../../../widgets/message/message_bubble.dart'; import '../../../widgets/message/message_day_time.dart'; import '../../../widgets/pin_bubble.dart'; import '../../../widgets/toast.dart'; +import '../../provider/account/security_key_value_provider.dart'; import '../../provider/conversation_provider.dart'; import '../../provider/mention_cache_provider.dart'; import '../../provider/menu_handle_provider.dart'; @@ -1113,9 +1115,12 @@ class _ChatMenuHandler extends HookConsumerWidget { final controller = ref.read(macMenuBarProvider.notifier); if (conversationId == null) return null; - final handle = _ConversationHandle(context, conversationId); + final handle = _ConversationHandle(context, conversationId, ref); Future(() => controller.attach(handle)); - return () => Future(() => controller.unAttach(handle)); + return () => Future(() { + controller.unAttach(handle); + handle.dispose(); + }); }, [conversationId]); return child; @@ -1123,10 +1128,11 @@ class _ChatMenuHandler extends HookConsumerWidget { } class _ConversationHandle extends ConversationMenuHandle { - _ConversationHandle(this.context, this.conversationId); + _ConversationHandle(this.context, this.conversationId, this.ref); final BuildContext context; final String conversationId; + final WidgetRef ref; @override Future delete() async { @@ -1227,7 +1233,23 @@ class _ConversationHandle extends ConversationMenuHandle { ); } + final _subscriptions = []; + @override - Stream get hasPasscode => - context.hiveKeyValues.securityKeyValue.watchHasPasscode(); + Stream get hasPasscode { + final streamController = StreamController.broadcast(); + final subscription = ref.listenManual( + securityKeyValueProvider, + (previous, next) { + streamController.add(next.hasPasscode); + }, + fireImmediately: true, + ); + _subscriptions.add(subscription); + return streamController.stream; + } + + void dispose() { + _subscriptions.forEach((element) => element.close()); + } } diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index d923c6360f..e000e0418a 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -84,7 +84,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { Future get initialized => _initialized.future; Future _init() async { - await _multiAuthKeyValue.initialized; + await _multiAuthKeyValue.initialize; final auths = _multiAuthKeyValue.authList; final activeUserId = _multiAuthKeyValue.activeUserId; if (auths.isEmpty) { diff --git a/lib/ui/provider/account/security_key_value_provider.dart b/lib/ui/provider/account/security_key_value_provider.dart new file mode 100644 index 0000000000..f841993c27 --- /dev/null +++ b/lib/ui/provider/account/security_key_value_provider.dart @@ -0,0 +1,32 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../../../enum/property_group.dart'; +import '../../../utils/db/db_key_value.dart'; +import '../database_provider.dart'; + +const _keyPasscode = 'passcode'; +const _keyBiometric = 'biometric'; +const _keyLockDuration = 'lockDuration'; + +final securityKeyValueProvider = ChangeNotifierProvider( + (ref) => SecurityKeyValue(dao: ref.watch(appDatabaseProvider).appKeyValueDao), +); + +class SecurityKeyValue extends AppKeyValue { + SecurityKeyValue({required super.dao}) + : super(group: AppPropertyGroup.setting); + + String? get passcode => get(_keyPasscode); + + set passcode(String? value) => set(_keyPasscode, value); + + bool get biometric => get(_keyBiometric) ?? false; + + set biometric(bool value) => set(_keyBiometric, value); + + bool get hasPasscode => passcode != null; + + Duration get lockDuration => Duration(minutes: get(_keyLockDuration) ?? 1); + + set lockDuration(Duration? value) => set(_keyLockDuration, value?.inMinutes); +} diff --git a/lib/ui/provider/hive_key_value_provider.dart b/lib/ui/provider/hive_key_value_provider.dart index 6268580168..89a7cae4c3 100644 --- a/lib/ui/provider/hive_key_value_provider.dart +++ b/lib/ui/provider/hive_key_value_provider.dart @@ -8,7 +8,6 @@ import 'package:mixin_logger/mixin_logger.dart'; import '../../account/account_key_value.dart'; import '../../account/scam_warning_key_value.dart'; -import '../../account/security_key_value.dart'; import '../../account/session_key_value.dart'; import '../../account/show_pin_message_key_value.dart'; import '../../crypto/privacy_key_value.dart'; @@ -69,9 +68,6 @@ final currentSessionKeyValueProvider = final privacyKeyValueProvider = _createHiveKeyValueProvider(PrivacyKeyValue.new); -final securityKeyValueProvider = - _createHiveKeyValueProvider(SecurityKeyValue.new); - final showPinMessageKeyValueProvider = _createHiveKeyValueProvider(ShowPinMessageKeyValue.new); @@ -85,7 +81,6 @@ class HiveKeyValues with EquatableMixin { required this.sessionKeyValue, required this.privacyKeyValue, required this.downloadKeyValue, - required this.securityKeyValue, required this.showPinMessageKeyValue, required this.scamWarningKeyValue, }); @@ -96,7 +91,6 @@ class HiveKeyValues with EquatableMixin { final SessionKeyValue sessionKeyValue; final PrivacyKeyValue privacyKeyValue; final DownloadKeyValue downloadKeyValue; - final SecurityKeyValue securityKeyValue; final ShowPinMessageKeyValue showPinMessageKeyValue; final ScamWarningKeyValue scamWarningKeyValue; @@ -107,7 +101,6 @@ class HiveKeyValues with EquatableMixin { sessionKeyValue, privacyKeyValue, downloadKeyValue, - securityKeyValue, showPinMessageKeyValue, scamWarningKeyValue, ]; @@ -119,7 +112,6 @@ class HiveKeyValues with EquatableMixin { sessionKeyValue.clear(), privacyKeyValue.clear(), downloadKeyValue.clear(), - securityKeyValue.clear(), showPinMessageKeyValue.clear(), scamWarningKeyValue.clear(), ]); @@ -143,8 +135,6 @@ final hiveKeyValueProvider = await ref.watch(privacyKeyValueProvider(identityNumber).future); final downloadKeyValue = await ref.watch(downloadKeyValueProvider(identityNumber).future); - final securityKeyValue = - await ref.watch(securityKeyValueProvider(identityNumber).future); final showPinMessageKeyValue = await ref.watch(showPinMessageKeyValueProvider(identityNumber).future); final scamWarningKeyValue = @@ -155,7 +145,6 @@ final hiveKeyValueProvider = sessionKeyValue: sessionKeyValue, privacyKeyValue: privacyKeyValue, downloadKeyValue: downloadKeyValue, - securityKeyValue: securityKeyValue, showPinMessageKeyValue: showPinMessageKeyValue, scamWarningKeyValue: scamWarningKeyValue, ); diff --git a/lib/ui/provider/setting_provider.dart b/lib/ui/provider/setting_provider.dart index 9b453f6217..7209ffa4ab 100644 --- a/lib/ui/provider/setting_provider.dart +++ b/lib/ui/provider/setting_provider.dart @@ -182,7 +182,7 @@ extension SettingMigration on AppSettingKeyValue { set(_keySettingHasMigratedFromHive, value); Future _migration() async { - await initialized; + await initialize; if (settingHasMigratedFromHive) { return; } diff --git a/lib/ui/setting/security_page.dart b/lib/ui/setting/security_page.dart index 38702de638..78b562194d 100644 --- a/lib/ui/setting/security_page.dart +++ b/lib/ui/setting/security_page.dart @@ -7,12 +7,12 @@ import 'package:pin_code_fields/pin_code_fields.dart'; import '../../utils/authentication.dart'; import '../../utils/extension/extension.dart'; -import '../../utils/hook.dart'; import '../../widgets/app_bar.dart'; import '../../widgets/buttons.dart'; import '../../widgets/cell.dart'; import '../../widgets/dialog.dart'; import '../../widgets/toast.dart'; +import '../provider/account/security_key_value_provider.dart'; class SecurityPage extends StatelessWidget { const SecurityPage({super.key}); @@ -45,19 +45,15 @@ class _Passcode extends HookConsumerWidget { final globalKey = useMemoized(GlobalKey>.new, []); - final securityKeyValue = context.hiveKeyValues.securityKeyValue; - - final hasPasscode = - useMemoizedStream(securityKeyValue.watchHasPasscode).data ?? - securityKeyValue.hasPasscode; + final hasPasscode = ref + .watch(securityKeyValueProvider.select((value) => value.hasPasscode)); final enableBiometric = - useMemoizedStream(securityKeyValue.watchBiometric).data ?? - securityKeyValue.biometric; + ref.watch(securityKeyValueProvider.select((value) => value.biometric)); - final minutes = useStream(securityKeyValue - .watchLockDuration() - .map((event) => event.inMinutes)).data; + final minutes = ref + .watch(securityKeyValueProvider.select((value) => value.lockDuration)) + .inMinutes; return Column( mainAxisSize: MainAxisSize.min, @@ -76,7 +72,7 @@ class _Passcode extends HookConsumerWidget { value: hasPasscode, onChanged: (value) { if (!value) { - securityKeyValue.passcode = null; + ref.read(securityKeyValueProvider).passcode = null; return; } showMixinDialog( @@ -125,10 +121,10 @@ class _Passcode extends HookConsumerWidget { ), ) .toList(), - onSelected: (value) => context - .hiveKeyValues.securityKeyValue.lockDuration = value, + onSelected: (value) => + ref.read(securityKeyValueProvider).lockDuration = value, child: Text( - (minutes == null || minutes == 0) + minutes == 0 ? context.l10n.disabled : minutes < 60 ? context.l10n.minute(minutes, minutes) @@ -157,7 +153,7 @@ class _Passcode extends HookConsumerWidget { return; } - securityKeyValue.biometric = value; + ref.read(securityKeyValueProvider).biometric = value; }, ), ), @@ -207,7 +203,7 @@ class _InputPasscode extends HookConsumerWidget { return; } - context.hiveKeyValues.securityKeyValue.passcode = passcode.value; + ref.read(securityKeyValueProvider).passcode = passcode.value; Navigator.maybePop(context); }); diff --git a/lib/utils/db/db_key_value.dart b/lib/utils/db/db_key_value.dart index 5dc37623c9..098f7fe350 100644 --- a/lib/utils/db/db_key_value.dart +++ b/lib/utils/db/db_key_value.dart @@ -71,7 +71,9 @@ class _BaseDbKeyValue extends ChangeNotifier { final Completer _initCompleter = Completer(); StreamSubscription? _subscription; - Future get initialized => _initCompleter.future; + Future get initialize => _initCompleter.future; + + bool get initialized => _initCompleter.isCompleted; Future _loadProperties() async { final properties = await _dao.getAll(group); diff --git a/lib/widgets/auth.dart b/lib/widgets/auth.dart index cc33188988..f84f5ef385 100644 --- a/lib/widgets/auth.dart +++ b/lib/widgets/auth.dart @@ -10,16 +10,14 @@ import 'package:pin_code_fields/pin_code_fields.dart'; import 'package:rxdart/rxdart.dart'; import '../constants/resources.dart'; -import '../ui/provider/account/account_server_provider.dart'; +import '../ui/provider/account/multi_auth_provider.dart'; +import '../ui/provider/account/security_key_value_provider.dart'; import '../utils/app_lifecycle.dart'; import '../utils/authentication.dart'; import '../utils/event_bus.dart'; import '../utils/extension/extension.dart'; -import '../utils/hook.dart'; import 'dialog.dart'; -const _lockDuration = Duration(minutes: 1); - enum LockEvent { lock, unlock } class AuthGuard extends HookConsumerWidget { @@ -32,10 +30,13 @@ class AuthGuard extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final signed = - ref.watch(accountServerProvider.select((value) => value.hasValue)); + final signed = ref.watch(authProvider) != null; + final securityKeyValueLoaded = ref + .watch(securityKeyValueProvider.select((value) => value.initialized)); - if (signed) return _AuthGuard(child: child); + if (signed && securityKeyValueLoaded) { + return _AuthGuard(child: child); + } return child; } @@ -50,18 +51,15 @@ class _AuthGuard extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final focusNode = useFocusNode(); final textEditingController = useTextEditingController(); - final securityKeyValue = context.hiveKeyValues.securityKeyValue; - final hasPasscode = - useMemoizedStream(securityKeyValue.watchHasPasscode).data ?? - securityKeyValue.hasPasscode; + final hasPasscode = ref + .watch(securityKeyValueProvider.select((value) => value.hasPasscode)); final enableBiometric = - useMemoizedStream(securityKeyValue.watchBiometric).data ?? - securityKeyValue.biometric; + ref.watch(securityKeyValueProvider.select((value) => value.biometric)); final hasError = useState(false); - final lock = useState(securityKeyValue.hasPasscode); + final lock = useState(hasPasscode); useEffect(() { final listen = @@ -84,7 +82,8 @@ class _AuthGuard extends HookConsumerWidget { final needLock = !isAppActive; - final lockDuration = securityKeyValue.lockDuration ?? _lockDuration; + final lockDuration = ref.read( + securityKeyValueProvider.select((value) => value.lockDuration)); if (needLock) { if (lockDuration.inMinutes > 0) { timer = Timer(lockDuration, () { @@ -200,7 +199,8 @@ class _AuthGuard extends HookConsumerWidget { showCursor: false, onCompleted: (value) { textEditingController.text = ''; - if (securityKeyValue.passcode == value) { + if (ref.read(securityKeyValueProvider).passcode == + value) { lock.value = false; } else { hasError.value = true; diff --git a/test/utils/db_key_value_test.dart b/test/utils/db_key_value_test.dart index 846039fa91..7a9d1995ec 100644 --- a/test/utils/db_key_value_test.dart +++ b/test/utils/db_key_value_test.dart @@ -15,7 +15,7 @@ void main() { dao: database.appKeyValueDao, ); - await storage.initialized; + await storage.initialize; expect(storage.get('test_empty'), null); unawaited(storage.set('test_empty', false)); From d1a45c56c402ed3754cc2c9f8f1c37e6ec6c7c2f Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:29:58 +0800 Subject: [PATCH 35/44] fix security lock --- lib/ui/home/chat/chat_page.dart | 31 +--- lib/ui/provider/menu_handle_provider.dart | 2 - lib/widgets/auth.dart | 174 ++++++++++++---------- lib/widgets/window/menus.dart | 13 +- 4 files changed, 107 insertions(+), 113 deletions(-) diff --git a/lib/ui/home/chat/chat_page.dart b/lib/ui/home/chat/chat_page.dart index 4714775d44..d0f8c88ea0 100644 --- a/lib/ui/home/chat/chat_page.dart +++ b/lib/ui/home/chat/chat_page.dart @@ -31,7 +31,6 @@ import '../../../widgets/message/message_bubble.dart'; import '../../../widgets/message/message_day_time.dart'; import '../../../widgets/pin_bubble.dart'; import '../../../widgets/toast.dart'; -import '../../provider/account/security_key_value_provider.dart'; import '../../provider/conversation_provider.dart'; import '../../provider/mention_cache_provider.dart'; import '../../provider/menu_handle_provider.dart'; @@ -1115,12 +1114,9 @@ class _ChatMenuHandler extends HookConsumerWidget { final controller = ref.read(macMenuBarProvider.notifier); if (conversationId == null) return null; - final handle = _ConversationHandle(context, conversationId, ref); + final handle = _ConversationHandle(context, conversationId); Future(() => controller.attach(handle)); - return () => Future(() { - controller.unAttach(handle); - handle.dispose(); - }); + return () => Future(() => controller.unAttach(handle)); }, [conversationId]); return child; @@ -1128,11 +1124,10 @@ class _ChatMenuHandler extends HookConsumerWidget { } class _ConversationHandle extends ConversationMenuHandle { - _ConversationHandle(this.context, this.conversationId, this.ref); + _ConversationHandle(this.context, this.conversationId); final BuildContext context; final String conversationId; - final WidgetRef ref; @override Future delete() async { @@ -1232,24 +1227,4 @@ class _ConversationHandle extends ConversationMenuHandle { ), ); } - - final _subscriptions = []; - - @override - Stream get hasPasscode { - final streamController = StreamController.broadcast(); - final subscription = ref.listenManual( - securityKeyValueProvider, - (previous, next) { - streamController.add(next.hasPasscode); - }, - fireImmediately: true, - ); - _subscriptions.add(subscription); - return streamController.stream; - } - - void dispose() { - _subscriptions.forEach((element) => element.close()); - } } diff --git a/lib/ui/provider/menu_handle_provider.dart b/lib/ui/provider/menu_handle_provider.dart index 8b4629ca7e..21dd74fa86 100644 --- a/lib/ui/provider/menu_handle_provider.dart +++ b/lib/ui/provider/menu_handle_provider.dart @@ -10,8 +10,6 @@ abstract class ConversationMenuHandle { Stream get isPinned; - Stream get hasPasscode; - void mute(); void unmute(); diff --git a/lib/widgets/auth.dart b/lib/widgets/auth.dart index f84f5ef385..99ce4c3de8 100644 --- a/lib/widgets/auth.dart +++ b/lib/widgets/auth.dart @@ -20,30 +20,104 @@ import 'dialog.dart'; enum LockEvent { lock, unlock } -class AuthGuard extends HookConsumerWidget { - const AuthGuard({ - super.key, - required this.child, - }); +final securityLockProvider = StateNotifierProvider( + LockStateNotifier.new, +); - final Widget child; +class LockStateNotifier extends StateNotifier { + LockStateNotifier(this.ref) : super(false) { + _initialize(); + } - @override - Widget build(BuildContext context, WidgetRef ref) { - final signed = ref.watch(authProvider) != null; - final securityKeyValueLoaded = ref - .watch(securityKeyValueProvider.select((value) => value.initialized)); + final Ref ref; + + StreamSubscription? _subscription; + Timer? _inactiveTimer; + + SecurityKeyValue get securityKeyValue => ref.read(securityKeyValueProvider); + + bool get signed => ref.read(authProvider) != null; - if (signed && securityKeyValueLoaded) { - return _AuthGuard(child: child); + Future _initialize() async { + await securityKeyValue.initialize; + if (securityKeyValue.hasPasscode && signed) { + lock(); } + _subscription = EventBus.instance.on.whereType().listen((event) { + if (event == LockEvent.lock && signed) { + lock(); + } + }); - return child; + appActiveListener.addListener(_onAppActiveChanged); + } + + void _onAppActiveChanged() { + if (state) { + // already locked + return; + } + + void clearTimer() { + _inactiveTimer?.cancel(); + _inactiveTimer = null; + } + + clearTimer(); + + final needLock = !isAppActive && securityKeyValue.hasPasscode && signed; + if (!needLock) { + return; + } + final lockDuration = securityKeyValue.lockDuration; + if (lockDuration.inMinutes > 0) { + _inactiveTimer = Timer(lockDuration, () { + if (securityKeyValue.hasPasscode && signed) { + lock(); + clearTimer(); + } + }); + } + } + + void lock() { + if (!signed) { + throw Exception('not signed'); + } + if (!securityKeyValue.hasPasscode) { + throw Exception('no passcode'); + } + state = true; + } + + // for unlock by biometric + void unlock() { + state = false; + } + + bool unlockWithPin(String input) { + assert(securityKeyValue.hasPasscode, 'no passcode'); + if (securityKeyValue.passcode == input) { + state = false; + return true; + } + return false; + } + + @override + void dispose() { + _inactiveTimer?.cancel(); + _subscription?.cancel(); + appActiveListener.removeListener(_onAppActiveChanged); + super.dispose(); } } -class _AuthGuard extends HookConsumerWidget { - const _AuthGuard({required this.child}); +class AuthGuard extends HookConsumerWidget { + const AuthGuard({ + super.key, + required this.child, + }); final Widget child; @@ -52,62 +126,11 @@ class _AuthGuard extends HookConsumerWidget { final focusNode = useFocusNode(); final textEditingController = useTextEditingController(); - final hasPasscode = ref - .watch(securityKeyValueProvider.select((value) => value.hasPasscode)); - final enableBiometric = ref.watch(securityKeyValueProvider.select((value) => value.biometric)); final hasError = useState(false); - final lock = useState(hasPasscode); - - useEffect(() { - final listen = - EventBus.instance.on.whereType().listen((event) { - lock.value = event == LockEvent.lock; - }); - - return listen.cancel; - }, []); - - useEffect(() { - Timer? timer; - void dispose() { - timer?.cancel(); - timer = null; - } - - void listener() { - if (lock.value) return; - - final needLock = !isAppActive; - - final lockDuration = ref.read( - securityKeyValueProvider.select((value) => value.lockDuration)); - if (needLock) { - if (lockDuration.inMinutes > 0) { - timer = Timer(lockDuration, () { - if (!hasPasscode) { - lock.value = false; - return; - } - - lock.value = !isAppActive; - }); - } - } else { - dispose(); - lock.value = needLock; - } - } - - listener(); - appActiveListener.addListener(listener); - return () { - dispose(); - appActiveListener.removeListener(listener); - }; - }, [hasPasscode]); + final lock = ref.watch(securityLockProvider); useEffect(() { focusNode.requestFocus(); @@ -129,12 +152,12 @@ class _AuthGuard extends HookConsumerWidget { FocusManager.instance.removeListener(listener); ServicesBinding.instance.keyboard.removeHandler(handler); }; - }, [lock.value]); + }, [lock]); return Stack( children: [ child, - if (lock.value) + if (lock) GestureDetector( onTap: focusNode.requestFocus, behavior: HitTestBehavior.translucent, @@ -199,10 +222,9 @@ class _AuthGuard extends HookConsumerWidget { showCursor: false, onCompleted: (value) { textEditingController.text = ''; - if (ref.read(securityKeyValueProvider).passcode == - value) { - lock.value = false; - } else { + if (!ref + .read(securityLockProvider.notifier) + .unlockWithPin(value)) { hasError.value = true; } }, @@ -235,7 +257,9 @@ class _AuthGuard extends HookConsumerWidget { padding: const EdgeInsets.all(24), onTap: () async { if (await authenticate()) { - lock.value = false; + ref + .read(securityLockProvider.notifier) + .unlock(); return; } }, diff --git a/lib/widgets/window/menus.dart b/lib/widgets/window/menus.dart index e2604166a5..a699d4fca2 100644 --- a/lib/widgets/window/menus.dart +++ b/lib/widgets/window/menus.dart @@ -9,10 +9,10 @@ import 'package:window_manager/window_manager.dart'; import '../../ui/home/conversation/conversation_hotkey.dart'; import '../../ui/provider/account/account_server_provider.dart'; +import '../../ui/provider/account/security_key_value_provider.dart'; import '../../ui/provider/menu_handle_provider.dart'; import '../../ui/provider/slide_category_provider.dart'; import '../../utils/device_transfer/device_transfer_dialog.dart'; -import '../../utils/event_bus.dart'; import '../../utils/extension/extension.dart'; import '../../utils/hook.dart'; import '../../utils/uri_utils.dart'; @@ -60,11 +60,8 @@ class _Menus extends HookConsumerWidget { ).data ?? false; - final hasPasscode = useMemoizedStream( - () => handle?.hasPasscode ?? const Stream.empty(), - keys: [handle], - ).data ?? - false; + final hasPasscode = ref + .watch(securityKeyValueProvider.select((value) => value.hasPasscode)); PlatformMenu buildConversationMenu() => PlatformMenu( label: context.l10n.conversation, @@ -141,8 +138,8 @@ class _Menus extends HookConsumerWidget { meta: true, shift: true, ), - onSelected: hasPasscode - ? () => EventBus.instance.fire(LockEvent.lock) + onSelected: hasPasscode && signed + ? () => ref.read(securityLockProvider.notifier).lock() : null, ), ]), From 3caa49e7d7733d6e2f5c8c54347393d24e290a63 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:51:06 +0800 Subject: [PATCH 36/44] clean security lock when all account logout --- lib/widgets/auth.dart | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/widgets/auth.dart b/lib/widgets/auth.dart index 99ce4c3de8..a1ca1e4fc4 100644 --- a/lib/widgets/auth.dart +++ b/lib/widgets/auth.dart @@ -7,19 +7,16 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:pin_code_fields/pin_code_fields.dart'; -import 'package:rxdart/rxdart.dart'; import '../constants/resources.dart'; import '../ui/provider/account/multi_auth_provider.dart'; import '../ui/provider/account/security_key_value_provider.dart'; import '../utils/app_lifecycle.dart'; import '../utils/authentication.dart'; -import '../utils/event_bus.dart'; import '../utils/extension/extension.dart'; +import '../utils/logger.dart'; import 'dialog.dart'; -enum LockEvent { lock, unlock } - final securityLockProvider = StateNotifierProvider( LockStateNotifier.new, ); @@ -31,7 +28,6 @@ class LockStateNotifier extends StateNotifier { final Ref ref; - StreamSubscription? _subscription; Timer? _inactiveTimer; SecurityKeyValue get securityKeyValue => ref.read(securityKeyValueProvider); @@ -43,13 +39,16 @@ class LockStateNotifier extends StateNotifier { if (securityKeyValue.hasPasscode && signed) { lock(); } - _subscription = EventBus.instance.on.whereType().listen((event) { - if (event == LockEvent.lock && signed) { - lock(); + appActiveListener.addListener(_onAppActiveChanged); + ref.listen(multiAuthStateNotifierProvider, (previous, next) { + if (next.auths.isEmpty) { + unlock(); + // remove passcode + securityKeyValue + ..passcode = null + ..lockDuration = null; } }); - - appActiveListener.addListener(_onAppActiveChanged); } void _onAppActiveChanged() { @@ -71,6 +70,7 @@ class LockStateNotifier extends StateNotifier { } final lockDuration = securityKeyValue.lockDuration; if (lockDuration.inMinutes > 0) { + d('schedule lock after ${lockDuration.inMinutes} minutes'); _inactiveTimer = Timer(lockDuration, () { if (securityKeyValue.hasPasscode && signed) { lock(); @@ -107,7 +107,6 @@ class LockStateNotifier extends StateNotifier { @override void dispose() { _inactiveTimer?.cancel(); - _subscription?.cancel(); appActiveListener.removeListener(_onAppActiveChanged); super.dispose(); } From 1ae368e93728780f329e6d072547aaf74fde76e6 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:53:56 +0800 Subject: [PATCH 37/44] update pod --- macos/Podfile.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index dc2cbc2b79..bd84221ccf 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -42,18 +42,18 @@ PODS: - FlutterMacOS - screen_retriever (0.0.1): - FlutterMacOS - - sqlite3 (3.43.1): - - sqlite3/common (= 3.43.1) - - sqlite3/common (3.43.1) - - sqlite3/fts5 (3.43.1): + - sqlite3 (3.44.0): + - sqlite3/common (= 3.44.0) + - sqlite3/common (3.44.0) + - sqlite3/fts5 (3.44.0): - sqlite3/common - - sqlite3/perf-threadsafe (3.43.1): + - sqlite3/perf-threadsafe (3.44.0): - sqlite3/common - - sqlite3/rtree (3.43.1): + - sqlite3/rtree (3.44.0): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - FlutterMacOS - - sqlite3 (~> 3.43.1) + - sqlite3 (~> 3.44.0) - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree @@ -174,8 +174,8 @@ SPEC CHECKSUMS: platform_device_id_macos: f763bb55f088be804d61b96eb4710b8ab6598e94 protocol_handler: 587e1caf6c0b92ce351ab14081968dae49cb8cc6 screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 - sqlite3: e0a0623a33a20a47cb5921552aebc6e9e437dc91 - sqlite3_flutter_libs: 9939d86d0f5a3f8f0e91feb4f333e01c9bb4cd89 + sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273 + sqlite3_flutter_libs: a25f3a0f522fdcd8fef6a4a50a3d681dd43d8dea super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3 url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 From caea7b1bd89a3c5db0074d75703f276837728aa5 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:56:55 +0800 Subject: [PATCH 38/44] fix legacy auth not removed. --- lib/ui/provider/account/multi_auth_provider.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index e000e0418a..281220236f 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -93,6 +93,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { if (oldAuthState != null) { i('migrate legacy auths'); state = oldAuthState; + _removeLegacyMultiAuthState(); return; } } From 6002b9f9392efc97108b8b8a78b9c56ece8edb03 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 14 Nov 2023 12:29:20 +0800 Subject: [PATCH 39/44] improve signal database and auth migration --- lib/account/account_server.dart | 1 - lib/crypto/signal/signal_database.dart | 72 -------------- lib/crypto/signal/signal_protocol.dart | 1 - lib/ui/landing/landing.dart | 1 - .../provider/account/multi_auth_provider.dart | 96 +++++++++++++++++-- lib/workers/message_worker_isolate.dart | 1 - 6 files changed, 89 insertions(+), 83 deletions(-) diff --git a/lib/account/account_server.dart b/lib/account/account_server.dart index 5e8d161708..ad307bf291 100644 --- a/lib/account/account_server.dart +++ b/lib/account/account_server.dart @@ -99,7 +99,6 @@ class AccountServer { signalDatabase = await SignalDatabase.connect( identityNumber: identityNumber, - openForLogin: false, fromMainIsolate: true, ); checkSignalKeyTimer = diff --git a/lib/crypto/signal/signal_database.dart b/lib/crypto/signal/signal_database.dart index 489dc4db65..df58af1454 100644 --- a/lib/crypto/signal/signal_database.dart +++ b/lib/crypto/signal/signal_database.dart @@ -1,11 +1,8 @@ import 'dart:async'; -import 'dart:io'; import 'package:drift/drift.dart'; -import 'package:path/path.dart' as p; import '../../db/util/open_database.dart'; -import '../../utils/file.dart'; import '../../utils/logger.dart'; import 'dao/identity_dao.dart'; import 'dao/pre_key_dao.dart'; @@ -35,80 +32,11 @@ part 'signal_database.g.dart'; class SignalDatabase extends _$SignalDatabase { SignalDatabase._(super.e); - static Future _removeLegacySignalDatabase() async { - final dbFolder = mixinDocumentsDirectory.path; - const dbFiles = ['signal.db', 'signal.db-shm', 'signal.db-wal']; - final files = dbFiles.map((e) => File(p.join(dbFolder, e))); - for (final file in files) { - try { - if (file.existsSync()) { - i('remove legacy signal database: ${file.path}'); - await file.delete(); - } - } catch (error, stacktrace) { - e('_removeLegacySignalDatabase ${file.path} error: $error, stacktrace: $stacktrace'); - } - } - } - - static Future _migrationLegacySignalDatabaseIfNecessary( - String identityNumber) async { - final dbFolder = p.join(mixinDocumentsDirectory.path, identityNumber); - - final dbFile = File(p.join(dbFolder, 'signal.db')); - // migration only when new database file not exists. - if (dbFile.existsSync()) { - return _removeLegacySignalDatabase(); - } - - final legacyDbFolder = mixinDocumentsDirectory.path; - final legacyDbFile = File(p.join(legacyDbFolder, 'signal.db')); - if (!legacyDbFile.existsSync()) { - return; - } - const dbFiles = ['signal.db', 'signal.db-shm', 'signal.db-wal']; - final legacyFiles = dbFiles.map((e) => File(p.join(legacyDbFolder, e))); - var hasError = false; - for (final file in legacyFiles) { - try { - final newLocation = p.join(dbFolder, p.basename(file.path)); - // delete new location file if exists - final newFile = File(newLocation); - if (newFile.existsSync()) { - await newFile.delete(); - } - if (file.existsSync()) { - await file.copy(newLocation); - } - i('migrate legacy signal database: ${file.path}'); - } catch (error, stacktrace) { - e('_migrationLegacySignalDatabaseIfNecessary ${file.path} error: $error, stacktrace: $stacktrace'); - hasError = true; - } - } - if (hasError) { - // migration error. remove copied database file. - for (final name in dbFiles) { - final file = File(p.join(dbFolder, name)); - if (file.existsSync()) { - await file.delete(); - } - } - } - return _removeLegacySignalDatabase(); - } static Future connect({ required String identityNumber, - required bool openForLogin, required bool fromMainIsolate, }) async { - if (openForLogin) { - // delete old database file - await _removeLegacySignalDatabase(); - } else { - await _migrationLegacySignalDatabaseIfNecessary(identityNumber); - } final executor = await openQueryExecutor( identityNumber: identityNumber, dbName: 'signal', diff --git a/lib/crypto/signal/signal_protocol.dart b/lib/crypto/signal/signal_protocol.dart index 3297af621a..05b32f72be 100644 --- a/lib/crypto/signal/signal_protocol.dart +++ b/lib/crypto/signal/signal_protocol.dart @@ -39,7 +39,6 @@ class SignalProtocol { String identityNumber, int registrationId, List? private) async { final db = await SignalDatabase.connect( identityNumber: identityNumber, - openForLogin: true, fromMainIsolate: true, ); try { diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index b104ecc9f1..be14e3d3f5 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -206,7 +206,6 @@ class _LoginFailed extends HookConsumerWidget { await hiveKeyValues.clearAll(); final signalDb = await SignalDatabase.connect( identityNumber: authState.account.identityNumber, - openForLogin: true, fromMainIsolate: true, ); await signalDb.clear(); diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index 281220236f..34756f77e7 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:equatable/equatable.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -6,10 +7,12 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:mixin_bot_sdk_dart/mixin_bot_sdk_dart.dart'; import 'package:mixin_logger/mixin_logger.dart'; +import 'package:path/path.dart' as p; import '../../../enum/property_group.dart'; import '../../../utils/db/db_key_value.dart'; import '../../../utils/extension/extension.dart'; +import '../../../utils/file.dart'; import '../../../utils/hydrated_bloc.dart'; import '../../../utils/rivepod.dart'; import '../database_provider.dart'; @@ -86,18 +89,24 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { Future _init() async { await _multiAuthKeyValue.initialize; final auths = _multiAuthKeyValue.authList; - final activeUserId = _multiAuthKeyValue.activeUserId; - if (auths.isEmpty) { + var activeUserId = _multiAuthKeyValue.activeUserId; + if (auths.isEmpty && !_multiAuthKeyValue.authMigrated) { + await _multiAuthKeyValue.setAuthMigrated(); // check if old auths exist. - final oldAuthState = _getLegacyMultiAuthState(); + final oldAuthState = _getLegacyMultiAuthState()?.auths.lastOrNull; if (oldAuthState != null) { i('migrate legacy auths'); - state = oldAuthState; - _removeLegacyMultiAuthState(); - return; + final signalDbMigrated = await _migrationLegacySignalDatabase( + oldAuthState.account.identityNumber); + if (signalDbMigrated) { + auths.add(oldAuthState); + activeUserId = oldAuthState.userId; + } else { + w('migration legacy signal database failed, ignore legacy auths.'); + } } + _removeLegacyMultiAuthState(); } - _removeLegacyMultiAuthState(); super.state = MultiAuthState(auths: auths, activeUserId: activeUserId); } @@ -176,6 +185,70 @@ void _removeLegacyMultiAuthState() { HydratedBloc.storage.delete(_kMultiAuthCubitKey); } +Future _removeLegacySignalDatabase() async { + final dbFolder = mixinDocumentsDirectory.path; + const dbFiles = ['signal.db', 'signal.db-shm', 'signal.db-wal']; + final files = dbFiles.map((e) => File(p.join(dbFolder, e))); + for (final file in files) { + try { + if (file.existsSync()) { + i('remove legacy signal database: ${file.path}'); + await file.delete(); + } + } catch (error, stacktrace) { + e('_removeLegacySignalDatabase ${file.path} error: $error, stacktrace: $stacktrace'); + } + } +} + +Future _migrationLegacySignalDatabase(String identityNumber) async { + final dbFolder = p.join(mixinDocumentsDirectory.path, identityNumber); + + final dbFile = File(p.join(dbFolder, 'signal.db')); + // migration only when new database file not exists. + if (dbFile.existsSync()) { + await _removeLegacySignalDatabase(); + return false; + } + + final legacyDbFolder = mixinDocumentsDirectory.path; + final legacyDbFile = File(p.join(legacyDbFolder, 'signal.db')); + if (!legacyDbFile.existsSync()) { + return false; + } + const dbFiles = ['signal.db', 'signal.db-shm', 'signal.db-wal']; + final legacyFiles = dbFiles.map((e) => File(p.join(legacyDbFolder, e))); + var hasError = false; + for (final file in legacyFiles) { + try { + final newLocation = p.join(dbFolder, p.basename(file.path)); + // delete new location file if exists + final newFile = File(newLocation); + if (newFile.existsSync()) { + await newFile.delete(); + } + if (file.existsSync()) { + await file.copy(newLocation); + } + i('migrate legacy signal database: ${file.path}'); + } catch (error, stacktrace) { + e('_migrationLegacySignalDatabaseIfNecessary ${file.path} error: $error, stacktrace: $stacktrace'); + hasError = true; + } + } + if (hasError) { + // migration error. remove copied database file. + for (final name in dbFiles) { + final file = File(p.join(dbFolder, name)); + if (file.existsSync()) { + await file.delete(); + } + } + } + await _removeLegacySignalDatabase(); + return !hasError; +} + final multiAuthStateNotifierProvider = StateNotifierProvider((ref) { final multiAuthKeyValue = ref.watch(multiAuthKeyValueProvider); @@ -189,6 +262,7 @@ final authAccountProvider = authProvider.select((value) => value?.account); const _keyAuths = 'auths'; const _keyActiveUserId = 'active_user_id'; +const _keyAuthMigrated = 'auth_migrated_from_hive'; final multiAuthKeyValueProvider = Provider((ref) { final dao = ref.watch(appDatabaseProvider).appKeyValueDao; @@ -212,4 +286,12 @@ class MultiAuthKeyValue extends AppKeyValue { set(_keyAuths, auths.map((e) => e.toJson()).toList()); Future setActiveUserId(String? userId) => set(_keyActiveUserId, userId); + + Future setAuthMigrated() => set(_keyAuthMigrated, true); + + /// In old version, we use hive to store auths. + /// from the vision of support multi account feature we use db key value to store auths. + /// We only do one time migration from old version, because the signal database only + /// migrate once. + bool get authMigrated => get(_keyAuthMigrated) ?? false; } diff --git a/lib/workers/message_worker_isolate.dart b/lib/workers/message_worker_isolate.dart index ae1e36893b..20e67a2586 100644 --- a/lib/workers/message_worker_isolate.dart +++ b/lib/workers/message_worker_isolate.dart @@ -154,7 +154,6 @@ class _MessageProcessRunner { final signalDb = await SignalDatabase.connect( identityNumber: identityNumber, - openForLogin: false, fromMainIsolate: false, ); From eba4fa219feda0d80c7659c12465054477a00299 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Tue, 14 Nov 2023 14:00:35 +0800 Subject: [PATCH 40/44] fix format --- lib/crypto/signal/signal_database.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/crypto/signal/signal_database.dart b/lib/crypto/signal/signal_database.dart index df58af1454..af7c274a4c 100644 --- a/lib/crypto/signal/signal_database.dart +++ b/lib/crypto/signal/signal_database.dart @@ -32,7 +32,6 @@ part 'signal_database.g.dart'; class SignalDatabase extends _$SignalDatabase { SignalDatabase._(super.e); - static Future connect({ required String identityNumber, required bool fromMainIsolate, From 84673a00194b426052aa6e08476b82e9f215c56d Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Fri, 17 Nov 2023 10:39:40 +0800 Subject: [PATCH 41/44] fix lint --- lib/ui/landing/landing.dart | 2 -- lib/ui/landing/landing_initialize.dart | 2 +- lib/ui/landing/landing_qrcode.dart | 2 +- lib/ui/provider/setting_provider.dart | 3 --- lib/utils/mixin_api_client.dart | 2 +- lib/widgets/menu.dart | 2 +- test/crypto/crypto_key_value_test.dart | 2 ++ test/utils/db_key_value_test.dart | 2 ++ 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/ui/landing/landing.dart b/lib/ui/landing/landing.dart index d3038b3854..1269ad6caa 100644 --- a/lib/ui/landing/landing.dart +++ b/lib/ui/landing/landing.dart @@ -61,10 +61,8 @@ class LandingDialog extends ConsumerWidget { switch (mode) { case _LandingMode.qrcode: child = const LandingQrCodeWidget(); - break; case _LandingMode.mobile: child = const LoginWithMobileWidget(); - break; } return Portal( child: Scaffold( diff --git a/lib/ui/landing/landing_initialize.dart b/lib/ui/landing/landing_initialize.dart index 5fc0de6dc6..81852cf740 100644 --- a/lib/ui/landing/landing_initialize.dart +++ b/lib/ui/landing/landing_initialize.dart @@ -19,9 +19,9 @@ class AppInitializingPage extends StatelessWidget { class LoadingWidget extends StatelessWidget { const LoadingWidget({ - super.key, required this.title, required this.message, + super.key, }); final String title; diff --git a/lib/ui/landing/landing_qrcode.dart b/lib/ui/landing/landing_qrcode.dart index 584baf0ade..e24005d5a2 100644 --- a/lib/ui/landing/landing_qrcode.dart +++ b/lib/ui/landing/landing_qrcode.dart @@ -210,8 +210,8 @@ enum LandingStatus { class LandingState extends Equatable { const LandingState({ - this.authUrl, required this.status, + this.authUrl, this.errorMessage, }); diff --git a/lib/ui/provider/setting_provider.dart b/lib/ui/provider/setting_provider.dart index 7209ffa4ab..f91c9f459b 100644 --- a/lib/ui/provider/setting_provider.dart +++ b/lib/ui/provider/setting_provider.dart @@ -101,13 +101,10 @@ extension ApperenceSetting on AppSettingKeyValue { switch (value) { case Brightness.dark: _brightness = 1; - break; case Brightness.light: _brightness = 2; - break; case null: _brightness = 0; - break; } } diff --git a/lib/utils/mixin_api_client.dart b/lib/utils/mixin_api_client.dart index 4a9ea2251f..ce38f2bd6a 100644 --- a/lib/utils/mixin_api_client.dart +++ b/lib/utils/mixin_api_client.dart @@ -97,9 +97,9 @@ Client createClient({ required String userId, required String sessionId, required String privateKey, - List interceptors = const [], // Hive didn't support multi isolate. required bool loginByPhoneNumber, + List interceptors = const [], }) => _createClient( userId: userId, diff --git a/lib/widgets/menu.dart b/lib/widgets/menu.dart index 71b5badcc0..1aaf2b9fff 100644 --- a/lib/widgets/menu.dart +++ b/lib/widgets/menu.dart @@ -338,10 +338,10 @@ class ContextMenuPage extends StatelessWidget { class ContextMenuLayout extends StatelessWidget { const ContextMenuLayout({ + required this.child, super.key, this.onTap, this.onTapUp, - required this.child, }); final VoidCallback? onTap; diff --git a/test/crypto/crypto_key_value_test.dart b/test/crypto/crypto_key_value_test.dart index 7723dd9e9f..2738249352 100644 --- a/test/crypto/crypto_key_value_test.dart +++ b/test/crypto/crypto_key_value_test.dart @@ -1,4 +1,6 @@ @TestOn('linux || mac-os') +library; + import 'dart:io'; import 'package:drift/native.dart'; diff --git a/test/utils/db_key_value_test.dart b/test/utils/db_key_value_test.dart index 7a9d1995ec..8fdb00b2d2 100644 --- a/test/utils/db_key_value_test.dart +++ b/test/utils/db_key_value_test.dart @@ -1,4 +1,6 @@ @TestOn('linux || mac-os') +library; + import 'dart:async'; import 'package:drift/native.dart'; From 5ffe35407e21caf2b2307e799c55d7ff56f47d59 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Fri, 24 Nov 2023 12:55:54 +0800 Subject: [PATCH 42/44] improve migration --- lib/ui/provider/account/multi_auth_provider.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index 34756f77e7..24e3131756 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -90,8 +90,9 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { await _multiAuthKeyValue.initialize; final auths = _multiAuthKeyValue.authList; var activeUserId = _multiAuthKeyValue.activeUserId; - if (auths.isEmpty && !_multiAuthKeyValue.authMigrated) { - await _multiAuthKeyValue.setAuthMigrated(); + final migrated = _multiAuthKeyValue.authMigrated; + unawaited(_multiAuthKeyValue.setAuthMigrated()); + if (auths.isEmpty && !migrated) { // check if old auths exist. final oldAuthState = _getLegacyMultiAuthState()?.auths.lastOrNull; if (oldAuthState != null) { @@ -106,6 +107,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { } } _removeLegacyMultiAuthState(); + await _removeLegacySignalDatabase(); } super.state = MultiAuthState(auths: auths, activeUserId: activeUserId); } From b876e38b8d0a63f6afda884f2e47939fc24239d7 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Fri, 24 Nov 2023 13:14:07 +0800 Subject: [PATCH 43/44] remove legacy auth and signal db --- .../provider/account/multi_auth_provider.dart | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index 24e3131756..f49829686c 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -106,8 +106,12 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { w('migration legacy signal database failed, ignore legacy auths.'); } } + } + try { _removeLegacyMultiAuthState(); - await _removeLegacySignalDatabase(); + unawaited(_removeLegacySignalDatabase()); + } catch (error, stacktrace) { + e('remove legacy auths error: $error\n$stacktrace'); } super.state = MultiAuthState(auths: auths, activeUserId: activeUserId); } @@ -128,7 +132,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { void updateAccount(Account account) { final index = - state.auths.indexWhere((element) => element.userId == account.userId); + state.auths.indexWhere((element) => element.userId == account.userId); if (index == -1) { i('update account, but ${account.userId} auth state not found.'); return; @@ -198,7 +202,8 @@ Future _removeLegacySignalDatabase() async { await file.delete(); } } catch (error, stacktrace) { - e('_removeLegacySignalDatabase ${file.path} error: $error, stacktrace: $stacktrace'); + e('_removeLegacySignalDatabase ${file + .path} error: $error, stacktrace: $stacktrace'); } } } @@ -234,7 +239,8 @@ Future _migrationLegacySignalDatabase(String identityNumber) async { } i('migrate legacy signal database: ${file.path}'); } catch (error, stacktrace) { - e('_migrationLegacySignalDatabaseIfNecessary ${file.path} error: $error, stacktrace: $stacktrace'); + e('_migrationLegacySignalDatabaseIfNecessary ${file + .path} error: $error, stacktrace: $stacktrace'); hasError = true; } } @@ -252,13 +258,13 @@ Future _migrationLegacySignalDatabase(String identityNumber) async { } final multiAuthStateNotifierProvider = - StateNotifierProvider((ref) { +StateNotifierProvider((ref) { final multiAuthKeyValue = ref.watch(multiAuthKeyValueProvider); return MultiAuthStateNotifier(multiAuthKeyValue); }); final authProvider = - multiAuthStateNotifierProvider.select((value) => value.current); +multiAuthStateNotifierProvider.select((value) => value.current); final authAccountProvider = authProvider.select((value) => value?.account); @@ -267,7 +273,9 @@ const _keyActiveUserId = 'active_user_id'; const _keyAuthMigrated = 'auth_migrated_from_hive'; final multiAuthKeyValueProvider = Provider((ref) { - final dao = ref.watch(appDatabaseProvider).appKeyValueDao; + final dao = ref + .watch(appDatabaseProvider) + .appKeyValueDao; return MultiAuthKeyValue(dao: dao); }); From 5f10aabf4c01c4e8268b6a51bf36315dee4d9843 Mon Sep 17 00:00:00 2001 From: bin <17426470+boyan01@users.noreply.github.com> Date: Fri, 24 Nov 2023 13:30:31 +0800 Subject: [PATCH 44/44] fix format --- lib/ui/provider/account/multi_auth_provider.dart | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/ui/provider/account/multi_auth_provider.dart b/lib/ui/provider/account/multi_auth_provider.dart index f49829686c..f55f80f939 100644 --- a/lib/ui/provider/account/multi_auth_provider.dart +++ b/lib/ui/provider/account/multi_auth_provider.dart @@ -132,7 +132,7 @@ class MultiAuthStateNotifier extends DistinctStateNotifier { void updateAccount(Account account) { final index = - state.auths.indexWhere((element) => element.userId == account.userId); + state.auths.indexWhere((element) => element.userId == account.userId); if (index == -1) { i('update account, but ${account.userId} auth state not found.'); return; @@ -202,8 +202,7 @@ Future _removeLegacySignalDatabase() async { await file.delete(); } } catch (error, stacktrace) { - e('_removeLegacySignalDatabase ${file - .path} error: $error, stacktrace: $stacktrace'); + e('_removeLegacySignalDatabase ${file.path} error: $error, stacktrace: $stacktrace'); } } } @@ -239,8 +238,7 @@ Future _migrationLegacySignalDatabase(String identityNumber) async { } i('migrate legacy signal database: ${file.path}'); } catch (error, stacktrace) { - e('_migrationLegacySignalDatabaseIfNecessary ${file - .path} error: $error, stacktrace: $stacktrace'); + e('_migrationLegacySignalDatabaseIfNecessary ${file.path} error: $error, stacktrace: $stacktrace'); hasError = true; } } @@ -258,13 +256,13 @@ Future _migrationLegacySignalDatabase(String identityNumber) async { } final multiAuthStateNotifierProvider = -StateNotifierProvider((ref) { + StateNotifierProvider((ref) { final multiAuthKeyValue = ref.watch(multiAuthKeyValueProvider); return MultiAuthStateNotifier(multiAuthKeyValue); }); final authProvider = -multiAuthStateNotifierProvider.select((value) => value.current); + multiAuthStateNotifierProvider.select((value) => value.current); final authAccountProvider = authProvider.select((value) => value?.account); @@ -273,9 +271,7 @@ const _keyActiveUserId = 'active_user_id'; const _keyAuthMigrated = 'auth_migrated_from_hive'; final multiAuthKeyValueProvider = Provider((ref) { - final dao = ref - .watch(appDatabaseProvider) - .appKeyValueDao; + final dao = ref.watch(appDatabaseProvider).appKeyValueDao; return MultiAuthKeyValue(dao: dao); });