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> {
AccountInfoCubit(
AccountRepository accountRepository, TypedKey superIdentityRecordKey)
{required AccountRepository accountRepository,
required TypedKey superIdentityRecordKey})
: _accountRepository = accountRepository,
super(accountRepository.getAccountInfo(superIdentityRecordKey)!) {
// Subscribe to streams

View File

@ -1,6 +1,7 @@
export 'account_info_cubit.dart';
export 'account_record_cubit.dart';
export 'account_records_bloc_map_cubit.dart';
export 'active_local_account_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';

View File

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

View File

@ -10,6 +10,7 @@ import '../../chat/chat.dart';
import '../../chat_list/chat_list.dart';
import '../../contact_invitation/contact_invitation.dart';
import '../../contacts/contacts.dart';
import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto;
import '../account_manager.dart';
@ -30,6 +31,14 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
await _accountRecordSubscription?.cancel();
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();
}
@ -95,48 +104,72 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
state.copyWith(avAccountRecordState: accountRecordCubit!.state);
// Get bloc parameters
final accountRecordKey = nextState.accountInfo.accountRecordKey;
final accountInfo = nextState.accountInfo;
// ContactInvitationListCubit
final contactInvitationListRecordPointer = nextState
.avAccountRecordState.asData?.value.contactInvitationRecords
.toVeilid();
contactInvitationListCubitUpdater.update(
final contactInvitationListCubit = contactInvitationListCubitUpdater.update(
contactInvitationListRecordPointer == null
? null
: (
collectionLocator,
accountRecordKey,
contactInvitationListRecordPointer
));
: (accountInfo, contactInvitationListRecordPointer));
// ContactListCubit
final contactListRecordPointer =
nextState.avAccountRecordState.asData?.value.contactList.toVeilid();
contactListCubitUpdater.update(contactListRecordPointer == null
? null
: (collectionLocator, accountRecordKey, contactListRecordPointer));
final contactListCubit = contactListCubitUpdater.update(
contactListRecordPointer == null
? null
: (accountInfo, contactListRecordPointer));
// WaitingInvitationsBlocMapCubit
waitingInvitationsBlocMapCubitUpdater.update(
nextState.avAccountRecordState.isData ? collectionLocator : null);
contactInvitationListCubit == null
? null
: (accountInfo, accountRecordCubit!, contactInvitationListCubit));
// ActiveChatCubit
activeChatCubitUpdater
final 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));
final chatListCubit = chatListCubitUpdater.update(
chatListRecordPointer == null || activeChatCubit == null
? null
: (accountInfo, chatListRecordPointer, activeChatCubit));
// 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;
}
@ -163,6 +196,12 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
if (T is ChatListCubit) {
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>();
}
@ -178,32 +217,52 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
StreamSubscription<AsyncValue<AccountRecordState>>?
_accountRecordSubscription;
final contactInvitationListCubitUpdater = BlocUpdater<
ContactInvitationListCubit,
(Locator, TypedKey, OwnedDHTRecordPointer)>(
ContactInvitationListCubit, (AccountInfo, OwnedDHTRecordPointer)>(
create: (params) => ContactInvitationListCubit(
locator: params.$1,
accountRecordKey: params.$2,
contactInvitationListRecordPointer: params.$3,
accountInfo: params.$1,
contactInvitationListRecordPointer: params.$2,
));
final contactListCubitUpdater =
BlocUpdater<ContactListCubit, (Locator, TypedKey, OwnedDHTRecordPointer)>(
BlocUpdater<ContactListCubit, (AccountInfo, OwnedDHTRecordPointer)>(
create: (params) => ContactListCubit(
locator: params.$1,
accountRecordKey: params.$2,
contactListRecordPointer: params.$3,
));
final waitingInvitationsBlocMapCubitUpdater =
BlocUpdater<WaitingInvitationsBlocMapCubit, Locator>(
create: (params) => WaitingInvitationsBlocMapCubit(
locator: params,
accountInfo: params.$1,
contactListRecordPointer: params.$2,
));
final waitingInvitationsBlocMapCubitUpdater = BlocUpdater<
WaitingInvitationsBlocMapCubit,
(AccountInfo, AccountRecordCubit, ContactInvitationListCubit)>(
create: (params) => WaitingInvitationsBlocMapCubit(
accountInfo: params.$1,
accountRecordCubit: params.$2,
contactInvitationListCubit: params.$3));
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,
));
final chatListCubitUpdater = BlocUpdater<ChatListCubit,
(AccountInfo, OwnedDHTRecordPointer, ActiveChatCubit)>(
create: (params) => ChatListCubit(
accountInfo: params.$1,
chatListRecordPointer: params.$2,
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 {
const AccountInfo({
required this.status,
required this.active,
required this.localAccount,
required this.userLogin,
});
final AccountInfoStatus status;
final bool active;
final LocalAccount localAccount;
final UserLogin? userLogin;
@override
List<Object?> get props => [
status,
active,
localAccount,
userLogin,
];

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
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:veilid_support/veilid_support.dart';
@ -27,10 +26,12 @@ const metadataKeyAttachments = 'attachments';
class ChatComponentCubit extends Cubit<ChatComponentState> {
ChatComponentCubit._({
required Locator locator,
required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required List<ActiveConversationCubit> conversationCubits,
required SingleContactMessagesCubit messagesCubit,
}) : _locator = locator,
}) : _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_conversationCubits = conversationCubits,
_messagesCubit = messagesCubit,
super(ChatComponentState(
@ -48,26 +49,25 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
}
factory ChatComponentCubit.singleContact(
{required Locator locator,
{required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ActiveConversationCubit activeConversationCubit,
required SingleContactMessagesCubit messagesCubit}) =>
ChatComponentCubit._(
locator: locator,
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
conversationCubits: [activeConversationCubit],
messagesCubit: messagesCubit,
);
Future<void> _init() async {
// Get local user info and account record cubit
final unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
_localUserIdentityKey = unlockedAccountInfo.identityTypedPublicKey;
_localUserAccountRecordCubit = _locator<AccountRecordCubit>();
_localUserIdentityKey = _accountInfo.identityTypedPublicKey;
// Subscribe to local user info
_localUserAccountRecordSubscription = _localUserAccountRecordCubit.stream
.listen(_onChangedLocalUserAccountRecord);
_onChangedLocalUserAccountRecord(_localUserAccountRecordCubit.state);
_accountRecordSubscription =
_accountRecordCubit.stream.listen(_onChangedAccountRecord);
_onChangedAccountRecord(_accountRecordCubit.state);
// Subscribe to remote user info
await _updateConversationSubscriptions();
@ -80,7 +80,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
@override
Future<void> close() async {
await _initWait();
await _localUserAccountRecordSubscription.cancel();
await _accountRecordSubscription.cancel();
await _messagesSubscription.cancel();
await super.close();
}
@ -145,7 +145,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
////////////////////////////////////////////////////////////////////////////
// Private Implementation
void _onChangedLocalUserAccountRecord(AsyncValue<proto.Account> avAccount) {
void _onChangedAccountRecord(AsyncValue<proto.Account> avAccount) {
final account = avAccount.asData?.value;
if (account == null) {
emit(state.copyWith(localUser: null));
@ -374,15 +374,14 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
////////////////////////////////////////////////////////////////////////////
final _initWait = WaitSet<void>();
final Locator _locator;
final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
final List<ActiveConversationCubit> _conversationCubits;
final SingleContactMessagesCubit _messagesCubit;
late final TypedKey _localUserIdentityKey;
late final AccountRecordCubit _localUserAccountRecordCubit;
late final StreamSubscription<AsyncValue<proto.Account>>
_localUserAccountRecordSubscription;
_accountRecordSubscription;
final Map<TypedKey, StreamSubscription<AsyncValue<ActiveConversationState>>>
_conversationSubscriptions = {};
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:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.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
class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
SingleContactMessagesCubit({
required Locator locator,
required AccountInfo accountInfo,
required TypedKey remoteIdentityPublicKey,
required TypedKey localConversationRecordKey,
required TypedKey localMessagesRecordKey,
required TypedKey remoteConversationRecordKey,
required TypedKey remoteMessagesRecordKey,
}) : _locator = locator,
}) : _accountInfo = accountInfo,
_remoteIdentityPublicKey = remoteIdentityPublicKey,
_localConversationRecordKey = localConversationRecordKey,
_localMessagesRecordKey = localMessagesRecordKey,
@ -87,9 +86,6 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Initialize everything
Future<void> _init() async {
_unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
_unsentMessagesQueue = PersistentQueue<proto.Message>(
table: 'SingleContactUnsentMessages',
key: _remoteConversationRecordKey.toString(),
@ -115,15 +111,15 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Make crypto
Future<void> _initCrypto() async {
_conversationCrypto = await _unlockedAccountInfo
.makeConversationCrypto(_remoteIdentityPublicKey);
_conversationCrypto =
await _accountInfo.makeConversationCrypto(_remoteIdentityPublicKey);
_senderMessageIntegrity = await MessageIntegrity.create(
author: _unlockedAccountInfo.identityTypedPublicKey);
author: _accountInfo.identityTypedPublicKey);
}
// Open local messages key
Future<void> _initSentMessagesCubit() async {
final writer = _unlockedAccountInfo.identityWriter;
final writer = _accountInfo.identityWriter;
_sentMessagesCubit = DHTLogCubit(
open: () async => DHTLog.openWrite(_localMessagesRecordKey, writer,
@ -153,7 +149,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
Future<VeilidCrypto> _makeLocalMessagesCrypto() async =>
VeilidCryptoPrivate.fromTypedKey(
_unlockedAccountInfo.userLogin.identitySecret, 'tabledb');
_accountInfo.userLogin!.identitySecret, 'tabledb');
// Open reconciled chat record key
Future<void> _initReconciledMessagesCubit() async {
@ -245,9 +241,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
}
_reconciliation.reconcileMessages(
_unlockedAccountInfo.identityTypedPublicKey,
sentMessages,
_sentMessagesCubit!);
_accountInfo.identityTypedPublicKey, sentMessages, _sentMessagesCubit!);
// Update the view
_renderState();
@ -284,7 +278,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Now sign it
await _senderMessageIntegrity.signMessage(
message, _unlockedAccountInfo.identitySecretKey);
message, _accountInfo.identitySecretKey);
}
// Async process to send messages in the background
@ -336,8 +330,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
final renderedElements = <RenderStateElement>[];
for (final m in reconciledMessages.windowElements) {
final isLocal = m.content.author.toVeilid() ==
_unlockedAccountInfo.identityTypedPublicKey;
final isLocal =
m.content.author.toVeilid() == _accountInfo.identityTypedPublicKey;
final reconciledTimestamp = Timestamp.fromInt64(m.reconciledTime);
final sm =
isLocal ? sentMessagesMap[m.content.authorUniqueIdString] : null;
@ -375,7 +369,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Add common fields
// id and signature will get set by _processMessageToSend
message
..author = _unlockedAccountInfo.identityTypedPublicKey.toProto()
..author = _accountInfo.identityTypedPublicKey.toProto()
..timestamp = Veilid.instance.now().toInt64();
// Put in the queue
@ -408,8 +402,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
/////////////////////////////////////////////////////////////////////////
final WaitSet<void> _initWait = WaitSet();
final Locator _locator;
late final UnlockedAccountInfo _unlockedAccountInfo;
late final AccountInfo _accountInfo;
final TypedKey _remoteIdentityPublicKey;
final TypedKey _localConversationRecordKey;
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:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../conversation/conversation.dart';
import '../../theme/theme.dart';
import '../chat.dart';
@ -21,6 +22,12 @@ class ChatComponentWidget extends StatelessWidget {
static Widget builder(
{required TypedKey localConversationRecordKey, Key? key}) =>
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
final activeConversationCubit = context
.select<ActiveConversationsBlocMapCubit, ActiveConversationCubit?>(
@ -43,7 +50,8 @@ class ChatComponentWidget extends StatelessWidget {
// Make chat component state
return BlocProvider(
create: (context) => ChatComponentCubit.singleContact(
locator: context.read,
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
activeConversationCubit: activeConversationCubit,
messagesCubit: messagesCubit,
),

View File

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

View File

@ -1,4 +1,3 @@
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
@ -8,23 +7,21 @@ import '../../proto/proto.dart' as proto;
class ContactRequestInboxCubit
extends DefaultDHTRecordCubit<proto.SignedContactResponse?> {
ContactRequestInboxCubit(
{required Locator locator, required this.contactInvitationRecord})
{required AccountInfo accountInfo, required this.contactInvitationRecord})
: super(
open: () => _open(
locator: locator,
accountInfo: accountInfo,
contactInvitationRecord: contactInvitationRecord),
decodeState: (buf) => buf.isEmpty
? null
: proto.SignedContactResponse.fromBuffer(buf));
static Future<DHTRecord> _open(
{required Locator locator,
{required AccountInfo accountInfo,
required proto.ContactInvitationRecord contactInvitationRecord}) async {
final pool = DHTRecordPool.instance;
final unlockedAccountInfo =
locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final accountRecordKey = accountInfo.accountRecordKey;
final writerSecret = contactInvitationRecord.writerSecret.toVeilid();
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:equatable/equatable.dart';
import 'package:meta/meta.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart';
@ -24,18 +24,22 @@ class InvitationStatus extends Equatable {
class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
proto.SignedContactResponse?> {
WaitingInvitationCubit(ContactRequestInboxCubit super.input,
{required Locator locator,
required proto.ContactInvitationRecord contactInvitationRecord})
: super(
WaitingInvitationCubit(
ContactRequestInboxCubit super.input, {
required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required proto.ContactInvitationRecord contactInvitationRecord,
}) : super(
transform: (signedContactResponse) => _transform(
signedContactResponse,
locator: locator,
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
contactInvitationRecord: contactInvitationRecord));
static Future<AsyncValue<InvitationStatus>> _transform(
proto.SignedContactResponse? signedContactResponse,
{required Locator locator,
{required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required proto.ContactInvitationRecord contactInvitationRecord}) async {
if (signedContactResponse == null) {
return const AsyncValue.loading();
@ -69,7 +73,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
contactResponse.remoteConversationRecordKey.toVeilid();
final conversation = ConversationCubit(
locator: locator,
accountInfo: accountInfo,
remoteIdentityPublicKey:
contactSuperIdentity.currentInstance.typedPublicKey,
remoteConversationRecordKey: remoteConversationRecordKey);
@ -96,6 +100,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
final localConversationRecordKey =
contactInvitationRecord.localConversationRecordKey.toVeilid();
return conversation.initLocalConversation(
profile: accountRecordCubit.state.asData!.value.profile,
existingConversationRecordKey: localConversationRecordKey,
callback: (localConversation) async => AsyncValue.data(InvitationStatus(
acceptedContact: AcceptedContact(

View File

@ -1,8 +1,8 @@
import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../proto/proto.dart' as proto;
import 'cubits.dart';
@ -17,11 +17,14 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
with
StateMapFollower<DHTShortArrayBusyState<proto.ContactInvitationRecord>,
TypedKey, proto.ContactInvitationRecord> {
WaitingInvitationsBlocMapCubit({
required Locator locator,
}) : _locator = locator {
WaitingInvitationsBlocMapCubit(
{required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ContactInvitationListCubit contactInvitationListCubit})
: _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit {
// Follow the contact invitation list cubit
follow(locator<ContactInvitationListCubit>());
follow(contactInvitationListCubit);
}
Future<void> _addWaitingInvitation(
@ -31,9 +34,10 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(),
WaitingInvitationCubit(
ContactRequestInboxCubit(
locator: _locator,
accountInfo: _accountInfo,
contactInvitationRecord: contactInvitationRecord),
locator: _locator,
accountInfo: _accountInfo,
accountRecordCubit: _accountRecordCubit,
contactInvitationRecord: contactInvitationRecord)));
/// StateFollower /////////////////////////
@ -46,5 +50,6 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
_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:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
@ -14,12 +13,12 @@ import 'models.dart';
class ValidContactInvitation {
@internal
ValidContactInvitation(
{required Locator locator,
{required AccountInfo accountInfo,
required TypedKey contactRequestInboxKey,
required proto.ContactRequestPrivate contactRequestPrivate,
required SuperIdentity contactSuperIdentity,
required KeyPair writer})
: _locator = locator,
: _accountInfo = accountInfo,
_contactRequestInboxKey = contactRequestInboxKey,
_contactRequestPrivate = contactRequestPrivate,
_contactSuperIdentity = contactSuperIdentity,
@ -27,65 +26,57 @@ class ValidContactInvitation {
proto.Profile get remoteProfile => _contactRequestPrivate.profile;
Future<AcceptedContact?> accept() async {
Future<AcceptedContact?> accept(proto.Profile profile) async {
final pool = DHTRecordPool.instance;
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
// The initiating side will delete the records in deleteInvitation()
final isSelf =
_contactSuperIdentity.currentInstance.publicKey == identityPublicKey;
final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
_accountInfo.identityPublicKey;
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::accept::'
'ContactRequestInbox',
parent: pool.getParentRecordKey(_contactRequestInboxKey) ??
accountRecordKey))
_accountInfo.accountRecordKey))
// ignore: prefer_expression_function_bodies
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
// Create local conversation key for this
// contact and send via contact response
final conversation = ConversationCubit(
locator: _locator,
accountInfo: _accountInfo,
remoteIdentityPublicKey:
_contactSuperIdentity.currentInstance.typedPublicKey);
return conversation.initLocalConversation(
profile: profile,
callback: (localConversation) async {
final contactResponse = proto.ContactResponse()
..accept = true
..remoteConversationRecordKey = localConversation.key.toProto()
..superIdentityRecordKey =
unlockedAccountInfo.superIdentityRecordKey.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final contactResponse = proto.ContactResponse()
..accept = true
..remoteConversationRecordKey = localConversation.key.toProto()
..superIdentityRecordKey =
_accountInfo.superIdentityRecordKey.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final cs =
await pool.veilid.getCryptoSystem(_contactRequestInboxKey.kind);
final cs = await _accountInfo.identityCryptoSystem;
final identitySignature = await cs.signWithKeyPair(
_accountInfo.identityWriter, contactResponseBytes);
final identitySignature = await cs.sign(
unlockedAccountInfo.identityWriter.key,
unlockedAccountInfo.identityWriter.secret,
contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the acceptance to the inbox
await contactRequestInbox
.eventualWriteProtobuf(signedContactResponse, subkey: 1);
// Write the acceptance to the inbox
await contactRequestInbox.eventualWriteProtobuf(signedContactResponse,
subkey: 1);
return AcceptedContact(
remoteProfile: _contactRequestPrivate.profile,
remoteIdentity: _contactSuperIdentity,
remoteConversationRecordKey:
_contactRequestPrivate.chatRecordKey.toVeilid(),
localConversationRecordKey: localConversation.key,
);
});
return AcceptedContact(
remoteProfile: _contactRequestPrivate.profile,
remoteIdentity: _contactSuperIdentity,
remoteConversationRecordKey:
_contactRequestPrivate.chatRecordKey.toVeilid(),
localConversationRecordKey: localConversation.key,
);
});
});
} on Exception catch (e) {
log.debug('exception: $e', e);
@ -96,33 +87,24 @@ class ValidContactInvitation {
Future<bool> reject() async {
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
final isSelf =
_contactSuperIdentity.currentInstance.publicKey == identityPublicKey;
final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
_accountInfo.identityPublicKey;
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::reject::'
'ContactRequestInbox',
parent: accountRecordKey))
parent: _accountInfo.accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
final cs =
await pool.veilid.getCryptoSystem(_contactRequestInboxKey.kind);
final contactResponse = proto.ContactResponse()
..accept = false
..superIdentityRecordKey =
unlockedAccountInfo.superIdentityRecordKey.toProto();
_accountInfo.superIdentityRecordKey.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final identitySignature = await cs.sign(
unlockedAccountInfo.identityWriter.key,
unlockedAccountInfo.identityWriter.secret,
contactResponseBytes);
final cs = await _accountInfo.identityCryptoSystem;
final identitySignature = await cs.signWithKeyPair(
_accountInfo.identityWriter, contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
@ -136,7 +118,7 @@ class ValidContactInvitation {
}
//
final Locator _locator;
final AccountInfo _accountInfo;
final TypedKey _contactRequestInboxKey;
final SuperIdentity _contactSuperIdentity;
final KeyPair _writer;

View File

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

View File

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

View File

@ -3,9 +3,9 @@ import 'dart:convert';
import 'package:async_tools/async_tools.dart';
import 'package:protobuf/protobuf.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart';
@ -15,12 +15,12 @@ import '../../tools/tools.dart';
class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
ContactListCubit({
required Locator locator,
required TypedKey accountRecordKey,
required AccountInfo accountInfo,
required OwnedDHTRecordPointer contactListRecordPointer,
}) : _locator = locator,
}) : _accountInfo = accountInfo,
super(
open: () => _open(accountRecordKey, contactListRecordPointer),
open: () =>
_open(accountInfo.accountRecordKey, contactListRecordPointer),
decodeElement: proto.Contact.fromBuffer);
static Future<DHTShortArray> _open(TypedKey accountRecordKey,
@ -126,7 +126,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
try {
// Make a conversation cubit to manipulate the conversation
final conversationCubit = ConversationCubit(
locator: _locator,
accountInfo: _accountInfo,
remoteIdentityPublicKey: deletedItem.identityPublicKey.toVeilid(),
localConversationRecordKey:
deletedItem.localConversationRecordKey.toVeilid(),
@ -144,5 +144,5 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
final _contactProfileUpdateMap =
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:equatable/equatable.dart';
import 'package:meta/meta.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
@ -45,10 +44,15 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
with StateMapFollower<ChatListCubitState, TypedKey, proto.Chat> {
ActiveConversationsBlocMapCubit({
required Locator locator,
}) : _locator = locator {
required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ChatListCubit chatListCubit,
required ContactListCubit contactListCubit,
}) : _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_contactListCubit = contactListCubit {
// 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
// and remote halves of a contact's relationship with this account
final conversationCubit = ConversationCubit(
locator: _locator,
accountInfo: _accountInfo,
remoteIdentityPublicKey: remoteIdentityPublicKey,
localConversationRecordKey: localConversationRecordKey,
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,
// update our local contact
_locator<ContactListCubit>().followContactProfileChanges(
_contactListCubit.followContactProfileChanges(
localConversationRecordKey,
conversationCubit.stream.map((x) => x.map(
data: (d) => d.value.remoteConversation?.profile,
@ -90,6 +89,10 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
error: (_) => null)),
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
// along with the contact that corresponds to the completed
// conversation
@ -119,7 +122,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
@override
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) {
await addState(key, const AsyncValue.loading());
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:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../chat/chat.dart';
import '../../chat_list/cubits/chat_list_cubit.dart';
import '../../contacts/contacts.dart';
import '../../proto/proto.dart' as proto;
import '../conversation.dart';
import 'active_conversations_bloc_map_cubit.dart';
// Map of localConversationRecordKey to MessagesCubit
@ -19,10 +20,16 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
with
StateMapFollower<ActiveConversationsBlocMapState, TypedKey,
AsyncValue<ActiveConversationState>> {
ActiveSingleContactChatBlocMapCubit({required Locator locator})
: _locator = locator {
ActiveSingleContactChatBlocMapCubit(
{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(locator<ActiveConversationsBlocMapCubit>());
follow(activeConversationsBlocMapCubit);
}
Future<void> _addConversationMessages(
@ -33,7 +40,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
add(() => MapEntry(
contact.localConversationRecordKey.toVeilid(),
SingleContactMessagesCubit(
locator: _locator,
accountInfo: _accountInfo,
remoteIdentityPublicKey: contact.identityPublicKey.toVeilid(),
localConversationRecordKey:
contact.localConversationRecordKey.toVeilid(),
@ -52,7 +59,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
Future<void> updateState(
TypedKey key, AsyncValue<ActiveConversationState> value) async {
// 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) {
await addState(key, const AsyncValue.loading());
return;
@ -67,7 +74,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
final contact = contactList[contactIndex].value;
// 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) {
await addState(key, const AsyncValue.loading());
return;
@ -92,6 +99,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
}
////
final Locator _locator;
final AccountInfo _accountInfo;
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:meta/meta.dart';
import 'package:protobuf/protobuf.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
@ -36,19 +35,16 @@ class ConversationState extends Equatable {
/// 1-1 chats
class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
ConversationCubit(
{required Locator locator,
{required AccountInfo accountInfo,
required TypedKey remoteIdentityPublicKey,
TypedKey? localConversationRecordKey,
TypedKey? remoteConversationRecordKey})
: _locator = locator,
: _accountInfo = accountInfo,
_localConversationRecordKey = localConversationRecordKey,
_remoteIdentityPublicKey = remoteIdentityPublicKey,
_remoteConversationRecordKey = remoteConversationRecordKey,
super(const AsyncValue.loading()) {
final unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
_accountRecordKey = unlockedAccountInfo.accountRecordKey;
_identityWriter = unlockedAccountInfo.identityWriter;
_identityWriter = _accountInfo.identityWriter;
if (_localConversationRecordKey != null) {
_initWait.add(() async {
@ -60,7 +56,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final record = await pool.openRecordWrite(
_localConversationRecordKey!, writer,
debugName: 'ConversationCubit::LocalConversation',
parent: _accountRecordKey,
parent: accountInfo.accountRecordKey,
crypto: crypto);
return record;
@ -77,7 +73,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final record = await pool.openRecordRead(_remoteConversationRecordKey,
debugName: 'ConversationCubit::RemoteConversation',
parent: pool.getParentRecordKey(_remoteConversationRecordKey) ??
_accountRecordKey,
accountInfo.accountRecordKey,
crypto: crypto);
return record;
});
@ -108,7 +104,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
/// The callback allows for more initialization to occur and for
/// cleanup to delete records upon failure of the callback
Future<T> initLocalConversation<T>(
{required FutureOr<T> Function(DHTRecord) callback,
{required proto.Profile profile,
required FutureOr<T> Function(DHTRecord) callback,
TypedKey? existingConversationRecordKey}) async {
assert(_localConversationRecordKey == null,
'must not have a local conversation yet');
@ -116,11 +113,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final pool = DHTRecordPool.instance;
final crypto = await _cachedConversationCrypto();
final account = _locator<AccountRecordCubit>().state.asData!.value;
final unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final writer = unlockedAccountInfo.identityWriter;
final accountRecordKey = _accountInfo.accountRecordKey;
final writer = _accountInfo.identityWriter;
// Open with SMPL scheme for identity writer
late final DHTRecord localConversationRecord;
@ -150,9 +144,9 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
callback: (messages) async {
// Create initial local conversation key contents
final conversation = proto.Conversation()
..profile = account.profile
..superIdentityJson = jsonEncode(
unlockedAccountInfo.localAccount.superIdentity.toJson())
..profile = profile
..superIdentityJson =
jsonEncode(_accountInfo.localAccount.superIdentity.toJson())
..messages = messages.recordKey.toProto();
// Write initial conversation to record
@ -359,10 +353,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
if (conversationCrypto != null) {
return conversationCrypto;
}
final unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
conversationCrypto = await unlockedAccountInfo
.makeConversationCrypto(_remoteIdentityPublicKey);
conversationCrypto =
await _accountInfo.makeConversationCrypto(_remoteIdentityPublicKey);
_conversationCrypto = conversationCrypto;
return conversationCrypto;
}
@ -371,8 +363,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
// Fields
TypedKey get remoteIdentityPublicKey => _remoteIdentityPublicKey;
final Locator _locator;
late final TypedKey _accountRecordKey;
final AccountInfo _accountInfo;
late final KeyPair _identityWriter;
final TypedKey _remoteIdentityPublicKey;
TypedKey? _localConversationRecordKey;

View File

@ -233,7 +233,8 @@ class _DrawerMenuState extends State<DrawerMenu> {
final scale = theme.extension<ScaleScheme>()!;
//final textTheme = theme.textTheme;
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 gradient = LinearGradient(
begin: Alignment.topLeft,

View File

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