From 3edf2ebb468b0f560aeeb158b86445a69b25a8dc Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 17 Jun 2024 23:38:30 -0400 Subject: [PATCH] refactor cubits to keep them alive, wip --- .../cubits/account_info_cubit.dart | 8 +- .../cubits/account_record_cubit.dart | 13 +- .../account_records_bloc_map_cubit.dart | 10 +- .../cubits/local_accounts_cubit.dart | 15 +- .../cubits/per_account_collection_cubit.dart | 209 ++++++++++++++++++ .../cubits/user_logins_cubit.dart | 14 +- lib/account_manager/models/account_info.dart | 48 +++- lib/account_manager/models/models.dart | 2 +- .../per_account_collection_state.dart | 17 ++ .../per_account_collection_state.freezed.dart | 204 +++++++++++++++++ .../models/unlocked_account_info.dart | 55 ----- .../repository/account_repository.dart | 30 +-- lib/layout/home/home_screen.dart | 83 ++++--- 13 files changed, 550 insertions(+), 158 deletions(-) create mode 100644 lib/account_manager/cubits/per_account_collection_cubit.dart create mode 100644 lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart create mode 100644 lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart delete mode 100644 lib/account_manager/models/unlocked_account_info.dart diff --git a/lib/account_manager/cubits/account_info_cubit.dart b/lib/account_manager/cubits/account_info_cubit.dart index 234e694..d34b107 100644 --- a/lib/account_manager/cubits/account_info_cubit.dart +++ b/lib/account_manager/cubits/account_info_cubit.dart @@ -10,14 +10,18 @@ class AccountInfoCubit extends Cubit { AccountInfoCubit( AccountRepository accountRepository, TypedKey superIdentityRecordKey) : _accountRepository = accountRepository, - super(accountRepository.getAccountInfo(superIdentityRecordKey)) { + super(accountRepository.getAccountInfo(superIdentityRecordKey)!) { // Subscribe to streams _accountRepositorySubscription = _accountRepository.stream.listen((change) { switch (change) { case AccountRepositoryChange.activeLocalAccount: case AccountRepositoryChange.localAccounts: case AccountRepositoryChange.userLogins: - emit(accountRepository.getAccountInfo(superIdentityRecordKey)); + final acctInfo = + accountRepository.getAccountInfo(superIdentityRecordKey); + if (acctInfo != null) { + emit(acctInfo); + } break; } }); diff --git a/lib/account_manager/cubits/account_record_cubit.dart b/lib/account_manager/cubits/account_record_cubit.dart index b393161..4028d65 100644 --- a/lib/account_manager/cubits/account_record_cubit.dart +++ b/lib/account_manager/cubits/account_record_cubit.dart @@ -15,18 +15,13 @@ typedef AccountRecordState = proto.Account; /// tabledb-local storage, encrypted by the unlock code for the account. class AccountRecordCubit extends DefaultDHTRecordCubit { AccountRecordCubit( - {required AccountRepository accountRepository, - required TypedKey superIdentityRecordKey}) + {required LocalAccount localAccount, required UserLogin userLogin}) : super( decodeState: proto.Account.fromBuffer, - open: () => _open(accountRepository, superIdentityRecordKey)); - - static Future _open(AccountRepository accountRepository, - TypedKey superIdentityRecordKey) async { - final localAccount = - accountRepository.fetchLocalAccount(superIdentityRecordKey)!; - final userLogin = accountRepository.fetchUserLogin(superIdentityRecordKey)!; + open: () => _open(localAccount, userLogin)); + static Future _open( + LocalAccount localAccount, UserLogin userLogin) async { // Record not yet open, do it final pool = DHTRecordPool.instance; final record = await pool.openRecordOwned( diff --git a/lib/account_manager/cubits/account_records_bloc_map_cubit.dart b/lib/account_manager/cubits/account_records_bloc_map_cubit.dart index eee6d1f..50a7e60 100644 --- a/lib/account_manager/cubits/account_records_bloc_map_cubit.dart +++ b/lib/account_manager/cubits/account_records_bloc_map_cubit.dart @@ -12,12 +12,12 @@ typedef AccountRecordsBlocMapState /// Ensures there is an single account record cubit for each logged in account class AccountRecordsBlocMapCubit extends BlocMapCubit, AccountRecordCubit> - with StateMapFollower { + with StateMapFollower { AccountRecordsBlocMapCubit( AccountRepository accountRepository, Locator locator) : _accountRepository = accountRepository { - // Follow the user logins cubit - follow(locator()); + // Follow the local accounts cubit + follow(locator()); } // Add account record cubit @@ -35,9 +35,9 @@ class AccountRecordsBlocMapCubit extends BlocMapCubit removeFromState(TypedKey key) => remove(key); @override - Future updateState(TypedKey key, UserLogin value) async { + Future updateState(TypedKey key, LocalAccount value) async { await _addAccountRecordCubit( - superIdentityRecordKey: value.superIdentityRecordKey); + superIdentityRecordKey: value.superIdentity.recordKey); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/account_manager/cubits/local_accounts_cubit.dart b/lib/account_manager/cubits/local_accounts_cubit.dart index a324602..704d8c5 100644 --- a/lib/account_manager/cubits/local_accounts_cubit.dart +++ b/lib/account_manager/cubits/local_accounts_cubit.dart @@ -1,12 +1,17 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:veilid_support/veilid_support.dart'; import '../models/models.dart'; import '../repository/account_repository.dart'; -class LocalAccountsCubit extends Cubit> { +typedef LocalAccountsState = IList; + +class LocalAccountsCubit extends Cubit + with StateMapFollowable { LocalAccountsCubit(AccountRepository accountRepository) : _accountRepository = accountRepository, super(accountRepository.getLocalAccounts()) { @@ -30,6 +35,14 @@ class LocalAccountsCubit extends Cubit> { await _accountRepositorySubscription.cancel(); } + /// StateMapFollowable ///////////////////////// + @override + IMap getStateMap(LocalAccountsState state) { + final stateValue = state; + return IMap.fromIterable(stateValue, + keyMapper: (e) => e.superIdentity.recordKey, valueMapper: (e) => e); + } + final AccountRepository _accountRepository; late final StreamSubscription _accountRepositorySubscription; diff --git a/lib/account_manager/cubits/per_account_collection_cubit.dart b/lib/account_manager/cubits/per_account_collection_cubit.dart new file mode 100644 index 0000000..e76488a --- /dev/null +++ b/lib/account_manager/cubits/per_account_collection_cubit.dart @@ -0,0 +1,209 @@ +import 'dart:async'; + +import 'package:async_tools/async_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; +import 'package:veilid_support/veilid_support.dart'; + +import '../../chat/chat.dart'; +import '../../chat_list/chat_list.dart'; +import '../../contact_invitation/contact_invitation.dart'; +import '../../contacts/contacts.dart'; +import '../../proto/proto.dart' as proto; +import '../account_manager.dart'; + +class PerAccountCollectionCubit extends Cubit { + PerAccountCollectionCubit({ + required Locator locator, + required this.accountInfoCubit, + }) : _locator = locator, + super(_initialState(accountInfoCubit)) { + // Async Init + _initWait.add(_init); + } + + @override + Future close() async { + await _processor.close(); + await accountInfoCubit.close(); + await _accountRecordSubscription?.cancel(); + await accountRecordCubit?.close(); + + await super.close(); + } + + Future _init() async { + await _initWait(); + + // subscribe to accountInfo changes + _processor.follow(accountInfoCubit.stream, accountInfoCubit.state, + _followAccountInfoState); + } + + static PerAccountCollectionState _initialState( + AccountInfoCubit accountInfoCubit) => + PerAccountCollectionState( + accountInfo: accountInfoCubit.state, + avAccountRecordState: const AsyncValue.loading(), + contactInvitationListCubit: null); + + Future _followAccountInfoState(AccountInfo accountInfo) async { + var nextState = state.copyWith(accountInfo: accountInfo); + + if (accountInfo.userLogin == null) { + /////////////// Not logged in ///////////////// + + // Unsubscribe AccountRecordCubit + await _accountRecordSubscription?.cancel(); + _accountRecordSubscription = null; + + // Update state + nextState = + _updateAccountRecordState(nextState, const AsyncValue.loading()); + emit(nextState); + + // Close AccountRecordCubit + await accountRecordCubit?.close(); + accountRecordCubit = null; + } else { + ///////////////// Logged in /////////////////// + + // AccountRecordCubit + accountRecordCubit ??= AccountRecordCubit( + localAccount: accountInfo.localAccount, + userLogin: accountInfo.userLogin!); + + // Update State + nextState = + _updateAccountRecordState(nextState, accountRecordCubit!.state); + emit(nextState); + + // Subscribe AccountRecordCubit + _accountRecordSubscription ??= + accountRecordCubit!.stream.listen((avAccountRecordState) { + emit(_updateAccountRecordState(state, avAccountRecordState)); + }); + } + } + + PerAccountCollectionState _updateAccountRecordState( + PerAccountCollectionState prevState, + AsyncValue avAccountRecordState) { + // Get next state + final nextState = + state.copyWith(avAccountRecordState: accountRecordCubit!.state); + + // Get bloc parameters + final accountRecordKey = nextState.accountInfo.accountRecordKey; + + // ContactInvitationListCubit + final contactInvitationListRecordPointer = nextState + .avAccountRecordState.asData?.value.contactInvitationRecords + .toVeilid(); + + contactInvitationListCubitUpdater.update( + contactInvitationListRecordPointer == null + ? null + : ( + collectionLocator, + accountRecordKey, + contactInvitationListRecordPointer + )); + + // ContactListCubit + final contactListRecordPointer = + nextState.avAccountRecordState.asData?.value.contactList.toVeilid(); + + contactListCubitUpdater.update(contactListRecordPointer == null + ? null + : (collectionLocator, accountRecordKey, contactListRecordPointer)); + + // WaitingInvitationsBlocMapCubit + waitingInvitationsBlocMapCubitUpdater.update( + nextState.avAccountRecordState.isData ? collectionLocator : null); + + // ActiveChatCubit + activeChatCubitUpdater + .update(nextState.avAccountRecordState.isData ? true : null); + + // ChatListCubit + final chatListRecordPointer = + nextState.avAccountRecordState.asData?.value.chatList.toVeilid(); + + chatListCubitUpdater.update(chatListRecordPointer == null + ? null + : (collectionLocator, accountRecordKey, chatListRecordPointer)); + + // ActiveConversationsBlocMapCubit + // xxx + + return nextState; + } + + T collectionLocator() { + if (T is AccountInfoCubit) { + return accountInfoCubit as T; + } + if (T is AccountRecordCubit) { + return accountRecordCubit! as T; + } + if (T is ContactInvitationListCubit) { + return contactInvitationListCubitUpdater.bloc! as T; + } + if (T is ContactListCubit) { + return contactListCubitUpdater.bloc! as T; + } + if (T is WaitingInvitationsBlocMapCubit) { + return waitingInvitationsBlocMapCubitUpdater.bloc! as T; + } + if (T is ActiveChatCubit) { + return activeChatCubitUpdater.bloc! as T; + } + if (T is ChatListCubit) { + return chatListCubitUpdater.bloc! as T; + } + return _locator(); + } + + final Locator _locator; + final _processor = SingleStateProcessor(); + final _initWait = WaitSet(); + + // Per-account cubits regardless of login state + final AccountInfoCubit accountInfoCubit; + + // Per logged-in account cubits + AccountRecordCubit? accountRecordCubit; + StreamSubscription>? + _accountRecordSubscription; + final contactInvitationListCubitUpdater = BlocUpdater< + ContactInvitationListCubit, + (Locator, TypedKey, OwnedDHTRecordPointer)>( + create: (params) => ContactInvitationListCubit( + locator: params.$1, + accountRecordKey: params.$2, + contactInvitationListRecordPointer: params.$3, + )); + final contactListCubitUpdater = + BlocUpdater( + create: (params) => ContactListCubit( + locator: params.$1, + accountRecordKey: params.$2, + contactListRecordPointer: params.$3, + )); + final waitingInvitationsBlocMapCubitUpdater = + BlocUpdater( + create: (params) => WaitingInvitationsBlocMapCubit( + locator: params, + )); + final activeChatCubitUpdater = + BlocUpdater(create: (_) => ActiveChatCubit(null)); + final chatListCubitUpdater = + BlocUpdater( + create: (params) => ChatListCubit( + locator: params.$1, + accountRecordKey: params.$2, + chatListRecordPointer: params.$3, + )); +} diff --git a/lib/account_manager/cubits/user_logins_cubit.dart b/lib/account_manager/cubits/user_logins_cubit.dart index 75d6ad1..734ced3 100644 --- a/lib/account_manager/cubits/user_logins_cubit.dart +++ b/lib/account_manager/cubits/user_logins_cubit.dart @@ -1,17 +1,14 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; -import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:veilid_support/veilid_support.dart'; import '../models/models.dart'; import '../repository/account_repository.dart'; typedef UserLoginsState = IList; -class UserLoginsCubit extends Cubit - with StateMapFollowable { +class UserLoginsCubit extends Cubit { UserLoginsCubit(AccountRepository accountRepository) : _accountRepository = accountRepository, super(accountRepository.getUserLogins()) { @@ -34,15 +31,6 @@ class UserLoginsCubit extends Cubit await super.close(); await _accountRepositorySubscription.cancel(); } - - /// StateMapFollowable ///////////////////////// - @override - IMap getStateMap(UserLoginsState state) { - final stateValue = state; - return IMap.fromIterable(stateValue, - keyMapper: (e) => e.superIdentityRecordKey, valueMapper: (e) => e); - } - //////////////////////////////////////////////////////////////////////////// final AccountRepository _accountRepository; diff --git a/lib/account_manager/models/account_info.dart b/lib/account_manager/models/account_info.dart index 78b330e..a2a9a39 100644 --- a/lib/account_manager/models/account_info.dart +++ b/lib/account_manager/models/account_info.dart @@ -1,10 +1,12 @@ +import 'dart:convert'; + import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; +import 'package:veilid_support/veilid_support.dart'; -import 'unlocked_account_info.dart'; +import '../account_manager.dart'; enum AccountInfoStatus { - noAccount, accountInvalid, accountLocked, accountUnlocked, @@ -15,13 +17,49 @@ class AccountInfo extends Equatable { const AccountInfo({ required this.status, required this.active, - required this.unlockedAccountInfo, + required this.localAccount, + required this.userLogin, }); final AccountInfoStatus status; final bool active; - final UnlockedAccountInfo? unlockedAccountInfo; + final LocalAccount localAccount; + final UserLogin? userLogin; @override - List get props => [status, active, unlockedAccountInfo]; + List get props => [ + status, + active, + localAccount, + userLogin, + ]; +} + +extension AccountInfoExt on AccountInfo { + TypedKey get superIdentityRecordKey => localAccount.superIdentity.recordKey; + TypedKey get accountRecordKey => + userLogin!.accountRecordInfo.accountRecord.recordKey; + TypedKey get identityTypedPublicKey => + localAccount.superIdentity.currentInstance.typedPublicKey; + PublicKey get identityPublicKey => + localAccount.superIdentity.currentInstance.publicKey; + SecretKey get identitySecretKey => userLogin!.identitySecret.value; + KeyPair get identityWriter => + KeyPair(key: identityPublicKey, secret: identitySecretKey); + Future get identityCryptoSystem => + localAccount.superIdentity.currentInstance.cryptoSystem; + + Future makeConversationCrypto( + TypedKey remoteIdentityPublicKey) async { + final identitySecret = userLogin!.identitySecret; + final cs = await Veilid.instance.getCryptoSystem(identitySecret.kind); + final sharedSecret = await cs.generateSharedSecret( + remoteIdentityPublicKey.value, + identitySecret.value, + utf8.encode('VeilidChat Conversation')); + + final messagesCrypto = await VeilidCryptoPrivate.fromSharedSecret( + identitySecret.kind, sharedSecret); + return messagesCrypto; + } } diff --git a/lib/account_manager/models/models.dart b/lib/account_manager/models/models.dart index bb08695..2860eec 100644 --- a/lib/account_manager/models/models.dart +++ b/lib/account_manager/models/models.dart @@ -1,6 +1,6 @@ export 'account_info.dart'; -export 'unlocked_account_info.dart'; export 'encryption_key_type.dart'; export 'local_account/local_account.dart'; export 'new_profile_spec.dart'; +export 'per_account_collection_state/per_account_collection_state.dart'; export 'user_login/user_login.dart'; diff --git a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart new file mode 100644 index 0000000..9794784 --- /dev/null +++ b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart @@ -0,0 +1,17 @@ +import 'package:async_tools/async_tools.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../../contact_invitation/contact_invitation.dart'; +import '../../../proto/proto.dart' show Account; +import '../../account_manager.dart'; + +part 'per_account_collection_state.freezed.dart'; + +@freezed +class PerAccountCollectionState with _$PerAccountCollectionState { + const factory PerAccountCollectionState( + {required AccountInfo accountInfo, + required AsyncValue avAccountRecordState, + required ContactInvitationListCubit? contactInvitationListCubit}) = + _PerAccountCollectionState; +} diff --git a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart new file mode 100644 index 0000000..d49f42a --- /dev/null +++ b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart @@ -0,0 +1,204 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'per_account_collection_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$PerAccountCollectionState { + AccountInfo get accountInfo => throw _privateConstructorUsedError; + AsyncValue get avAccountRecordState => + throw _privateConstructorUsedError; + ContactInvitationListCubit? get contactInvitationListCubit => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $PerAccountCollectionStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PerAccountCollectionStateCopyWith<$Res> { + factory $PerAccountCollectionStateCopyWith(PerAccountCollectionState value, + $Res Function(PerAccountCollectionState) then) = + _$PerAccountCollectionStateCopyWithImpl<$Res, PerAccountCollectionState>; + @useResult + $Res call( + {AccountInfo accountInfo, + AsyncValue avAccountRecordState, + ContactInvitationListCubit? contactInvitationListCubit}); + + $AsyncValueCopyWith get avAccountRecordState; +} + +/// @nodoc +class _$PerAccountCollectionStateCopyWithImpl<$Res, + $Val extends PerAccountCollectionState> + implements $PerAccountCollectionStateCopyWith<$Res> { + _$PerAccountCollectionStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountInfo = null, + Object? avAccountRecordState = null, + Object? contactInvitationListCubit = freezed, + }) { + return _then(_value.copyWith( + accountInfo: null == accountInfo + ? _value.accountInfo + : accountInfo // ignore: cast_nullable_to_non_nullable + as AccountInfo, + avAccountRecordState: null == avAccountRecordState + ? _value.avAccountRecordState + : avAccountRecordState // ignore: cast_nullable_to_non_nullable + as AsyncValue, + contactInvitationListCubit: freezed == contactInvitationListCubit + ? _value.contactInvitationListCubit + : contactInvitationListCubit // ignore: cast_nullable_to_non_nullable + as ContactInvitationListCubit?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $AsyncValueCopyWith get avAccountRecordState { + return $AsyncValueCopyWith(_value.avAccountRecordState, + (value) { + return _then(_value.copyWith(avAccountRecordState: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$PerAccountCollectionStateImplCopyWith<$Res> + implements $PerAccountCollectionStateCopyWith<$Res> { + factory _$$PerAccountCollectionStateImplCopyWith( + _$PerAccountCollectionStateImpl value, + $Res Function(_$PerAccountCollectionStateImpl) then) = + __$$PerAccountCollectionStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {AccountInfo accountInfo, + AsyncValue avAccountRecordState, + ContactInvitationListCubit? contactInvitationListCubit}); + + @override + $AsyncValueCopyWith get avAccountRecordState; +} + +/// @nodoc +class __$$PerAccountCollectionStateImplCopyWithImpl<$Res> + extends _$PerAccountCollectionStateCopyWithImpl<$Res, + _$PerAccountCollectionStateImpl> + implements _$$PerAccountCollectionStateImplCopyWith<$Res> { + __$$PerAccountCollectionStateImplCopyWithImpl( + _$PerAccountCollectionStateImpl _value, + $Res Function(_$PerAccountCollectionStateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountInfo = null, + Object? avAccountRecordState = null, + Object? contactInvitationListCubit = freezed, + }) { + return _then(_$PerAccountCollectionStateImpl( + accountInfo: null == accountInfo + ? _value.accountInfo + : accountInfo // ignore: cast_nullable_to_non_nullable + as AccountInfo, + avAccountRecordState: null == avAccountRecordState + ? _value.avAccountRecordState + : avAccountRecordState // ignore: cast_nullable_to_non_nullable + as AsyncValue, + contactInvitationListCubit: freezed == contactInvitationListCubit + ? _value.contactInvitationListCubit + : contactInvitationListCubit // ignore: cast_nullable_to_non_nullable + as ContactInvitationListCubit?, + )); + } +} + +/// @nodoc + +class _$PerAccountCollectionStateImpl implements _PerAccountCollectionState { + const _$PerAccountCollectionStateImpl( + {required this.accountInfo, + required this.avAccountRecordState, + required this.contactInvitationListCubit}); + + @override + final AccountInfo accountInfo; + @override + final AsyncValue avAccountRecordState; + @override + final ContactInvitationListCubit? contactInvitationListCubit; + + @override + String toString() { + return 'PerAccountCollectionState(accountInfo: $accountInfo, avAccountRecordState: $avAccountRecordState, contactInvitationListCubit: $contactInvitationListCubit)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PerAccountCollectionStateImpl && + (identical(other.accountInfo, accountInfo) || + other.accountInfo == accountInfo) && + (identical(other.avAccountRecordState, avAccountRecordState) || + other.avAccountRecordState == avAccountRecordState) && + (identical(other.contactInvitationListCubit, + contactInvitationListCubit) || + other.contactInvitationListCubit == + contactInvitationListCubit)); + } + + @override + int get hashCode => Object.hash(runtimeType, accountInfo, + avAccountRecordState, contactInvitationListCubit); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$PerAccountCollectionStateImplCopyWith<_$PerAccountCollectionStateImpl> + get copyWith => __$$PerAccountCollectionStateImplCopyWithImpl< + _$PerAccountCollectionStateImpl>(this, _$identity); +} + +abstract class _PerAccountCollectionState implements PerAccountCollectionState { + const factory _PerAccountCollectionState( + {required final AccountInfo accountInfo, + required final AsyncValue avAccountRecordState, + required final ContactInvitationListCubit? + contactInvitationListCubit}) = _$PerAccountCollectionStateImpl; + + @override + AccountInfo get accountInfo; + @override + AsyncValue get avAccountRecordState; + @override + ContactInvitationListCubit? get contactInvitationListCubit; + @override + @JsonKey(ignore: true) + _$$PerAccountCollectionStateImplCopyWith<_$PerAccountCollectionStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/account_manager/models/unlocked_account_info.dart b/lib/account_manager/models/unlocked_account_info.dart deleted file mode 100644 index 2aa5bdb..0000000 --- a/lib/account_manager/models/unlocked_account_info.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'dart:convert'; - -import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; -import 'package:veilid_support/veilid_support.dart'; - -import 'local_account/local_account.dart'; -import 'user_login/user_login.dart'; - -@immutable -class UnlockedAccountInfo extends Equatable { - const UnlockedAccountInfo({ - required this.localAccount, - required this.userLogin, - }); - - //////////////////////////////////////////////////////////////////////////// - // Public Interface - - TypedKey get superIdentityRecordKey => localAccount.superIdentity.recordKey; - TypedKey get accountRecordKey => - userLogin.accountRecordInfo.accountRecord.recordKey; - TypedKey get identityTypedPublicKey => - localAccount.superIdentity.currentInstance.typedPublicKey; - PublicKey get identityPublicKey => - localAccount.superIdentity.currentInstance.publicKey; - SecretKey get identitySecretKey => userLogin.identitySecret.value; - KeyPair get identityWriter => - KeyPair(key: identityPublicKey, secret: identitySecretKey); - Future get identityCryptoSystem => - localAccount.superIdentity.currentInstance.cryptoSystem; - - Future makeConversationCrypto( - TypedKey remoteIdentityPublicKey) async { - final identitySecret = userLogin.identitySecret; - final cs = await Veilid.instance.getCryptoSystem(identitySecret.kind); - final sharedSecret = await cs.generateSharedSecret( - remoteIdentityPublicKey.value, - identitySecret.value, - utf8.encode('VeilidChat Conversation')); - - final messagesCrypto = await VeilidCryptoPrivate.fromSharedSecret( - identitySecret.kind, sharedSecret); - return messagesCrypto; - } - - //////////////////////////////////////////////////////////////////////////// - // Fields - - final LocalAccount localAccount; - final UserLogin userLogin; - - @override - List get props => [localAccount, userLogin]; -} diff --git a/lib/account_manager/repository/account_repository.dart b/lib/account_manager/repository/account_repository.dart index ff916ac..73ece3e 100644 --- a/lib/account_manager/repository/account_repository.dart +++ b/lib/account_manager/repository/account_repository.dart @@ -92,29 +92,15 @@ class AccountRepository { return userLogins[idx]; } - AccountInfo getAccountInfo(TypedKey? superIdentityRecordKey) { + AccountInfo? getAccountInfo(TypedKey superIdentityRecordKey) { // Get active account if we have one final activeLocalAccount = getActiveLocalAccount(); - if (superIdentityRecordKey == null) { - if (activeLocalAccount == null) { - // No user logged in - return const AccountInfo( - status: AccountInfoStatus.noAccount, - active: false, - unlockedAccountInfo: null); - } - superIdentityRecordKey = activeLocalAccount; - } final active = superIdentityRecordKey == activeLocalAccount; // Get which local account we want to fetch the profile for final localAccount = fetchLocalAccount(superIdentityRecordKey); if (localAccount == null) { - // account does not exist - return AccountInfo( - status: AccountInfoStatus.noAccount, - active: active, - unlockedAccountInfo: null); + return null; } // See if we've logged into this account or if it is locked @@ -122,17 +108,19 @@ class AccountRepository { if (userLogin == null) { // Account was locked return AccountInfo( - status: AccountInfoStatus.accountLocked, - active: active, - unlockedAccountInfo: null); + status: AccountInfoStatus.accountLocked, + active: active, + localAccount: localAccount, + userLogin: null, + ); } // Got account, decrypted and decoded return AccountInfo( status: AccountInfoStatus.accountUnlocked, active: active, - unlockedAccountInfo: - UnlockedAccountInfo(localAccount: localAccount, userLogin: userLogin), + localAccount: localAccount, + userLogin: userLogin, ); } diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index 53fef55..d064ad7 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -164,44 +164,41 @@ class HomeScreenState extends State { ], child: Builder(builder: _buildAccountReadyDeviceSpecific))); } - Widget _buildAccount(BuildContext context) { - // Get active account info status - final ( - accountInfoStatus, - accountInfoActive, - superIdentityRecordKey - ) = context - .select((c) => ( - c.state.status, - c.state.active, - c.state.unlockedAccountInfo?.superIdentityRecordKey - )); + Widget _buildAccount(BuildContext context, TypedKey superIdentityRecordKey) => + BlocProvider( + key: ValueKey(superIdentityRecordKey), + create: (context) => AccountInfoCubit( + AccountRepository.instance, superIdentityRecordKey), + child: Builder(builder: (context) { + // Get active account info status + final accountInfoStatus = + context.select( + (c) => c.state.status); - switch (accountInfoStatus) { - case AccountInfoStatus.noAccount: - return const HomeAccountMissing(); - case AccountInfoStatus.accountInvalid: - return const HomeAccountInvalid(); - case AccountInfoStatus.accountLocked: - return const HomeAccountLocked(); - case AccountInfoStatus.accountUnlocked: + switch (accountInfoStatus) { + case AccountInfoStatus.noAccount: + return const HomeAccountMissing(); + case AccountInfoStatus.accountInvalid: + return const HomeAccountInvalid(); + case AccountInfoStatus.accountLocked: + return const HomeAccountLocked(); + case AccountInfoStatus.accountUnlocked: - // Get the current active account record cubit - final activeAccountRecordCubit = - context.select( - (c) => superIdentityRecordKey == null - ? null - : c.tryOperate(superIdentityRecordKey, closure: (x) => x)); - if (activeAccountRecordCubit == null) { - return waitingPage(); - } + // Get the current active account record cubit + final activeAccountRecordCubit = context + .select( + (c) => c.tryOperate(superIdentityRecordKey, + closure: (x) => x)); + if (activeAccountRecordCubit == null) { + return waitingPage(); + } - return MultiBlocProvider(providers: [ - BlocProvider.value( - value: activeAccountRecordCubit), - ], child: Builder(builder: _buildUnlockedAccount)); - } - } + return MultiBlocProvider(providers: [ + BlocProvider.value( + value: activeAccountRecordCubit), + ], child: Builder(builder: _buildUnlockedAccount)); + } + })); Widget _buildAccountPageView(BuildContext context) { final localAccounts = context.watch().state; @@ -221,8 +218,7 @@ class HomeScreenState extends State { value.dispose(); }, child: Builder( - builder: (context) => PageView.builder( - itemCount: localAccounts.length, + builder: (context) => PageView.custom( onPageChanged: (idx) { singleFuture(this, () async { await AccountRepository.instance.switchToAccount( @@ -232,15 +228,10 @@ class HomeScreenState extends State { controller: context .read() .pageController, - itemBuilder: (context, index) { - final localAccount = localAccounts[index]; - return BlocProvider( - key: ValueKey(localAccount.superIdentity.recordKey), - create: (context) => AccountInfoCubit( - AccountRepository.instance, - localAccount.superIdentity.recordKey), - child: Builder(builder: _buildAccount)); - }))); + childrenDelegate: SliverChildListDelegate(localAccounts + .map((la) => + _buildAccount(context, la.superIdentity.recordKey)) + .toList())))); } @override