checkpoint

This commit is contained in:
Christien Rioux 2024-06-18 21:20:06 -04:00
parent 3edf2ebb46
commit c40f835ec5
25 changed files with 378 additions and 312 deletions

View File

@ -8,7 +8,8 @@ import '../repository/account_repository.dart';
class AccountInfoCubit extends Cubit<AccountInfo> { class AccountInfoCubit extends Cubit<AccountInfo> {
AccountInfoCubit( AccountInfoCubit(
AccountRepository accountRepository, TypedKey superIdentityRecordKey) {required AccountRepository accountRepository,
required TypedKey superIdentityRecordKey})
: _accountRepository = accountRepository, : _accountRepository = accountRepository,
super(accountRepository.getAccountInfo(superIdentityRecordKey)!) { super(accountRepository.getAccountInfo(superIdentityRecordKey)!) {
// Subscribe to streams // Subscribe to streams

View File

@ -1,6 +1,7 @@
export 'account_info_cubit.dart'; export 'account_info_cubit.dart';
export 'account_record_cubit.dart'; export 'account_record_cubit.dart';
export 'account_records_bloc_map_cubit.dart';
export 'active_local_account_cubit.dart'; export 'active_local_account_cubit.dart';
export 'local_accounts_cubit.dart'; export 'local_accounts_cubit.dart';
export 'per_account_collection_bloc_map_cubit.dart';
export 'per_account_collection_cubit.dart';
export 'user_logins_cubit.dart'; export 'user_logins_cubit.dart';

View File

@ -8,26 +8,30 @@ import '../../account_manager/account_manager.dart';
typedef AccountRecordsBlocMapState typedef AccountRecordsBlocMapState
= BlocMapState<TypedKey, AsyncValue<AccountRecordState>>; = BlocMapState<TypedKey, AsyncValue<AccountRecordState>>;
/// Map of the logged in user accounts to their AccountRecordCubit /// Map of the logged in user accounts to their PerAccountCollectionCubit
/// 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 PerAccountCollectionBlocMapCubit extends BlocMapCubit<TypedKey,
AsyncValue<AccountRecordState>, AccountRecordCubit> PerAccountCollectionState, PerAccountCollectionCubit>
with StateMapFollower<LocalAccountsState, TypedKey, LocalAccount> { with StateMapFollower<LocalAccountsState, TypedKey, LocalAccount> {
AccountRecordsBlocMapCubit( PerAccountCollectionBlocMapCubit({
AccountRepository accountRepository, Locator locator) required Locator locator,
: _accountRepository = accountRepository { required AccountRepository accountRepository,
}) : _locator = locator,
_accountRepository = accountRepository {
// Follow the local accounts cubit // Follow the local accounts cubit
follow(locator<LocalAccountsCubit>()); follow(locator<LocalAccountsCubit>());
} }
// Add account record cubit // Add account record cubit
Future<void> _addAccountRecordCubit( Future<void> _addPerAccountCollectionCubit(
{required TypedKey superIdentityRecordKey}) async => {required TypedKey superIdentityRecordKey}) async =>
add(() => MapEntry( add(() => MapEntry(
superIdentityRecordKey, superIdentityRecordKey,
AccountRecordCubit( PerAccountCollectionCubit(
accountRepository: _accountRepository, locator: _locator,
superIdentityRecordKey: superIdentityRecordKey))); accountInfoCubit: AccountInfoCubit(
accountRepository: _accountRepository,
superIdentityRecordKey: superIdentityRecordKey))));
/// StateFollower ///////////////////////// /// StateFollower /////////////////////////
@ -36,10 +40,11 @@ class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey,
@override @override
Future<void> updateState(TypedKey key, LocalAccount value) async { Future<void> updateState(TypedKey key, LocalAccount value) async {
await _addAccountRecordCubit( await _addPerAccountCollectionCubit(
superIdentityRecordKey: value.superIdentity.recordKey); superIdentityRecordKey: value.superIdentity.recordKey);
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
final AccountRepository _accountRepository; final AccountRepository _accountRepository;
final Locator _locator;
} }

View File

@ -10,6 +10,7 @@ import '../../chat/chat.dart';
import '../../chat_list/chat_list.dart'; import '../../chat_list/chat_list.dart';
import '../../contact_invitation/contact_invitation.dart'; import '../../contact_invitation/contact_invitation.dart';
import '../../contacts/contacts.dart'; import '../../contacts/contacts.dart';
import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../account_manager.dart'; import '../account_manager.dart';
@ -30,6 +31,14 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
await _accountRecordSubscription?.cancel(); await _accountRecordSubscription?.cancel();
await accountRecordCubit?.close(); await accountRecordCubit?.close();
await activeSingleContactChatBlocMapCubitUpdater.close();
await activeConversationsBlocMapCubitUpdater.close();
await activeChatCubitUpdater.close();
await waitingInvitationsBlocMapCubitUpdater.close();
await chatListCubitUpdater.close();
await contactListCubitUpdater.close();
await contactInvitationListCubitUpdater.close();
await super.close(); await super.close();
} }
@ -95,48 +104,72 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
state.copyWith(avAccountRecordState: accountRecordCubit!.state); state.copyWith(avAccountRecordState: accountRecordCubit!.state);
// Get bloc parameters // Get bloc parameters
final accountRecordKey = nextState.accountInfo.accountRecordKey; final accountInfo = nextState.accountInfo;
// ContactInvitationListCubit // ContactInvitationListCubit
final contactInvitationListRecordPointer = nextState final contactInvitationListRecordPointer = nextState
.avAccountRecordState.asData?.value.contactInvitationRecords .avAccountRecordState.asData?.value.contactInvitationRecords
.toVeilid(); .toVeilid();
contactInvitationListCubitUpdater.update( final contactInvitationListCubit = contactInvitationListCubitUpdater.update(
contactInvitationListRecordPointer == null contactInvitationListRecordPointer == null
? null ? null
: ( : (accountInfo, contactInvitationListRecordPointer));
collectionLocator,
accountRecordKey,
contactInvitationListRecordPointer
));
// ContactListCubit // ContactListCubit
final contactListRecordPointer = final contactListRecordPointer =
nextState.avAccountRecordState.asData?.value.contactList.toVeilid(); nextState.avAccountRecordState.asData?.value.contactList.toVeilid();
contactListCubitUpdater.update(contactListRecordPointer == null final contactListCubit = contactListCubitUpdater.update(
? null contactListRecordPointer == null
: (collectionLocator, accountRecordKey, contactListRecordPointer)); ? null
: (accountInfo, contactListRecordPointer));
// WaitingInvitationsBlocMapCubit // WaitingInvitationsBlocMapCubit
waitingInvitationsBlocMapCubitUpdater.update( waitingInvitationsBlocMapCubitUpdater.update(
nextState.avAccountRecordState.isData ? collectionLocator : null); contactInvitationListCubit == null
? null
: (accountInfo, accountRecordCubit!, contactInvitationListCubit));
// ActiveChatCubit // ActiveChatCubit
activeChatCubitUpdater final activeChatCubit = activeChatCubitUpdater
.update(nextState.avAccountRecordState.isData ? true : null); .update(nextState.avAccountRecordState.isData ? true : null);
// ChatListCubit // ChatListCubit
final chatListRecordPointer = final chatListRecordPointer =
nextState.avAccountRecordState.asData?.value.chatList.toVeilid(); nextState.avAccountRecordState.asData?.value.chatList.toVeilid();
chatListCubitUpdater.update(chatListRecordPointer == null final chatListCubit = chatListCubitUpdater.update(
? null chatListRecordPointer == null || activeChatCubit == null
: (collectionLocator, accountRecordKey, chatListRecordPointer)); ? null
: (accountInfo, chatListRecordPointer, activeChatCubit));
// ActiveConversationsBlocMapCubit // ActiveConversationsBlocMapCubit
// xxx final activeConversationsBlocMapCubit =
activeConversationsBlocMapCubitUpdater.update(
accountRecordCubit == null ||
chatListCubit == null ||
contactListCubit == null
? null
: (
accountInfo,
accountRecordCubit!,
chatListCubit,
contactListCubit
));
// ActiveSingleContactChatBlocMapCubit
activeSingleContactChatBlocMapCubitUpdater.update(
activeConversationsBlocMapCubit == null ||
chatListCubit == null ||
contactListCubit == null
? null
: (
accountInfo,
activeConversationsBlocMapCubit,
chatListCubit,
contactListCubit
));
return nextState; return nextState;
} }
@ -163,6 +196,12 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
if (T is ChatListCubit) { if (T is ChatListCubit) {
return chatListCubitUpdater.bloc! as T; return chatListCubitUpdater.bloc! as T;
} }
if (T is ActiveConversationsBlocMapCubit) {
return activeConversationsBlocMapCubitUpdater.bloc! as T;
}
if (T is ActiveSingleContactChatBlocMapCubit) {
return activeSingleContactChatBlocMapCubitUpdater.bloc! as T;
}
return _locator<T>(); return _locator<T>();
} }
@ -178,32 +217,52 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
StreamSubscription<AsyncValue<AccountRecordState>>? StreamSubscription<AsyncValue<AccountRecordState>>?
_accountRecordSubscription; _accountRecordSubscription;
final contactInvitationListCubitUpdater = BlocUpdater< final contactInvitationListCubitUpdater = BlocUpdater<
ContactInvitationListCubit, ContactInvitationListCubit, (AccountInfo, OwnedDHTRecordPointer)>(
(Locator, TypedKey, OwnedDHTRecordPointer)>(
create: (params) => ContactInvitationListCubit( create: (params) => ContactInvitationListCubit(
locator: params.$1, accountInfo: params.$1,
accountRecordKey: params.$2, contactInvitationListRecordPointer: params.$2,
contactInvitationListRecordPointer: params.$3,
)); ));
final contactListCubitUpdater = final contactListCubitUpdater =
BlocUpdater<ContactListCubit, (Locator, TypedKey, OwnedDHTRecordPointer)>( BlocUpdater<ContactListCubit, (AccountInfo, OwnedDHTRecordPointer)>(
create: (params) => ContactListCubit( create: (params) => ContactListCubit(
locator: params.$1, accountInfo: params.$1,
accountRecordKey: params.$2, contactListRecordPointer: params.$2,
contactListRecordPointer: params.$3,
));
final waitingInvitationsBlocMapCubitUpdater =
BlocUpdater<WaitingInvitationsBlocMapCubit, Locator>(
create: (params) => WaitingInvitationsBlocMapCubit(
locator: params,
)); ));
final waitingInvitationsBlocMapCubitUpdater = BlocUpdater<
WaitingInvitationsBlocMapCubit,
(AccountInfo, AccountRecordCubit, ContactInvitationListCubit)>(
create: (params) => WaitingInvitationsBlocMapCubit(
accountInfo: params.$1,
accountRecordCubit: params.$2,
contactInvitationListCubit: params.$3));
final activeChatCubitUpdater = final activeChatCubitUpdater =
BlocUpdater<ActiveChatCubit, bool>(create: (_) => ActiveChatCubit(null)); BlocUpdater<ActiveChatCubit, bool>(create: (_) => ActiveChatCubit(null));
final chatListCubitUpdater = final chatListCubitUpdater = BlocUpdater<ChatListCubit,
BlocUpdater<ChatListCubit, (Locator, TypedKey, OwnedDHTRecordPointer)>( (AccountInfo, OwnedDHTRecordPointer, ActiveChatCubit)>(
create: (params) => ChatListCubit( create: (params) => ChatListCubit(
locator: params.$1, accountInfo: params.$1,
accountRecordKey: params.$2, chatListRecordPointer: params.$2,
chatListRecordPointer: params.$3, activeChatCubit: params.$3));
)); final activeConversationsBlocMapCubitUpdater = BlocUpdater<
ActiveConversationsBlocMapCubit,
(AccountInfo, AccountRecordCubit, ChatListCubit, ContactListCubit)>(
create: (params) => ActiveConversationsBlocMapCubit(
accountInfo: params.$1,
accountRecordCubit: params.$2,
chatListCubit: params.$3,
contactListCubit: params.$4));
final activeSingleContactChatBlocMapCubitUpdater = BlocUpdater<
ActiveSingleContactChatBlocMapCubit,
(
AccountInfo,
ActiveConversationsBlocMapCubit,
ChatListCubit,
ContactListCubit
)>(
create: (params) => ActiveSingleContactChatBlocMapCubit(
accountInfo: params.$1,
activeConversationsBlocMapCubit: params.$2,
chatListCubit: params.$3,
contactListCubit: params.$4,
));
} }

