refactor cubits to keep them alive, wip

This commit is contained in:
Christien Rioux 2024-06-17 23:38:30 -04:00
parent 360ba436f8
commit 3edf2ebb46
13 changed files with 550 additions and 158 deletions

View File

@ -10,14 +10,18 @@ class AccountInfoCubit extends Cubit<AccountInfo> {
AccountInfoCubit( AccountInfoCubit(
AccountRepository accountRepository, TypedKey superIdentityRecordKey) AccountRepository accountRepository, TypedKey superIdentityRecordKey)
: _accountRepository = accountRepository, : _accountRepository = accountRepository,
super(accountRepository.getAccountInfo(superIdentityRecordKey)) { super(accountRepository.getAccountInfo(superIdentityRecordKey)!) {
// Subscribe to streams // Subscribe to streams
_accountRepositorySubscription = _accountRepository.stream.listen((change) { _accountRepositorySubscription = _accountRepository.stream.listen((change) {
switch (change) { switch (change) {
case AccountRepositoryChange.activeLocalAccount: case AccountRepositoryChange.activeLocalAccount:
case AccountRepositoryChange.localAccounts: case AccountRepositoryChange.localAccounts:
case AccountRepositoryChange.userLogins: case AccountRepositoryChange.userLogins:
emit(accountRepository.getAccountInfo(superIdentityRecordKey)); final acctInfo =
accountRepository.getAccountInfo(superIdentityRecordKey);
if (acctInfo != null) {
emit(acctInfo);
}
break; break;
} }
}); });

View File

@ -15,18 +15,13 @@ typedef AccountRecordState = proto.Account;
/// tabledb-local storage, encrypted by the unlock code for the account. /// tabledb-local storage, encrypted by the unlock code for the account.
class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> { class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
AccountRecordCubit( AccountRecordCubit(
{required AccountRepository accountRepository, {required LocalAccount localAccount, required UserLogin userLogin})
required TypedKey superIdentityRecordKey})
: super( : super(
decodeState: proto.Account.fromBuffer, decodeState: proto.Account.fromBuffer,
open: () => _open(accountRepository, superIdentityRecordKey)); open: () => _open(localAccount, userLogin));
static Future<DHTRecord> _open(AccountRepository accountRepository,
TypedKey superIdentityRecordKey) async {
final localAccount =
accountRepository.fetchLocalAccount(superIdentityRecordKey)!;
final userLogin = accountRepository.fetchUserLogin(superIdentityRecordKey)!;
static Future<DHTRecord> _open(
LocalAccount localAccount, UserLogin userLogin) async {
// Record not yet open, do it // Record not yet open, do it
final pool = DHTRecordPool.instance; final pool = DHTRecordPool.instance;
final record = await pool.openRecordOwned( final record = await pool.openRecordOwned(

View File

@ -12,12 +12,12 @@ typedef AccountRecordsBlocMapState
/// Ensures there is an single account record cubit for each logged in account /// Ensures there is an single account record cubit for each logged in account
class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey, class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey,
AsyncValue<AccountRecordState>, AccountRecordCubit> AsyncValue<AccountRecordState>, AccountRecordCubit>
with StateMapFollower<UserLoginsState, TypedKey, UserLogin> { with StateMapFollower<LocalAccountsState, TypedKey, LocalAccount> {
AccountRecordsBlocMapCubit( AccountRecordsBlocMapCubit(
AccountRepository accountRepository, Locator locator) AccountRepository accountRepository, Locator locator)
: _accountRepository = accountRepository { : _accountRepository = accountRepository {
// Follow the user logins cubit // Follow the local accounts cubit
follow(locator<UserLoginsCubit>()); follow(locator<LocalAccountsCubit>());
} }
// Add account record cubit // Add account record cubit
@ -35,9 +35,9 @@ class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey,
Future<void> removeFromState(TypedKey key) => remove(key); Future<void> removeFromState(TypedKey key) => remove(key);
@override @override
Future<void> updateState(TypedKey key, UserLogin value) async { Future<void> updateState(TypedKey key, LocalAccount value) async {
await _addAccountRecordCubit( await _addAccountRecordCubit(
superIdentityRecordKey: value.superIdentityRecordKey); superIdentityRecordKey: value.superIdentity.recordKey);
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View File

@ -1,12 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; 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:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:veilid_support/veilid_support.dart';
import '../models/models.dart'; import '../models/models.dart';
import '../repository/account_repository.dart'; import '../repository/account_repository.dart';
class LocalAccountsCubit extends Cubit<IList<LocalAccount>> { typedef LocalAccountsState = IList<LocalAccount>;
class LocalAccountsCubit extends Cubit<LocalAccountsState>
with StateMapFollowable<LocalAccountsState, TypedKey, LocalAccount> {
LocalAccountsCubit(AccountRepository accountRepository) LocalAccountsCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository, : _accountRepository = accountRepository,
super(accountRepository.getLocalAccounts()) { super(accountRepository.getLocalAccounts()) {
@ -30,6 +35,14 @@ class LocalAccountsCubit extends Cubit<IList<LocalAccount>> {
await _accountRepositorySubscription.cancel(); await _accountRepositorySubscription.cancel();
} }
/// StateMapFollowable /////////////////////////
@override
IMap<TypedKey, LocalAccount> getStateMap(LocalAccountsState state) {
final stateValue = state;
return IMap.fromIterable(stateValue,
keyMapper: (e) => e.superIdentity.recordKey, valueMapper: (e) => e);
}
final AccountRepository _accountRepository; final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange> late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription; _accountRepositorySubscription;

View File

@ -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<PerAccountCollectionState> {
PerAccountCollectionCubit({
required Locator locator,
required this.accountInfoCubit,
}) : _locator = locator,
super(_initialState(accountInfoCubit)) {
// Async Init
_initWait.add(_init);
}
@override
Future<void> close() async {
await _processor.close();
await accountInfoCubit.close();
await _accountRecordSubscription?.cancel();
await accountRecordCubit?.close();
await super.close();
}
Future<void> _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<void> _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<AccountRecordState> 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<T>() {
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<T>();
}
final Locator _locator;
final _processor = SingleStateProcessor<AccountInfo>();
final _initWait = WaitSet<void>();
// Per-account cubits regardless of login state
final AccountInfoCubit accountInfoCubit;
// Per logged-in account cubits
AccountRecordCubit? accountRecordCubit;
StreamSubscription<AsyncValue<AccountRecordState>>?
_accountRecordSubscription;
final contactInvitationListCubitUpdater = BlocUpdater<
ContactInvitationListCubit,
(Locator, TypedKey, OwnedDHTRecordPointer)>(
create: (params) => ContactInvitationListCubit(
locator: params.$1,
accountRecordKey: params.$2,
contactInvitationListRecordPointer: params.$3,
));
final contactListCubitUpdater =
BlocUpdater<ContactListCubit, (Locator, TypedKey, OwnedDHTRecordPointer)>(
create: (params) => ContactListCubit(
locator: params.$1,
accountRecordKey: params.$2,
contactListRecordPointer: params.$3,
));
final waitingInvitationsBlocMapCubitUpdater =
BlocUpdater<WaitingInvitationsBlocMapCubit, Locator>(
create: (params) => WaitingInvitationsBlocMapCubit(
locator: params,
));
final activeChatCubitUpdater =
BlocUpdater<ActiveChatCubit, bool>(create: (_) => ActiveChatCubit(null));
final chatListCubitUpdater =
BlocUpdater<ChatListCubit, (Locator, TypedKey, OwnedDHTRecordPointer)>(
create: (params) => ChatListCubit(
locator: params.$1,
accountRecordKey: params.$2,
chatListRecordPointer: params.$3,
));
}

View File

@ -1,17 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; 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:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:veilid_support/veilid_support.dart';
import '../models/models.dart'; import '../models/models.dart';
import '../repository/account_repository.dart'; import '../repository/account_repository.dart';
typedef UserLoginsState = IList<UserLogin>; typedef UserLoginsState = IList<UserLogin>;
class UserLoginsCubit extends Cubit<UserLoginsState> class UserLoginsCubit extends Cubit<UserLoginsState> {
with StateMapFollowable<UserLoginsState, TypedKey, UserLogin> {
UserLoginsCubit(AccountRepository accountRepository) UserLoginsCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository, : _accountRepository = accountRepository,
super(accountRepository.getUserLogins()) { super(accountRepository.getUserLogins()) {
@ -34,15 +31,6 @@ class UserLoginsCubit extends Cubit<UserLoginsState>
await super.close(); await super.close();
await _accountRepositorySubscription.cancel(); await _accountRepositorySubscription.cancel();
} }
/// StateMapFollowable /////////////////////////
@override
IMap<TypedKey, UserLogin> getStateMap(UserLoginsState state) {
final stateValue = state;
return IMap.fromIterable(stateValue,
keyMapper: (e) => e.superIdentityRecordKey, valueMapper: (e) => e);
}
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
final AccountRepository _accountRepository; final AccountRepository _accountRepository;

View File

@ -1,10 +1,12 @@
import 'dart:convert';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:veilid_support/veilid_support.dart';
import 'unlocked_account_info.dart'; import '../account_manager.dart';
enum AccountInfoStatus { enum AccountInfoStatus {
noAccount,
accountInvalid, accountInvalid,
accountLocked, accountLocked,
accountUnlocked, accountUnlocked,
@ -15,13 +17,49 @@ class AccountInfo extends Equatable {
const AccountInfo({ const AccountInfo({
required this.status, required this.status,
required this.active, required this.active,
required this.unlockedAccountInfo, required this.localAccount,
required this.userLogin,
}); });
final AccountInfoStatus status; final AccountInfoStatus status;
final bool active; final bool active;
final UnlockedAccountInfo? unlockedAccountInfo; final LocalAccount localAccount;
final UserLogin? userLogin;
@override @override
List<Object?> get props => [status, active, unlockedAccountInfo]; List<Object?> 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<VeilidCryptoSystem> get identityCryptoSystem =>
localAccount.superIdentity.currentInstance.cryptoSystem;
Future<VeilidCrypto> 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;
}
} }

View File

@ -1,6 +1,6 @@
export 'account_info.dart'; export 'account_info.dart';
export 'unlocked_account_info.dart';
export 'encryption_key_type.dart'; export 'encryption_key_type.dart';
export 'local_account/local_account.dart'; export 'local_account/local_account.dart';
export 'new_profile_spec.dart'; export 'new_profile_spec.dart';
export 'per_account_collection_state/per_account_collection_state.dart';
export 'user_login/user_login.dart'; export 'user_login/user_login.dart';

View File

@ -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<AccountRecordState> avAccountRecordState,
required ContactInvitationListCubit? contactInvitationListCubit}) =
_PerAccountCollectionState;
}

View File

@ -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>(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<Account> get avAccountRecordState =>
throw _privateConstructorUsedError;
ContactInvitationListCubit? get contactInvitationListCubit =>
throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$PerAccountCollectionStateCopyWith<PerAccountCollectionState> 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<Account> avAccountRecordState,
ContactInvitationListCubit? contactInvitationListCubit});
$AsyncValueCopyWith<Account, $Res> 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<Account>,
contactInvitationListCubit: freezed == contactInvitationListCubit
? _value.contactInvitationListCubit
: contactInvitationListCubit // ignore: cast_nullable_to_non_nullable
as ContactInvitationListCubit?,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$AsyncValueCopyWith<Account, $Res> get avAccountRecordState {
return $AsyncValueCopyWith<Account, $Res>(_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<Account> avAccountRecordState,
ContactInvitationListCubit? contactInvitationListCubit});
@override
$AsyncValueCopyWith<Account, $Res> 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<Account>,
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<Account> 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<Account> avAccountRecordState,
required final ContactInvitationListCubit?
contactInvitationListCubit}) = _$PerAccountCollectionStateImpl;
@override
AccountInfo get accountInfo;
@override
AsyncValue<Account> get avAccountRecordState;
@override
ContactInvitationListCubit? get contactInvitationListCubit;
@override
@JsonKey(ignore: true)
_$$PerAccountCollectionStateImplCopyWith<_$PerAccountCollectionStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -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<VeilidCryptoSystem> get identityCryptoSystem =>
localAccount.superIdentity.currentInstance.cryptoSystem;
Future<VeilidCrypto> 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<Object?> get props => [localAccount, userLogin];
}

View File

@ -92,29 +92,15 @@ class AccountRepository {
return userLogins[idx]; return userLogins[idx];
} }
AccountInfo getAccountInfo(TypedKey? superIdentityRecordKey) { AccountInfo? getAccountInfo(TypedKey superIdentityRecordKey) {
// Get active account if we have one // Get active account if we have one
final activeLocalAccount = getActiveLocalAccount(); 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; final active = superIdentityRecordKey == activeLocalAccount;
// Get which local account we want to fetch the profile for // Get which local account we want to fetch the profile for
final localAccount = fetchLocalAccount(superIdentityRecordKey); final localAccount = fetchLocalAccount(superIdentityRecordKey);
if (localAccount == null) { if (localAccount == null) {
// account does not exist return null;
return AccountInfo(
status: AccountInfoStatus.noAccount,
active: active,
unlockedAccountInfo: null);
} }
// See if we've logged into this account or if it is locked // See if we've logged into this account or if it is locked
@ -122,17 +108,19 @@ class AccountRepository {
if (userLogin == null) { if (userLogin == null) {
// Account was locked // Account was locked
return AccountInfo( return AccountInfo(
status: AccountInfoStatus.accountLocked, status: AccountInfoStatus.accountLocked,
active: active, active: active,
unlockedAccountInfo: null); localAccount: localAccount,
userLogin: null,
);
} }
// Got account, decrypted and decoded // Got account, decrypted and decoded
return AccountInfo( return AccountInfo(
status: AccountInfoStatus.accountUnlocked, status: AccountInfoStatus.accountUnlocked,
active: active, active: active,
unlockedAccountInfo: localAccount: localAccount,
UnlockedAccountInfo(localAccount: localAccount, userLogin: userLogin), userLogin: userLogin,
); );
} }

View File

@ -164,44 +164,41 @@ class HomeScreenState extends State<HomeScreen> {
], child: Builder(builder: _buildAccountReadyDeviceSpecific))); ], child: Builder(builder: _buildAccountReadyDeviceSpecific)));
} }
Widget _buildAccount(BuildContext context) { Widget _buildAccount(BuildContext context, TypedKey superIdentityRecordKey) =>
// Get active account info status BlocProvider<AccountInfoCubit>(
final ( key: ValueKey(superIdentityRecordKey),
accountInfoStatus, create: (context) => AccountInfoCubit(
accountInfoActive, AccountRepository.instance, superIdentityRecordKey),
superIdentityRecordKey child: Builder(builder: (context) {
) = context // Get active account info status
.select<AccountInfoCubit, (AccountInfoStatus, bool, TypedKey?)>((c) => ( final accountInfoStatus =
c.state.status, context.select<AccountInfoCubit, AccountInfoStatus>(
c.state.active, (c) => c.state.status);
c.state.unlockedAccountInfo?.superIdentityRecordKey
));
switch (accountInfoStatus) { switch (accountInfoStatus) {
case AccountInfoStatus.noAccount: case AccountInfoStatus.noAccount:
return const HomeAccountMissing(); return const HomeAccountMissing();
case AccountInfoStatus.accountInvalid: case AccountInfoStatus.accountInvalid:
return const HomeAccountInvalid(); return const HomeAccountInvalid();
case AccountInfoStatus.accountLocked: case AccountInfoStatus.accountLocked:
return const HomeAccountLocked(); return const HomeAccountLocked();
case AccountInfoStatus.accountUnlocked: case AccountInfoStatus.accountUnlocked:
// Get the current active account record cubit // Get the current active account record cubit
final activeAccountRecordCubit = final activeAccountRecordCubit = context
context.select<AccountRecordsBlocMapCubit, AccountRecordCubit?>( .select<AccountRecordsBlocMapCubit, AccountRecordCubit?>(
(c) => superIdentityRecordKey == null (c) => c.tryOperate(superIdentityRecordKey,
? null closure: (x) => x));
: c.tryOperate(superIdentityRecordKey, closure: (x) => x)); if (activeAccountRecordCubit == null) {
if (activeAccountRecordCubit == null) { return waitingPage();
return waitingPage(); }
}
return MultiBlocProvider(providers: [ return MultiBlocProvider(providers: [
BlocProvider<AccountRecordCubit>.value( BlocProvider<AccountRecordCubit>.value(
value: activeAccountRecordCubit), value: activeAccountRecordCubit),
], child: Builder(builder: _buildUnlockedAccount)); ], child: Builder(builder: _buildUnlockedAccount));
} }
} }));
Widget _buildAccountPageView(BuildContext context) { Widget _buildAccountPageView(BuildContext context) {
final localAccounts = context.watch<LocalAccountsCubit>().state; final localAccounts = context.watch<LocalAccountsCubit>().state;
@ -221,8 +218,7 @@ class HomeScreenState extends State<HomeScreen> {
value.dispose(); value.dispose();
}, },
child: Builder( child: Builder(
builder: (context) => PageView.builder( builder: (context) => PageView.custom(
itemCount: localAccounts.length,
onPageChanged: (idx) { onPageChanged: (idx) {
singleFuture(this, () async { singleFuture(this, () async {
await AccountRepository.instance.switchToAccount( await AccountRepository.instance.switchToAccount(
@ -232,15 +228,10 @@ class HomeScreenState extends State<HomeScreen> {
controller: context controller: context
.read<ActiveAccountPageControllerWrapper>() .read<ActiveAccountPageControllerWrapper>()
.pageController, .pageController,
itemBuilder: (context, index) { childrenDelegate: SliverChildListDelegate(localAccounts
final localAccount = localAccounts[index]; .map((la) =>
return BlocProvider<AccountInfoCubit>( _buildAccount(context, la.superIdentity.recordKey))
key: ValueKey(localAccount.superIdentity.recordKey), .toList()))));
create: (context) => AccountInfoCubit(
AccountRepository.instance,
localAccount.superIdentity.recordKey),
child: Builder(builder: _buildAccount));
})));
} }
@override @override