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(
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;
}
});

View File

@ -15,18 +15,13 @@ typedef AccountRecordState = proto.Account;
/// tabledb-local storage, encrypted by the unlock code for the account.
class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
AccountRecordCubit(
{required AccountRepository accountRepository,
required TypedKey superIdentityRecordKey})
{required LocalAccount localAccount, required UserLogin userLogin})
: super(
decodeState: proto.Account.fromBuffer,
open: () => _open(accountRepository, superIdentityRecordKey));
static Future<DHTRecord> _open(AccountRepository accountRepository,
TypedKey superIdentityRecordKey) async {
final localAccount =
accountRepository.fetchLocalAccount(superIdentityRecordKey)!;
final userLogin = accountRepository.fetchUserLogin(superIdentityRecordKey)!;
open: () => _open(localAccount, userLogin));
static Future<DHTRecord> _open(
LocalAccount localAccount, UserLogin userLogin) async {
// Record not yet open, do it
final pool = DHTRecordPool.instance;
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
class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey,
AsyncValue<AccountRecordState>, AccountRecordCubit>
with StateMapFollower<UserLoginsState, TypedKey, UserLogin> {
with StateMapFollower<LocalAccountsState, TypedKey, LocalAccount> {
AccountRecordsBlocMapCubit(
AccountRepository accountRepository, Locator locator)
: _accountRepository = accountRepository {
// Follow the user logins cubit
follow(locator<UserLoginsCubit>());
// Follow the local accounts cubit
follow(locator<LocalAccountsCubit>());
}
// Add account record cubit
@ -35,9 +35,9 @@ class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey,
Future<void> removeFromState(TypedKey key) => remove(key);
@override
Future<void> updateState(TypedKey key, UserLogin value) async {
Future<void> updateState(TypedKey key, LocalAccount value) async {
await _addAccountRecordCubit(
superIdentityRecordKey: value.superIdentityRecordKey);
superIdentityRecordKey: value.superIdentity.recordKey);
}
////////////////////////////////////////////////////////////////////////////

View File

@ -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<IList<LocalAccount>> {
typedef LocalAccountsState = IList<LocalAccount>;
class LocalAccountsCubit extends Cubit<LocalAccountsState>
with StateMapFollowable<LocalAccountsState, TypedKey, LocalAccount> {
LocalAccountsCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository,
super(accountRepository.getLocalAccounts()) {
@ -30,6 +35,14 @@ class LocalAccountsCubit extends Cubit<IList<LocalAccount>> {
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;
late final StreamSubscription<AccountRepositoryChange>
_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 '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<UserLogin>;
class UserLoginsCubit extends Cubit<UserLoginsState>
with StateMapFollowable<UserLoginsState, TypedKey, UserLogin> {
class UserLoginsCubit extends Cubit<UserLoginsState> {
UserLoginsCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository,
super(accountRepository.getUserLogins()) {
@ -34,15 +31,6 @@ class UserLoginsCubit extends Cubit<UserLoginsState>
await super.close();
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;

View File

@ -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<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 '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';

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];
}
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,
);
}

View File

@ -164,44 +164,41 @@ class HomeScreenState extends State<HomeScreen> {
], child: Builder(builder: _buildAccountReadyDeviceSpecific)));
}
Widget _buildAccount(BuildContext context) {
// Get active account info status
final (
accountInfoStatus,
accountInfoActive,
superIdentityRecordKey
) = context
.select<AccountInfoCubit, (AccountInfoStatus, bool, TypedKey?)>((c) => (
c.state.status,
c.state.active,
c.state.unlockedAccountInfo?.superIdentityRecordKey
));
Widget _buildAccount(BuildContext context, TypedKey superIdentityRecordKey) =>
BlocProvider<AccountInfoCubit>(
key: ValueKey(superIdentityRecordKey),
create: (context) => AccountInfoCubit(
AccountRepository.instance, superIdentityRecordKey),
child: Builder(builder: (context) {
// Get active account info status
final accountInfoStatus =
context.select<AccountInfoCubit, AccountInfoStatus>(
(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<AccountRecordsBlocMapCubit, AccountRecordCubit?>(
(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<AccountRecordsBlocMapCubit, AccountRecordCubit?>(
(c) => c.tryOperate(superIdentityRecordKey,
closure: (x) => x));
if (activeAccountRecordCubit == null) {
return waitingPage();
}
return MultiBlocProvider(providers: [
BlocProvider<AccountRecordCubit>.value(
value: activeAccountRecordCubit),
], child: Builder(builder: _buildUnlockedAccount));
}
}
return MultiBlocProvider(providers: [
BlocProvider<AccountRecordCubit>.value(
value: activeAccountRecordCubit),
], child: Builder(builder: _buildUnlockedAccount));
}
}));
Widget _buildAccountPageView(BuildContext context) {
final localAccounts = context.watch<LocalAccountsCubit>().state;
@ -221,8 +218,7 @@ class HomeScreenState extends State<HomeScreen> {
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<HomeScreen> {
controller: context
.read<ActiveAccountPageControllerWrapper>()
.pageController,
itemBuilder: (context, index) {
final localAccount = localAccounts[index];
return BlocProvider<AccountInfoCubit>(
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