View File

@ -16,20 +16,17 @@ enum AccountInfoStatus {
class AccountInfo extends Equatable { class AccountInfo extends Equatable {
const AccountInfo({ const AccountInfo({
required this.status, required this.status,
required this.active,
required this.localAccount, required this.localAccount,
required this.userLogin, required this.userLogin,
}); });
final AccountInfoStatus status; final AccountInfoStatus status;
final bool active;
final LocalAccount localAccount; final LocalAccount localAccount;
final UserLogin? userLogin; final UserLogin? userLogin;
@override @override
List<Object?> get props => [ List<Object?> get props => [
status, status,
active,
localAccount, localAccount,
userLogin, userLogin,
]; ];

View File

@ -93,10 +93,6 @@ class AccountRepository {
} }
AccountInfo? getAccountInfo(TypedKey superIdentityRecordKey) { AccountInfo? getAccountInfo(TypedKey superIdentityRecordKey) {
// Get active account if we have one
final activeLocalAccount = getActiveLocalAccount();
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) {
@ -109,7 +105,6 @@ class AccountRepository {
// Account was locked // Account was locked
return AccountInfo( return AccountInfo(
status: AccountInfoStatus.accountLocked, status: AccountInfoStatus.accountLocked,
active: active,
localAccount: localAccount, localAccount: localAccount,
userLogin: null, userLogin: null,
); );
@ -118,7 +113,6 @@ class AccountRepository {
// Got account, decrypted and decoded // Got account, decrypted and decoded
return AccountInfo( return AccountInfo(
status: AccountInfoStatus.accountUnlocked, status: AccountInfoStatus.accountUnlocked,
active: active,
localAccount: localAccount, localAccount: localAccount,
userLogin: userLogin, userLogin: userLogin,
); );

View File

@ -116,13 +116,13 @@ class _EditAccountPageState extends State<EditAccountPage> {
}); });
try { try {
// Look up account cubit for this specific account // Look up account cubit for this specific account
final accountRecordsCubit = final perAccountCollectionCubit =
context.read<AccountRecordsBlocMapCubit>(); context.read<PerAccountCollectionBlocMapCubit>();
await accountRecordsCubit.operateAsync( await perAccountCollectionCubit.operateAsync(
widget.superIdentityRecordKey, closure: (c) async { widget.superIdentityRecordKey, closure: (c) async {
// Update account profile DHT record // Update account profile DHT record
// This triggers ConversationCubits to update // This triggers ConversationCubits to update
await c.updateProfile(newProfile); await c.accountRecordCubit!.updateProfile(newProfile);
}); });
// Update local account profile // Update local account profile

View File

@ -131,9 +131,10 @@ class VeilidChatApp extends StatelessWidget {
create: (context) => create: (context) =>
PreferencesCubit(PreferencesRepository.instance), PreferencesCubit(PreferencesRepository.instance),
), ),
BlocProvider<AccountRecordsBlocMapCubit>( BlocProvider<PerAccountCollectionBlocMapCubit>(
create: (context) => AccountRecordsBlocMapCubit( create: (context) => PerAccountCollectionBlocMapCubit(
AccountRepository.instance, context.read)), accountRepository: AccountRepository.instance,
locator: context.read)),
], ],
child: BackgroundTicker( child: BackgroundTicker(
child: _buildShortcuts( child: _buildShortcuts(

View File

@ -8,7 +8,6 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' as types; import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:provider/provider.dart';
import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
@ -27,10 +26,12 @@ const metadataKeyAttachments = 'attachments';
class ChatComponentCubit extends Cubit<ChatComponentState> { class ChatComponentCubit extends Cubit<ChatComponentState> {
ChatComponentCubit._({ ChatComponentCubit._({
required Locator locator, required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required List<ActiveConversationCubit> conversationCubits, required List<ActiveConversationCubit> conversationCubits,
required SingleContactMessagesCubit messagesCubit, required SingleContactMessagesCubit messagesCubit,
}) : _locator = locator, }) : _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_conversationCubits = conversationCubits, _conversationCubits = conversationCubits,
_messagesCubit = messagesCubit, _messagesCubit = messagesCubit,
super(ChatComponentState( super(ChatComponentState(
@ -48,26 +49,25 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
} }
factory ChatComponentCubit.singleContact( factory ChatComponentCubit.singleContact(
{required Locator locator, {required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ActiveConversationCubit activeConversationCubit, required ActiveConversationCubit activeConversationCubit,
required SingleContactMessagesCubit messagesCubit}) => required SingleContactMessagesCubit messagesCubit}) =>
ChatComponentCubit._( ChatComponentCubit._(
locator: locator, accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
conversationCubits: [activeConversationCubit], conversationCubits: [activeConversationCubit],
messagesCubit: messagesCubit, messagesCubit: messagesCubit,
); );
Future<void> _init() async { Future<void> _init() async {
// Get local user info and account record cubit // Get local user info and account record cubit
final unlockedAccountInfo = _localUserIdentityKey = _accountInfo.identityTypedPublicKey;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
_localUserIdentityKey = unlockedAccountInfo.identityTypedPublicKey;
_localUserAccountRecordCubit = _locator<AccountRecordCubit>();
// Subscribe to local user info // Subscribe to local user info
_localUserAccountRecordSubscription = _localUserAccountRecordCubit.stream _accountRecordSubscription =
.listen(_onChangedLocalUserAccountRecord); _accountRecordCubit.stream.listen(_onChangedAccountRecord);
_onChangedLocalUserAccountRecord(_localUserAccountRecordCubit.state); _onChangedAccountRecord(_accountRecordCubit.state);
// Subscribe to remote user info // Subscribe to remote user info
await _updateConversationSubscriptions(); await _updateConversationSubscriptions();
@ -80,7 +80,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
@override @override
Future<void> close() async { Future<void> close() async {
await _initWait(); await _initWait();
await _localUserAccountRecordSubscription.cancel(); await _accountRecordSubscription.cancel();
await _messagesSubscription.cancel(); await _messagesSubscription.cancel();
await super.close(); await super.close();
} }
@ -145,7 +145,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Private Implementation // Private Implementation
void _onChangedLocalUserAccountRecord(AsyncValue<proto.Account> avAccount) { void _onChangedAccountRecord(AsyncValue<proto.Account> avAccount) {
final account = avAccount.asData?.value; final account = avAccount.asData?.value;
if (account == null) { if (account == null) {
emit(state.copyWith(localUser: null)); emit(state.copyWith(localUser: null));
@ -374,15 +374,14 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
final _initWait = WaitSet<void>(); final _initWait = WaitSet<void>();
final AccountInfo _accountInfo;
final Locator _locator; final AccountRecordCubit _accountRecordCubit;
final List<ActiveConversationCubit> _conversationCubits; final List<ActiveConversationCubit> _conversationCubits;
final SingleContactMessagesCubit _messagesCubit; final SingleContactMessagesCubit _messagesCubit;
late final TypedKey _localUserIdentityKey; late final TypedKey _localUserIdentityKey;
late final AccountRecordCubit _localUserAccountRecordCubit;
late final StreamSubscription<AsyncValue<proto.Account>> late final StreamSubscription<AsyncValue<proto.Account>>
_localUserAccountRecordSubscription; _accountRecordSubscription;
final Map<TypedKey, StreamSubscription<AsyncValue<ActiveConversationState>>> final Map<TypedKey, StreamSubscription<AsyncValue<ActiveConversationState>>>
_conversationSubscriptions = {}; _conversationSubscriptions = {};
late StreamSubscription<SingleContactMessagesState> _messagesSubscription; late StreamSubscription<SingleContactMessagesState> _messagesSubscription;

View File

@ -4,7 +4,6 @@ import 'package:async_tools/async_tools.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
@ -51,13 +50,13 @@ typedef SingleContactMessagesState = AsyncValue<WindowState<MessageState>>;
// Builds the reconciled chat record from the local and remote conversation keys // Builds the reconciled chat record from the local and remote conversation keys
class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> { class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
SingleContactMessagesCubit({ SingleContactMessagesCubit({
required Locator locator, required AccountInfo accountInfo,
required TypedKey remoteIdentityPublicKey, required TypedKey remoteIdentityPublicKey,
required TypedKey localConversationRecordKey, required TypedKey localConversationRecordKey,
required TypedKey localMessagesRecordKey, required TypedKey localMessagesRecordKey,
required TypedKey remoteConversationRecordKey, required TypedKey remoteConversationRecordKey,
required TypedKey remoteMessagesRecordKey, required TypedKey remoteMessagesRecordKey,
}) : _locator = locator, }) : _accountInfo = accountInfo,
_remoteIdentityPublicKey = remoteIdentityPublicKey, _remoteIdentityPublicKey = remoteIdentityPublicKey,
_localConversationRecordKey = localConversationRecordKey, _localConversationRecordKey = localConversationRecordKey,
_localMessagesRecordKey = localMessagesRecordKey, _localMessagesRecordKey = localMessagesRecordKey,
@ -87,9 +86,6 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Initialize everything // Initialize everything
Future<void> _init() async { Future<void> _init() async {
_unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
_unsentMessagesQueue = PersistentQueue<proto.Message>( _unsentMessagesQueue = PersistentQueue<proto.Message>(
table: 'SingleContactUnsentMessages', table: 'SingleContactUnsentMessages',
key: _remoteConversationRecordKey.toString(), key: _remoteConversationRecordKey.toString(),
@ -115,15 +111,15 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Make crypto // Make crypto
Future<void> _initCrypto() async { Future<void> _initCrypto() async {
_conversationCrypto = await _unlockedAccountInfo _conversationCrypto =
.makeConversationCrypto(_remoteIdentityPublicKey); await _accountInfo.makeConversationCrypto(_remoteIdentityPublicKey);
_senderMessageIntegrity = await MessageIntegrity.create( _senderMessageIntegrity = await MessageIntegrity.create(
author: _unlockedAccountInfo.identityTypedPublicKey); author: _accountInfo.identityTypedPublicKey);
} }
// Open local messages key // Open local messages key
Future<void> _initSentMessagesCubit() async { Future<void> _initSentMessagesCubit() async {
final writer = _unlockedAccountInfo.identityWriter; final writer = _accountInfo.identityWriter;
_sentMessagesCubit = DHTLogCubit( _sentMessagesCubit = DHTLogCubit(
open: () async => DHTLog.openWrite(_localMessagesRecordKey, writer, open: () async => DHTLog.openWrite(_localMessagesRecordKey, writer,
@ -153,7 +149,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
Future<VeilidCrypto> _makeLocalMessagesCrypto() async => Future<VeilidCrypto> _makeLocalMessagesCrypto() async =>
VeilidCryptoPrivate.fromTypedKey( VeilidCryptoPrivate.fromTypedKey(
_unlockedAccountInfo.userLogin.identitySecret, 'tabledb'); _accountInfo.userLogin!.identitySecret, 'tabledb');
// Open reconciled chat record key // Open reconciled chat record key
Future<void> _initReconciledMessagesCubit() async { Future<void> _initReconciledMessagesCubit() async {
@ -245,9 +241,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
} }
_reconciliation.reconcileMessages( _reconciliation.reconcileMessages(
_unlockedAccountInfo.identityTypedPublicKey, _accountInfo.identityTypedPublicKey, sentMessages, _sentMessagesCubit!);
sentMessages,
_sentMessagesCubit!);
// Update the view // Update the view
_renderState(); _renderState();
@ -284,7 +278,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Now sign it // Now sign it
await _senderMessageIntegrity.signMessage( await _senderMessageIntegrity.signMessage(
message, _unlockedAccountInfo.identitySecretKey); message, _accountInfo.identitySecretKey);
} }
// Async process to send messages in the background // Async process to send messages in the background
@ -336,8 +330,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
final renderedElements = <RenderStateElement>[]; final renderedElements = <RenderStateElement>[];
for (final m in reconciledMessages.windowElements) { for (final m in reconciledMessages.windowElements) {
final isLocal = m.content.author.toVeilid() == final isLocal =
_unlockedAccountInfo.identityTypedPublicKey; m.content.author.toVeilid() == _accountInfo.identityTypedPublicKey;
final reconciledTimestamp = Timestamp.fromInt64(m.reconciledTime); final reconciledTimestamp = Timestamp.fromInt64(m.reconciledTime);
final sm = final sm =
isLocal ? sentMessagesMap[m.content.authorUniqueIdString] : null; isLocal ? sentMessagesMap[m.content.authorUniqueIdString] : null;
@ -375,7 +369,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Add common fields // Add common fields
// id and signature will get set by _processMessageToSend // id and signature will get set by _processMessageToSend
message message
..author = _unlockedAccountInfo.identityTypedPublicKey.toProto() ..author = _accountInfo.identityTypedPublicKey.toProto()
..timestamp = Veilid.instance.now().toInt64(); ..timestamp = Veilid.instance.now().toInt64();
// Put in the queue // Put in the queue
@ -408,8 +402,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
final WaitSet<void> _initWait = WaitSet(); final WaitSet<void> _initWait = WaitSet();
final Locator _locator; late final AccountInfo _accountInfo;
late final UnlockedAccountInfo _unlockedAccountInfo;
final TypedKey _remoteIdentityPublicKey; final TypedKey _remoteIdentityPublicKey;
final TypedKey _localConversationRecordKey; final TypedKey _localConversationRecordKey;
final TypedKey _localMessagesRecordKey; final TypedKey _localMessagesRecordKey;

View File

@ -8,6 +8,7 @@ import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../conversation/conversation.dart'; import '../../conversation/conversation.dart';
import '../../theme/theme.dart'; import '../../theme/theme.dart';
import '../chat.dart'; import '../chat.dart';
@ -21,6 +22,12 @@ class ChatComponentWidget extends StatelessWidget {
static Widget builder( static Widget builder(
{required TypedKey localConversationRecordKey, Key? key}) => {required TypedKey localConversationRecordKey, Key? key}) =>
Builder(builder: (context) { Builder(builder: (context) {
// Get the account info
final accountInfo = context.watch<AccountInfoCubit>().state;
// Get the account record cubit
final accountRecordCubit = context.read<AccountRecordCubit>();
// Get the active conversation cubit // Get the active conversation cubit
final activeConversationCubit = context final activeConversationCubit = context
.select<ActiveConversationsBlocMapCubit, ActiveConversationCubit?>( .select<ActiveConversationsBlocMapCubit, ActiveConversationCubit?>(
@ -43,7 +50,8 @@ class ChatComponentWidget extends StatelessWidget {
// Make chat component state // Make chat component state
return BlocProvider( return BlocProvider(
create: (context) => ChatComponentCubit.singleContact( create: (context) => ChatComponentCubit.singleContact(
locator: context.read, accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
activeConversationCubit: activeConversationCubit, activeConversationCubit: activeConversationCubit,
messagesCubit: messagesCubit, messagesCubit: messagesCubit,
), ),

View File

@ -3,9 +3,9 @@ import 'dart:async';
import 'package:bloc_advanced_tools/bloc_advanced_tools.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:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../chat/chat.dart'; import '../../chat/chat.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart'; import '../../tools/tools.dart';
@ -19,18 +19,19 @@ typedef ChatListCubitState = DHTShortArrayBusyState<proto.Chat>;
class ChatListCubit extends DHTShortArrayCubit<proto.Chat> class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
with StateMapFollowable<ChatListCubitState, TypedKey, proto.Chat> { with StateMapFollowable<ChatListCubitState, TypedKey, proto.Chat> {
ChatListCubit({ ChatListCubit({
required Locator locator, required AccountInfo accountInfo,
required TypedKey accountRecordKey,
required OwnedDHTRecordPointer chatListRecordPointer, required OwnedDHTRecordPointer chatListRecordPointer,
}) : _locator = locator, required ActiveChatCubit activeChatCubit,
}) : _activeChatCubit = activeChatCubit,
super( super(
open: () => _open(locator, accountRecordKey, chatListRecordPointer), open: () => _open(accountInfo, chatListRecordPointer),
decodeElement: proto.Chat.fromBuffer); decodeElement: proto.Chat.fromBuffer);
static Future<DHTShortArray> _open(Locator locator, TypedKey accountRecordKey, static Future<DHTShortArray> _open(AccountInfo accountInfo,
OwnedDHTRecordPointer chatListRecordPointer) async { OwnedDHTRecordPointer chatListRecordPointer) async {
final dhtRecord = await DHTShortArray.openOwned(chatListRecordPointer, final dhtRecord = await DHTShortArray.openOwned(chatListRecordPointer,
debugName: 'ChatListCubit::_open::ChatList', parent: accountRecordKey); debugName: 'ChatListCubit::_open::ChatList',
parent: accountInfo.accountRecordKey);
return dhtRecord; return dhtRecord;
} }
@ -95,9 +96,8 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
final deletedItem = final deletedItem =
// Ensure followers get their changes before we return // Ensure followers get their changes before we return
await syncFollowers(() => operateWrite((writer) async { await syncFollowers(() => operateWrite((writer) async {
final activeChatCubit = _locator<ActiveChatCubit>(); if (_activeChatCubit.state == localConversationRecordKey) {
if (activeChatCubit.state == localConversationRecordKey) { _activeChatCubit.setActiveChat(null);
activeChatCubit.setActiveChat(null);
} }
for (var i = 0; i < writer.length; i++) { for (var i = 0; i < writer.length; i++) {
final c = await writer.getProtobuf(proto.Chat.fromBuffer, i); final c = await writer.getProtobuf(proto.Chat.fromBuffer, i);
@ -139,5 +139,5 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
final Locator _locator; final ActiveChatCubit _activeChatCubit;
} }

View File

@ -4,7 +4,6 @@ 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:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
@ -37,14 +36,12 @@ class ContactInvitationListCubit
StateMapFollowable<ContactInvitiationListState, TypedKey, StateMapFollowable<ContactInvitiationListState, TypedKey,
proto.ContactInvitationRecord> { proto.ContactInvitationRecord> {
ContactInvitationListCubit({ ContactInvitationListCubit({
required Locator locator, required AccountInfo accountInfo,
required TypedKey accountRecordKey,
required OwnedDHTRecordPointer contactInvitationListRecordPointer, required OwnedDHTRecordPointer contactInvitationListRecordPointer,
}) : _locator = locator, }) : _accountInfo = accountInfo,
_accountRecordKey = accountRecordKey,
super( super(
open: () => open: () => _open(accountInfo.accountRecordKey,
_open(accountRecordKey, contactInvitationListRecordPointer), contactInvitationListRecordPointer),
decodeElement: proto.ContactInvitationRecord.fromBuffer); decodeElement: proto.ContactInvitationRecord.fromBuffer);
static Future<DHTShortArray> _open(TypedKey accountRecordKey, static Future<DHTShortArray> _open(TypedKey accountRecordKey,
@ -58,7 +55,8 @@ class ContactInvitationListCubit
} }
Future<Uint8List> createInvitation( Future<Uint8List> createInvitation(
{required EncryptionKeyType encryptionKeyType, {required proto.Profile profile,
required EncryptionKeyType encryptionKeyType,
required String encryptionKey, required String encryptionKey,
required String message, required String message,
required Timestamp? expiration}) async { required Timestamp? expiration}) async {
@ -68,12 +66,8 @@ class ContactInvitationListCubit
final crcs = await pool.veilid.bestCryptoSystem(); final crcs = await pool.veilid.bestCryptoSystem();
final contactRequestWriter = await crcs.generateKeyPair(); final contactRequestWriter = await crcs.generateKeyPair();
final activeAccountInfo = final idcs = await _accountInfo.identityCryptoSystem;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!; final identityWriter = _accountInfo.identityWriter;
final profile = _locator<AccountRecordCubit>().state.asData!.value.profile;
final idcs = await activeAccountInfo.identityCryptoSystem;
final identityWriter = activeAccountInfo.identityWriter;
// Encrypt the writer secret with the encryption key // Encrypt the writer secret with the encryption key
final encryptedSecret = await encryptionKeyType.encryptSecretToBytes( final encryptedSecret = await encryptionKeyType.encryptSecretToBytes(
@ -91,7 +85,7 @@ class ContactInvitationListCubit
await (await pool.createRecord( await (await pool.createRecord(
debugName: 'ContactInvitationListCubit::createInvitation::' debugName: 'ContactInvitationListCubit::createInvitation::'
'LocalConversation', 'LocalConversation',
parent: _accountRecordKey, parent: _accountInfo.accountRecordKey,
schema: DHTSchema.smpl( schema: DHTSchema.smpl(
oCnt: 0, oCnt: 0,
members: [DHTSchemaMember(mKey: identityWriter.key, mCnt: 1)]))) members: [DHTSchemaMember(mKey: identityWriter.key, mCnt: 1)])))
@ -101,8 +95,7 @@ class ContactInvitationListCubit
final crpriv = proto.ContactRequestPrivate() final crpriv = proto.ContactRequestPrivate()
..writerKey = contactRequestWriter.key.toProto() ..writerKey = contactRequestWriter.key.toProto()
..profile = profile ..profile = profile
..superIdentityRecordKey = ..superIdentityRecordKey = _accountInfo.superIdentityRecordKey.toProto()
activeAccountInfo.userLogin.superIdentityRecordKey.toProto()
..chatRecordKey = localConversation.key.toProto() ..chatRecordKey = localConversation.key.toProto()
..expiration = expiration?.toInt64() ?? Int64.ZERO; ..expiration = expiration?.toInt64() ?? Int64.ZERO;
final crprivbytes = crpriv.writeToBuffer(); final crprivbytes = crpriv.writeToBuffer();
@ -120,7 +113,7 @@ class ContactInvitationListCubit
await (await pool.createRecord( await (await pool.createRecord(
debugName: 'ContactInvitationListCubit::createInvitation::' debugName: 'ContactInvitationListCubit::createInvitation::'
'ContactRequestInbox', 'ContactRequestInbox',
parent: _accountRecordKey, parent: _accountInfo.accountRecordKey,
schema: DHTSchema.smpl(oCnt: 1, members: [ schema: DHTSchema.smpl(oCnt: 1, members: [
DHTSchemaMember(mCnt: 1, mKey: contactRequestWriter.key) DHTSchemaMember(mCnt: 1, mKey: contactRequestWriter.key)
]), ]),
@ -197,7 +190,7 @@ class ContactInvitationListCubit
await (await pool.openRecordOwned(contactRequestInbox, await (await pool.openRecordOwned(contactRequestInbox,
debugName: 'ContactInvitationListCubit::deleteInvitation::' debugName: 'ContactInvitationListCubit::deleteInvitation::'
'ContactRequestInbox', 'ContactRequestInbox',
parent: _accountRecordKey)) parent: _accountInfo.accountRecordKey))
.scope((contactRequestInbox) async { .scope((contactRequestInbox) async {
// Wipe out old invitation so it shows up as invalid // Wipe out old invitation so it shows up as invalid
await contactRequestInbox.tryWriteBytes(Uint8List(0)); await contactRequestInbox.tryWriteBytes(Uint8List(0));
@ -248,7 +241,7 @@ class ContactInvitationListCubit
debugName: 'ContactInvitationListCubit::validateInvitation::' debugName: 'ContactInvitationListCubit::validateInvitation::'
'ContactRequestInbox', 'ContactRequestInbox',
parent: pool.getParentRecordKey(contactRequestInboxKey) ?? parent: pool.getParentRecordKey(contactRequestInboxKey) ??
_accountRecordKey)) _accountInfo.accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async { .maybeDeleteScope(!isSelf, (contactRequestInbox) async {
// //
final contactRequest = await contactRequestInbox final contactRequest = await contactRequestInbox
@ -293,7 +286,7 @@ class ContactInvitationListCubit
secret: writerSecret); secret: writerSecret);
out = ValidContactInvitation( out = ValidContactInvitation(
locator: _locator, accountInfo: _accountInfo,
contactRequestInboxKey: contactRequestInboxKey, contactRequestInboxKey: contactRequestInboxKey,
contactRequestPrivate: contactRequestPrivate, contactRequestPrivate: contactRequestPrivate,
contactSuperIdentity: contactSuperIdentity, contactSuperIdentity: contactSuperIdentity,
@ -317,6 +310,5 @@ class ContactInvitationListCubit
} }
// //
final Locator _locator; final AccountInfo _accountInfo;
final TypedKey _accountRecordKey;
} }

View File

@ -1,4 +1,3 @@
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
@ -8,23 +7,21 @@ import '../../proto/proto.dart' as proto;
class ContactRequestInboxCubit class ContactRequestInboxCubit
extends DefaultDHTRecordCubit<proto.SignedContactResponse?> { extends DefaultDHTRecordCubit<proto.SignedContactResponse?> {
ContactRequestInboxCubit( ContactRequestInboxCubit(
{required Locator locator, required this.contactInvitationRecord}) {required AccountInfo accountInfo, required this.contactInvitationRecord})
: super( : super(
open: () => _open( open: () => _open(
locator: locator, accountInfo: accountInfo,
contactInvitationRecord: contactInvitationRecord), contactInvitationRecord: contactInvitationRecord),
decodeState: (buf) => buf.isEmpty decodeState: (buf) => buf.isEmpty
? null ? null
: proto.SignedContactResponse.fromBuffer(buf)); : proto.SignedContactResponse.fromBuffer(buf));
static Future<DHTRecord> _open( static Future<DHTRecord> _open(
{required Locator locator, {required AccountInfo accountInfo,
required proto.ContactInvitationRecord contactInvitationRecord}) async { required proto.ContactInvitationRecord contactInvitationRecord}) async {
final pool = DHTRecordPool.instance; final pool = DHTRecordPool.instance;
final unlockedAccountInfo = final accountRecordKey = accountInfo.accountRecordKey;
locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final writerSecret = contactInvitationRecord.writerSecret.toVeilid(); final writerSecret = contactInvitationRecord.writerSecret.toVeilid();
final recordKey = final recordKey =

View File

@ -4,9 +4,9 @@ import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../conversation/conversation.dart'; import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart'; import '../../tools/tools.dart';
@ -24,18 +24,22 @@ class InvitationStatus extends Equatable {
class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus, class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
proto.SignedContactResponse?> { proto.SignedContactResponse?> {
WaitingInvitationCubit(ContactRequestInboxCubit super.input, WaitingInvitationCubit(
{required Locator locator, ContactRequestInboxCubit super.input, {
required proto.ContactInvitationRecord contactInvitationRecord}) required AccountInfo accountInfo,
: super( required AccountRecordCubit accountRecordCubit,
required proto.ContactInvitationRecord contactInvitationRecord,
}) : super(
transform: (signedContactResponse) => _transform( transform: (signedContactResponse) => _transform(
signedContactResponse, signedContactResponse,
locator: locator, accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
contactInvitationRecord: contactInvitationRecord)); contactInvitationRecord: contactInvitationRecord));
static Future<AsyncValue<InvitationStatus>> _transform( static Future<AsyncValue<InvitationStatus>> _transform(
proto.SignedContactResponse? signedContactResponse, proto.SignedContactResponse? signedContactResponse,
{required Locator locator, {required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required proto.ContactInvitationRecord contactInvitationRecord}) async { required proto.ContactInvitationRecord contactInvitationRecord}) async {
if (signedContactResponse == null) { if (signedContactResponse == null) {
return const AsyncValue.loading(); return const AsyncValue.loading();
@ -69,7 +73,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
contactResponse.remoteConversationRecordKey.toVeilid(); contactResponse.remoteConversationRecordKey.toVeilid();
final conversation = ConversationCubit( final conversation = ConversationCubit(
locator: locator, accountInfo: accountInfo,
remoteIdentityPublicKey: remoteIdentityPublicKey:
contactSuperIdentity.currentInstance.typedPublicKey, contactSuperIdentity.currentInstance.typedPublicKey,
remoteConversationRecordKey: remoteConversationRecordKey); remoteConversationRecordKey: remoteConversationRecordKey);
@ -96,6 +100,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
final localConversationRecordKey = final localConversationRecordKey =
contactInvitationRecord.localConversationRecordKey.toVeilid(); contactInvitationRecord.localConversationRecordKey.toVeilid();
return conversation.initLocalConversation( return conversation.initLocalConversation(
profile: accountRecordCubit.state.asData!.value.profile,
existingConversationRecordKey: localConversationRecordKey, existingConversationRecordKey: localConversationRecordKey,
callback: (localConversation) async => AsyncValue.data(InvitationStatus( callback: (localConversation) async => AsyncValue.data(InvitationStatus(
acceptedContact: AcceptedContact( acceptedContact: AcceptedContact(

View File

@ -1,8 +1,8 @@
import 'package:async_tools/async_tools.dart'; import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import 'cubits.dart'; import 'cubits.dart';
@ -17,11 +17,14 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
with with
StateMapFollower<DHTShortArrayBusyState<proto.ContactInvitationRecord>, StateMapFollower<DHTShortArrayBusyState<proto.ContactInvitationRecord>,
TypedKey, proto.ContactInvitationRecord> { TypedKey, proto.ContactInvitationRecord> {
WaitingInvitationsBlocMapCubit({ WaitingInvitationsBlocMapCubit(
required Locator locator, {required AccountInfo accountInfo,
}) : _locator = locator { required AccountRecordCubit accountRecordCubit,
required ContactInvitationListCubit contactInvitationListCubit})
: _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit {
// Follow the contact invitation list cubit // Follow the contact invitation list cubit
follow(locator<ContactInvitationListCubit>()); follow(contactInvitationListCubit);
} }
Future<void> _addWaitingInvitation( Future<void> _addWaitingInvitation(
@ -31,9 +34,10 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(), contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(),
WaitingInvitationCubit( WaitingInvitationCubit(
ContactRequestInboxCubit( ContactRequestInboxCubit(
locator: _locator, accountInfo: _accountInfo,
contactInvitationRecord: contactInvitationRecord), contactInvitationRecord: contactInvitationRecord),
locator: _locator, accountInfo: _accountInfo,
accountRecordCubit: _accountRecordCubit,
contactInvitationRecord: contactInvitationRecord))); contactInvitationRecord: contactInvitationRecord)));
/// StateFollower ///////////////////////// /// StateFollower /////////////////////////
@ -46,5 +50,6 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
_addWaitingInvitation(contactInvitationRecord: value); _addWaitingInvitation(contactInvitationRecord: value);
//// ////
final Locator _locator; final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
} }

View File

@ -1,5 +1,4 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
@ -14,12 +13,12 @@ import 'models.dart';
class ValidContactInvitation { class ValidContactInvitation {
@internal @internal
ValidContactInvitation( ValidContactInvitation(
{required Locator locator, {required AccountInfo accountInfo,
required TypedKey contactRequestInboxKey, required TypedKey contactRequestInboxKey,
required proto.ContactRequestPrivate contactRequestPrivate, required proto.ContactRequestPrivate contactRequestPrivate,
required SuperIdentity contactSuperIdentity, required SuperIdentity contactSuperIdentity,
required KeyPair writer}) required KeyPair writer})
: _locator = locator, : _accountInfo = accountInfo,
_contactRequestInboxKey = contactRequestInboxKey, _contactRequestInboxKey = contactRequestInboxKey,
_contactRequestPrivate = contactRequestPrivate, _contactRequestPrivate = contactRequestPrivate,
_contactSuperIdentity = contactSuperIdentity, _contactSuperIdentity = contactSuperIdentity,
@ -27,65 +26,57 @@ class ValidContactInvitation {
proto.Profile get remoteProfile => _contactRequestPrivate.profile; proto.Profile get remoteProfile => _contactRequestPrivate.profile;
Future<AcceptedContact?> accept() async { Future<AcceptedContact?> accept(proto.Profile profile) async {
final pool = DHTRecordPool.instance; final pool = DHTRecordPool.instance;
try { try {
final unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final identityPublicKey = unlockedAccountInfo.identityPublicKey;
// Ensure we don't delete this if we're trying to chat to self // Ensure we don't delete this if we're trying to chat to self
// The initiating side will delete the records in deleteInvitation() // The initiating side will delete the records in deleteInvitation()
final isSelf = final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
_contactSuperIdentity.currentInstance.publicKey == identityPublicKey; _accountInfo.identityPublicKey;
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer, return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::accept::' debugName: 'ValidContactInvitation::accept::'
'ContactRequestInbox', 'ContactRequestInbox',
parent: pool.getParentRecordKey(_contactRequestInboxKey) ?? parent: pool.getParentRecordKey(_contactRequestInboxKey) ??
accountRecordKey)) _accountInfo.accountRecordKey))
// ignore: prefer_expression_function_bodies // ignore: prefer_expression_function_bodies
.maybeDeleteScope(!isSelf, (contactRequestInbox) async { .maybeDeleteScope(!isSelf, (contactRequestInbox) async {
// Create local conversation key for this // Create local conversation key for this
// contact and send via contact response // contact and send via contact response
final conversation = ConversationCubit( final conversation = ConversationCubit(
locator: _locator, accountInfo: _accountInfo,
remoteIdentityPublicKey: remoteIdentityPublicKey:
_contactSuperIdentity.currentInstance.typedPublicKey); _contactSuperIdentity.currentInstance.typedPublicKey);
return conversation.initLocalConversation( return conversation.initLocalConversation(
profile: profile,
callback: (localConversation) async { callback: (localConversation) async {
final contactResponse = proto.ContactResponse() final contactResponse = proto.ContactResponse()
..accept = true ..accept = true
..remoteConversationRecordKey = localConversation.key.toProto() ..remoteConversationRecordKey = localConversation.key.toProto()
..superIdentityRecordKey = ..superIdentityRecordKey =
unlockedAccountInfo.superIdentityRecordKey.toProto(); _accountInfo.superIdentityRecordKey.toProto();
final contactResponseBytes = contactResponse.writeToBuffer(); final contactResponseBytes = contactResponse.writeToBuffer();
final cs = final cs = await _accountInfo.identityCryptoSystem;
await pool.veilid.getCryptoSystem(_contactRequestInboxKey.kind); final identitySignature = await cs.signWithKeyPair(
_accountInfo.identityWriter, contactResponseBytes);
final identitySignature = await cs.sign( final signedContactResponse = proto.SignedContactResponse()
unlockedAccountInfo.identityWriter.key, ..contactResponse = contactResponseBytes
unlockedAccountInfo.identityWriter.secret, ..identitySignature = identitySignature.toProto();
contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse() // Write the acceptance to the inbox
..contactResponse = contactResponseBytes await contactRequestInbox
..identitySignature = identitySignature.toProto(); .eventualWriteProtobuf(signedContactResponse, subkey: 1);
// Write the acceptance to the inbox return AcceptedContact(
await contactRequestInbox.eventualWriteProtobuf(signedContactResponse, remoteProfile: _contactRequestPrivate.profile,
subkey: 1); remoteIdentity: _contactSuperIdentity,
remoteConversationRecordKey:
return AcceptedContact( _contactRequestPrivate.chatRecordKey.toVeilid(),
remoteProfile: _contactRequestPrivate.profile, localConversationRecordKey: localConversation.key,
remoteIdentity: _contactSuperIdentity, );
remoteConversationRecordKey: });
_contactRequestPrivate.chatRecordKey.toVeilid(),
localConversationRecordKey: localConversation.key,
);
});
}); });
} on Exception catch (e) { } on Exception catch (e) {
log.debug('exception: $e', e); log.debug('exception: $e', e);
@ -96,33 +87,24 @@ class ValidContactInvitation {
Future<bool> reject() async { Future<bool> reject() async {
final pool = DHTRecordPool.instance; final pool = DHTRecordPool.instance;
final unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final identityPublicKey = unlockedAccountInfo.identityPublicKey;
// Ensure we don't delete this if we're trying to chat to self // Ensure we don't delete this if we're trying to chat to self
final isSelf = final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
_contactSuperIdentity.currentInstance.publicKey == identityPublicKey; _accountInfo.identityPublicKey;
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer, return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::reject::' debugName: 'ValidContactInvitation::reject::'
'ContactRequestInbox', 'ContactRequestInbox',
parent: accountRecordKey)) parent: _accountInfo.accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async { .maybeDeleteScope(!isSelf, (contactRequestInbox) async {
final cs =
await pool.veilid.getCryptoSystem(_contactRequestInboxKey.kind);
final contactResponse = proto.ContactResponse() final contactResponse = proto.ContactResponse()
..accept = false ..accept = false
..superIdentityRecordKey = ..superIdentityRecordKey =
unlockedAccountInfo.superIdentityRecordKey.toProto(); _accountInfo.superIdentityRecordKey.toProto();
final contactResponseBytes = contactResponse.writeToBuffer(); final contactResponseBytes = contactResponse.writeToBuffer();
final identitySignature = await cs.sign( final cs = await _accountInfo.identityCryptoSystem;
unlockedAccountInfo.identityWriter.key, final identitySignature = await cs.signWithKeyPair(
unlockedAccountInfo.identityWriter.secret, _accountInfo.identityWriter, contactResponseBytes);
contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse() final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes ..contactResponse = contactResponseBytes
@ -136,7 +118,7 @@ class ValidContactInvitation {
} }
// //
final Locator _locator; final AccountInfo _accountInfo;
final TypedKey _contactRequestInboxKey; final TypedKey _contactRequestInboxKey;
final SuperIdentity _contactSuperIdentity; final SuperIdentity _contactSuperIdentity;
final KeyPair _writer; final KeyPair _writer;

View File

@ -140,8 +140,18 @@ class CreateInvitationDialogState extends State<CreateInvitationDialog> {
// Start generation // Start generation
final contactInvitationListCubit = final contactInvitationListCubit =
widget.modalContext.read<ContactInvitationListCubit>(); widget.modalContext.read<ContactInvitationListCubit>();
final profile = widget.modalContext
.read<AccountRecordCubit>()
.state
.asData
?.value
.profile;
if (profile == null) {
return;
}
final generator = contactInvitationListCubit.createInvitation( final generator = contactInvitationListCubit.createInvitation(
profile: profile,
encryptionKeyType: _encryptionKeyType, encryptionKeyType: _encryptionKeyType,
encryptionKey: _encryptionKey, encryptionKey: _encryptionKey,
message: _messageTextController.text, message: _messageTextController.text,

View File

@ -76,17 +76,19 @@ class InvitationDialogState extends State<InvitationDialog> {
final navigator = Navigator.of(context); final navigator = Navigator.of(context);
final accountInfo = widget._locator<AccountInfoCubit>().state; final accountInfo = widget._locator<AccountInfoCubit>().state;
final contactList = widget._locator<ContactListCubit>(); final contactList = widget._locator<ContactListCubit>();
final profile =
widget._locator<AccountRecordCubit>().state.asData!.value.profile;
setState(() { setState(() {
_isAccepting = true; _isAccepting = true;
}); });
final validInvitation = _validInvitation; final validInvitation = _validInvitation;
if (validInvitation != null) { if (validInvitation != null) {
final acceptedContact = await validInvitation.accept(); final acceptedContact = await validInvitation.accept(profile);
if (acceptedContact != null) { if (acceptedContact != null) {
// initiator when accept is received will create // initiator when accept is received will create
// contact in the case of a 'note to self' // contact in the case of a 'note to self'
final isSelf = accountInfo.unlockedAccountInfo!.identityPublicKey == final isSelf = accountInfo.identityPublicKey ==
acceptedContact.remoteIdentity.currentInstance.publicKey; acceptedContact.remoteIdentity.currentInstance.publicKey;
if (!isSelf) { if (!isSelf) {
await contactList.createContact( await contactList.createContact(

View File

@ -3,9 +3,9 @@ import 'dart:convert';
import 'package:async_tools/async_tools.dart'; import 'package:async_tools/async_tools.dart';
import 'package:protobuf/protobuf.dart'; import 'package:protobuf/protobuf.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../conversation/conversation.dart'; import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart'; import '../../tools/tools.dart';
@ -15,12 +15,12 @@ import '../../tools/tools.dart';
class ContactListCubit extends DHTShortArrayCubit<proto.Contact> { class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
ContactListCubit({ ContactListCubit({
required Locator locator, required AccountInfo accountInfo,
required TypedKey accountRecordKey,
required OwnedDHTRecordPointer contactListRecordPointer, required OwnedDHTRecordPointer contactListRecordPointer,
}) : _locator = locator, }) : _accountInfo = accountInfo,
super( super(
open: () => _open(accountRecordKey, contactListRecordPointer), open: () =>
_open(accountInfo.accountRecordKey, contactListRecordPointer),
decodeElement: proto.Contact.fromBuffer); decodeElement: proto.Contact.fromBuffer);
static Future<DHTShortArray> _open(TypedKey accountRecordKey, static Future<DHTShortArray> _open(TypedKey accountRecordKey,
@ -126,7 +126,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
try { try {
// Make a conversation cubit to manipulate the conversation // Make a conversation cubit to manipulate the conversation
final conversationCubit = ConversationCubit( final conversationCubit = ConversationCubit(
locator: _locator, accountInfo: _accountInfo,
remoteIdentityPublicKey: deletedItem.identityPublicKey.toVeilid(), remoteIdentityPublicKey: deletedItem.identityPublicKey.toVeilid(),
localConversationRecordKey: localConversationRecordKey:
deletedItem.localConversationRecordKey.toVeilid(), deletedItem.localConversationRecordKey.toVeilid(),
@ -144,5 +144,5 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
final _contactProfileUpdateMap = final _contactProfileUpdateMap =
SingleStateProcessorMap<TypedKey, proto.Profile?>(); SingleStateProcessorMap<TypedKey, proto.Profile?>();
final Locator _locator; final AccountInfo _accountInfo;
} }

View File

@ -2,7 +2,6 @@ import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
@ -45,10 +44,15 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
AsyncValue<ActiveConversationState>, ActiveConversationCubit> AsyncValue<ActiveConversationState>, ActiveConversationCubit>
with StateMapFollower<ChatListCubitState, TypedKey, proto.Chat> { with StateMapFollower<ChatListCubitState, TypedKey, proto.Chat> {
ActiveConversationsBlocMapCubit({ ActiveConversationsBlocMapCubit({
required Locator locator, required AccountInfo accountInfo,
}) : _locator = locator { required AccountRecordCubit accountRecordCubit,
required ChatListCubit chatListCubit,
required ContactListCubit contactListCubit,
}) : _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_contactListCubit = contactListCubit {
// Follow the chat list cubit // Follow the chat list cubit
follow(locator<ChatListCubit>()); follow(chatListCubit);
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -69,20 +73,15 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
// Conversation cubit the tracks the state between the local // Conversation cubit the tracks the state between the local
// and remote halves of a contact's relationship with this account // and remote halves of a contact's relationship with this account
final conversationCubit = ConversationCubit( final conversationCubit = ConversationCubit(
locator: _locator, accountInfo: _accountInfo,
remoteIdentityPublicKey: remoteIdentityPublicKey, remoteIdentityPublicKey: remoteIdentityPublicKey,
localConversationRecordKey: localConversationRecordKey, localConversationRecordKey: localConversationRecordKey,
remoteConversationRecordKey: remoteConversationRecordKey, remoteConversationRecordKey: remoteConversationRecordKey,
); );
// When our local account profile changes, send it to the conversation
final accountRecordCubit = _locator<AccountRecordCubit>();
conversationCubit.watchAccountChanges(
accountRecordCubit.stream, accountRecordCubit.state);
// When remote conversation changes its profile, // When remote conversation changes its profile,
// update our local contact // update our local contact
_locator<ContactListCubit>().followContactProfileChanges( _contactListCubit.followContactProfileChanges(
localConversationRecordKey, localConversationRecordKey,
conversationCubit.stream.map((x) => x.map( conversationCubit.stream.map((x) => x.map(
data: (d) => d.value.remoteConversation?.profile, data: (d) => d.value.remoteConversation?.profile,
@ -90,6 +89,10 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
error: (_) => null)), error: (_) => null)),
conversationCubit.state.asData?.value.remoteConversation?.profile); conversationCubit.state.asData?.value.remoteConversation?.profile);
// When our local account profile changes, send it to the conversation
conversationCubit.watchAccountChanges(
_accountRecordCubit.stream, _accountRecordCubit.state);
// Transformer that only passes through completed/active conversations // Transformer that only passes through completed/active conversations
// along with the contact that corresponds to the completed // along with the contact that corresponds to the completed
// conversation // conversation
@ -119,7 +122,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
@override @override
Future<void> updateState(TypedKey key, proto.Chat value) async { Future<void> updateState(TypedKey key, proto.Chat value) async {
final contactList = _locator<ContactListCubit>().state.state.asData?.value; final contactList = _contactListCubit.state.state.asData?.value;
if (contactList == null) { if (contactList == null) {
await addState(key, const AsyncValue.loading()); await addState(key, const AsyncValue.loading());
return; return;
@ -136,5 +139,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
//// ////
final Locator _locator; final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
final ContactListCubit _contactListCubit;
} }

View File

@ -2,13 +2,14 @@ import 'dart:async';
import 'package:async_tools/async_tools.dart'; import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../chat/chat.dart'; import '../../chat/chat.dart';
import '../../chat_list/cubits/chat_list_cubit.dart'; import '../../chat_list/cubits/chat_list_cubit.dart';
import '../../contacts/contacts.dart'; import '../../contacts/contacts.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../conversation.dart';
import 'active_conversations_bloc_map_cubit.dart'; import 'active_conversations_bloc_map_cubit.dart';
// Map of localConversationRecordKey to MessagesCubit // Map of localConversationRecordKey to MessagesCubit
@ -19,10 +20,16 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
with with
StateMapFollower<ActiveConversationsBlocMapState, TypedKey, StateMapFollower<ActiveConversationsBlocMapState, TypedKey,
AsyncValue<ActiveConversationState>> { AsyncValue<ActiveConversationState>> {
ActiveSingleContactChatBlocMapCubit({required Locator locator}) ActiveSingleContactChatBlocMapCubit(
: _locator = locator { {required AccountInfo accountInfo,
required ActiveConversationsBlocMapCubit activeConversationsBlocMapCubit,
required ContactListCubit contactListCubit,
required ChatListCubit chatListCubit})
: _accountInfo = accountInfo,
_contactListCubit = contactListCubit,
_chatListCubit = chatListCubit {
// Follow the active conversations bloc map cubit // Follow the active conversations bloc map cubit
follow(locator<ActiveConversationsBlocMapCubit>()); follow(activeConversationsBlocMapCubit);
} }
Future<void> _addConversationMessages( Future<void> _addConversationMessages(
@ -33,7 +40,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
add(() => MapEntry( add(() => MapEntry(
contact.localConversationRecordKey.toVeilid(), contact.localConversationRecordKey.toVeilid(),
SingleContactMessagesCubit( SingleContactMessagesCubit(
locator: _locator, accountInfo: _accountInfo,
remoteIdentityPublicKey: contact.identityPublicKey.toVeilid(), remoteIdentityPublicKey: contact.identityPublicKey.toVeilid(),
localConversationRecordKey: localConversationRecordKey:
contact.localConversationRecordKey.toVeilid(), contact.localConversationRecordKey.toVeilid(),
@ -52,7 +59,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
Future<void> updateState( Future<void> updateState(
TypedKey key, AsyncValue<ActiveConversationState> value) async { TypedKey key, AsyncValue<ActiveConversationState> value) async {
// Get the contact object for this single contact chat // Get the contact object for this single contact chat
final contactList = _locator<ContactListCubit>().state.state.asData?.value; final contactList = _contactListCubit.state.state.asData?.value;
if (contactList == null) { if (contactList == null) {
await addState(key, const AsyncValue.loading()); await addState(key, const AsyncValue.loading());
return; return;
@ -67,7 +74,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
final contact = contactList[contactIndex].value; final contact = contactList[contactIndex].value;
// Get the chat object for this single contact chat // Get the chat object for this single contact chat
final chatList = _locator<ChatListCubit>().state.state.asData?.value; final chatList = _chatListCubit.state.state.asData?.value;
if (chatList == null) { if (chatList == null) {
await addState(key, const AsyncValue.loading()); await addState(key, const AsyncValue.loading());
return; return;
@ -92,6 +99,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
} }
//// ////
final AccountInfo _accountInfo;
final Locator _locator; final ContactListCubit _contactListCubit;
final ChatListCubit _chatListCubit;
} }

View File

@ -10,7 +10,6 @@ import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:protobuf/protobuf.dart'; import 'package:protobuf/protobuf.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
@ -36,19 +35,16 @@ class ConversationState extends Equatable {
/// 1-1 chats /// 1-1 chats
class ConversationCubit extends Cubit<AsyncValue<ConversationState>> { class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
ConversationCubit( ConversationCubit(
{required Locator locator, {required AccountInfo accountInfo,
required TypedKey remoteIdentityPublicKey, required TypedKey remoteIdentityPublicKey,
TypedKey? localConversationRecordKey, TypedKey? localConversationRecordKey,
TypedKey? remoteConversationRecordKey}) TypedKey? remoteConversationRecordKey})
: _locator = locator, : _accountInfo = accountInfo,
_localConversationRecordKey = localConversationRecordKey, _localConversationRecordKey = localConversationRecordKey,
_remoteIdentityPublicKey = remoteIdentityPublicKey, _remoteIdentityPublicKey = remoteIdentityPublicKey,
_remoteConversationRecordKey = remoteConversationRecordKey, _remoteConversationRecordKey = remoteConversationRecordKey,
super(const AsyncValue.loading()) { super(const AsyncValue.loading()) {
final unlockedAccountInfo = _identityWriter = _accountInfo.identityWriter;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
_accountRecordKey = unlockedAccountInfo.accountRecordKey;
_identityWriter = unlockedAccountInfo.identityWriter;
if (_localConversationRecordKey != null) { if (_localConversationRecordKey != null) {
_initWait.add(() async { _initWait.add(() async {
@ -60,7 +56,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final record = await pool.openRecordWrite( final record = await pool.openRecordWrite(
_localConversationRecordKey!, writer, _localConversationRecordKey!, writer,
debugName: 'ConversationCubit::LocalConversation', debugName: 'ConversationCubit::LocalConversation',
parent: _accountRecordKey, parent: accountInfo.accountRecordKey,
crypto: crypto); crypto: crypto);
return record; return record;
@ -77,7 +73,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final record = await pool.openRecordRead(_remoteConversationRecordKey, final record = await pool.openRecordRead(_remoteConversationRecordKey,
debugName: 'ConversationCubit::RemoteConversation', debugName: 'ConversationCubit::RemoteConversation',
parent: pool.getParentRecordKey(_remoteConversationRecordKey) ?? parent: pool.getParentRecordKey(_remoteConversationRecordKey) ??
_accountRecordKey, accountInfo.accountRecordKey,
crypto: crypto); crypto: crypto);
return record; return record;
}); });
@ -108,7 +104,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
/// The callback allows for more initialization to occur and for /// The callback allows for more initialization to occur and for
/// cleanup to delete records upon failure of the callback /// cleanup to delete records upon failure of the callback
Future<T> initLocalConversation<T>( Future<T> initLocalConversation<T>(
{required FutureOr<T> Function(DHTRecord) callback, {required proto.Profile profile,
required FutureOr<T> Function(DHTRecord) callback,
TypedKey? existingConversationRecordKey}) async { TypedKey? existingConversationRecordKey}) async {
assert(_localConversationRecordKey == null, assert(_localConversationRecordKey == null,
'must not have a local conversation yet'); 'must not have a local conversation yet');
@ -116,11 +113,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final pool = DHTRecordPool.instance; final pool = DHTRecordPool.instance;
final crypto = await _cachedConversationCrypto(); final crypto = await _cachedConversationCrypto();
final account = _locator<AccountRecordCubit>().state.asData!.value; final accountRecordKey = _accountInfo.accountRecordKey;
final unlockedAccountInfo = final writer = _accountInfo.identityWriter;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final writer = unlockedAccountInfo.identityWriter;
// Open with SMPL scheme for identity writer // Open with SMPL scheme for identity writer
late final DHTRecord localConversationRecord; late final DHTRecord localConversationRecord;
@ -150,9 +144,9 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
callback: (messages) async { callback: (messages) async {
// Create initial local conversation key contents // Create initial local conversation key contents
final conversation = proto.Conversation() final conversation = proto.Conversation()
..profile = account.profile ..profile = profile
..superIdentityJson = jsonEncode( ..superIdentityJson =
unlockedAccountInfo.localAccount.superIdentity.toJson()) jsonEncode(_accountInfo.localAccount.superIdentity.toJson())
..messages = messages.recordKey.toProto(); ..messages = messages.recordKey.toProto();
// Write initial conversation to record // Write initial conversation to record
@ -359,10 +353,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
if (conversationCrypto != null) { if (conversationCrypto != null) {
return conversationCrypto; return conversationCrypto;
} }
final unlockedAccountInfo = conversationCrypto =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!; await _accountInfo.makeConversationCrypto(_remoteIdentityPublicKey);
conversationCrypto = await unlockedAccountInfo
.makeConversationCrypto(_remoteIdentityPublicKey);
_conversationCrypto = conversationCrypto; _conversationCrypto = conversationCrypto;
return conversationCrypto; return conversationCrypto;
} }
@ -371,8 +363,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
// Fields // Fields
TypedKey get remoteIdentityPublicKey => _remoteIdentityPublicKey; TypedKey get remoteIdentityPublicKey => _remoteIdentityPublicKey;
final Locator _locator; final AccountInfo _accountInfo;
late final TypedKey _accountRecordKey;
late final KeyPair _identityWriter; late final KeyPair _identityWriter;
final TypedKey _remoteIdentityPublicKey; final TypedKey _remoteIdentityPublicKey;
TypedKey? _localConversationRecordKey; TypedKey? _localConversationRecordKey;

View File

@ -233,7 +233,8 @@ class _DrawerMenuState extends State<DrawerMenu> {
final scale = theme.extension<ScaleScheme>()!; final scale = theme.extension<ScaleScheme>()!;
//final textTheme = theme.textTheme; //final textTheme = theme.textTheme;
final localAccounts = context.watch<LocalAccountsCubit>().state; final localAccounts = context.watch<LocalAccountsCubit>().state;
final accountRecords = context.watch<AccountRecordsBlocMapCubit>().state; final accountRecords =
context.watch<PerAccountCollectionBlocMapCubit>().state;
final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state; final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state;
final gradient = LinearGradient( final gradient = LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,

View File

@ -164,45 +164,44 @@ class HomeScreenState extends State<HomeScreen> {
], child: Builder(builder: _buildAccountReadyDeviceSpecific))); ], child: Builder(builder: _buildAccountReadyDeviceSpecific)));
} }
Widget _buildAccount(BuildContext context, TypedKey superIdentityRecordKey) => Widget _buildAccount(BuildContext context, TypedKey superIdentityRecordKey,
BlocProvider<AccountInfoCubit>( PerAccountCollectionCubit perAccountCollectionCubit) =>
BlocBuilder<PerAccountCollectionCubit, PerAccountCollectionState>(
key: ValueKey(superIdentityRecordKey), key: ValueKey(superIdentityRecordKey),
create: (context) => AccountInfoCubit( bloc: perAccountCollectionCubit,
AccountRepository.instance, superIdentityRecordKey), builder: (context, state) {
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:
// Get the current active account record cubit switch (state.accountInfo.status) {
final activeAccountRecordCubit = context case AccountInfoStatus.accountInvalid:
.select<AccountRecordsBlocMapCubit, AccountRecordCubit?>( return const HomeAccountInvalid();
(c) => c.tryOperate(superIdentityRecordKey, case AccountInfoStatus.accountLocked:
closure: (x) => x)); return const HomeAccountLocked();
if (activeAccountRecordCubit == null) { case AccountInfoStatus.accountUnlocked:
return waitingPage();
}
return MultiBlocProvider(providers: [ // Get the current active account record cubit
BlocProvider<AccountRecordCubit>.value( final activeAccountRecordCubit = context.select<
value: activeAccountRecordCubit), PerAccountCollectionBlocMapCubit,
], child: Builder(builder: _buildUnlockedAccount)); 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));
}
});
};
Widget _buildAccountPageView(BuildContext context) { Widget _buildAccountPageView(BuildContext context) {
final localAccounts = context.watch<LocalAccountsCubit>().state; final localAccounts = context.watch<LocalAccountsCubit>().state;
final activeLocalAccountCubit = context.read<ActiveLocalAccountCubit>(); final activeLocalAccountCubit = context.watch<ActiveLocalAccountCubit>();
final perAccountCollectionBlocMapCubit =
context.watch<PerAccountCollectionBlocMapCubit>();
final activeIndex = localAccounts.indexWhere( final activeIndex = localAccounts.indexWhere(
(x) => x.superIdentity.recordKey == activeLocalAccountCubit.state); (x) => x.superIdentity.recordKey == activeLocalAccountCubit.state);
@ -218,7 +217,7 @@ class HomeScreenState extends State<HomeScreen> {
value.dispose(); value.dispose();
}, },
child: Builder( child: Builder(
builder: (context) => PageView.custom( builder: (context) => PageView.builder(
onPageChanged: (idx) { onPageChanged: (idx) {
singleFuture(this, () async { singleFuture(this, () async {
await AccountRepository.instance.switchToAccount( await AccountRepository.instance.switchToAccount(
@ -228,10 +227,21 @@ class HomeScreenState extends State<HomeScreen> {
controller: context controller: context
.read<ActiveAccountPageControllerWrapper>() .read<ActiveAccountPageControllerWrapper>()
.pageController, .pageController,
childrenDelegate: SliverChildListDelegate(localAccounts itemCount: localAccounts.length,
.map((la) => itemBuilder: (context, index) {
_buildAccount(context, la.superIdentity.recordKey)) final superIdentityRecordKey =
.toList())))); localAccounts[index].superIdentity.recordKey;
final perAccountCollectionCubit =
perAccountCollectionBlocMapCubit.tryOperate(
superIdentityRecordKey,
closure: (c) => c);
if (perAccountCollectionCubit == null) {
return HomeAccountMissing(
key: ValueKey(superIdentityRecordKey));
}
return _buildAccount(context, superIdentityRecordKey,
perAccountCollectionCubit);
})));
} }
@override @override