veilidchat/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart

136 lines
5.4 KiB
Dart
Raw Normal View History

2024-02-11 00:29:58 -05:00
import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
2024-02-11 00:29:58 -05:00
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
2024-06-15 00:01:08 -04:00
import '../../chat_list/cubits/cubits.dart';
import '../../contacts/contacts.dart';
import '../../proto/proto.dart' as proto;
2024-06-15 00:01:08 -04:00
import '../conversation.dart';
2024-02-11 00:29:58 -05:00
@immutable
class ActiveConversationState extends Equatable {
const ActiveConversationState({
required this.contact,
required this.localConversation,
required this.remoteConversation,
});
final proto.Contact contact;
final proto.Conversation localConversation;
final proto.Conversation remoteConversation;
@override
List<Object?> get props => [contact, localConversation, remoteConversation];
}
typedef ActiveConversationCubit = TransformerCubit<
AsyncValue<ActiveConversationState>,
AsyncValue<ConversationState>,
ConversationCubit>;
2024-02-11 00:29:58 -05:00
typedef ActiveConversationsBlocMapState
= BlocMapState<TypedKey, AsyncValue<ActiveConversationState>>;
2024-05-27 18:04:00 -04:00
// Map of localConversationRecordKey to ActiveConversationCubit
2024-02-11 00:29:58 -05:00
// Wraps a conversation cubit to only expose completely built conversations
2024-02-20 20:07:35 -05:00
// Automatically follows the state of a ChatListCubit.
2024-03-24 12:13:27 -04:00
// Even though 'conversations' are per-contact and not per-chat
// We currently only build the cubits for the chats that are active, not
// archived chats or contacts that are not actively in a chat.
2024-02-20 17:57:05 -05:00
class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
2024-02-20 20:07:35 -05:00
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
with StateMapFollower<ChatListCubitState, TypedKey, proto.Chat> {
2024-06-15 00:01:08 -04:00
ActiveConversationsBlocMapCubit({
required UnlockedAccountInfo unlockedAccountInfo,
required ContactListCubit contactListCubit,
required AccountRecordCubit accountRecordCubit,
}) : _activeAccountInfo = unlockedAccountInfo,
_contactListCubit = contactListCubit,
_accountRecordCubit = accountRecordCubit;
////////////////////////////////////////////////////////////////////////////
// Public Interface
////////////////////////////////////////////////////////////////////////////
// Private Implementation
2024-02-11 00:29:58 -05:00
// Add an active conversation to be tracked for changes
2024-02-28 11:58:46 -05:00
Future<void> _addConversation({required proto.Contact contact}) async =>
2024-06-15 00:01:08 -04:00
add(() {
final remoteIdentityPublicKey = contact.identityPublicKey.toVeilid();
final localConversationRecordKey =
contact.localConversationRecordKey.toVeilid();
final remoteConversationRecordKey =
contact.remoteConversationRecordKey.toVeilid();
// Conversation cubit the tracks the state between the local
// and remote halves of a contact's relationship with this account
final conversationCubit = ConversationCubit(
activeAccountInfo: _activeAccountInfo,
remoteIdentityPublicKey: remoteIdentityPublicKey,
localConversationRecordKey: localConversationRecordKey,
remoteConversationRecordKey: remoteConversationRecordKey,
)..watchAccountChanges(
_accountRecordCubit.stream, _accountRecordCubit.state);
_contactListCubit.followContactProfileChanges(
localConversationRecordKey,
conversationCubit.stream.map((x) => x.map(
data: (d) => d.value.remoteConversation?.profile,
loading: (_) => null,
error: (_) => null)),
conversationCubit.state.asData?.value.remoteConversation?.profile);
// Transformer that only passes through completed/active conversations
// along with the contact that corresponds to the completed
// conversation
final transformedCubit = TransformerCubit<
AsyncValue<ActiveConversationState>,
AsyncValue<ConversationState>,
ConversationCubit>(conversationCubit,
transform: (avstate) => avstate.when(
data: (data) => (data.localConversation == null ||
data.remoteConversation == null)
? const AsyncValue.loading()
: AsyncValue.data(ActiveConversationState(
contact: contact,
localConversation: data.localConversation!,
remoteConversation: data.remoteConversation!)),
loading: AsyncValue.loading,
error: AsyncValue.error));
return MapEntry(
contact.localConversationRecordKey.toVeilid(), transformedCubit);
});
2024-02-20 20:07:35 -05:00
/// StateFollower /////////////////////////
@override
Future<void> removeFromState(TypedKey key) => remove(key);
@override
Future<void> updateState(TypedKey key, proto.Chat value) async {
2024-04-05 22:03:04 -04:00
final contactList = _contactListCubit.state.state.asData?.value;
2024-02-20 20:07:35 -05:00
if (contactList == null) {
await addState(key, const AsyncValue.loading());
return;
}
2024-04-17 21:31:26 -04:00
final contactIndex = contactList.indexWhere(
2024-05-27 18:04:00 -04:00
(c) => c.value.localConversationRecordKey.toVeilid() == key);
2024-02-20 20:07:35 -05:00
if (contactIndex == -1) {
2024-03-24 12:13:27 -04:00
await addState(key, AsyncValue.error('Contact not found'));
2024-02-20 20:07:35 -05:00
return;
}
final contact = contactList[contactIndex];
2024-04-17 21:31:26 -04:00
await _addConversation(contact: contact.value);
2024-02-20 20:07:35 -05:00
}
////
final UnlockedAccountInfo _activeAccountInfo;
2024-02-20 20:07:35 -05:00
final ContactListCubit _contactListCubit;
2024-06-15 00:01:08 -04:00
final AccountRecordCubit _accountRecordCubit;
}