mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-12-09 21:45:38 -05:00
xfer
This commit is contained in:
parent
64d4d0cefb
commit
41bb198d92
40 changed files with 1623 additions and 1272 deletions
|
|
@ -1,68 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc_tools/bloc_tools.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.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 'active_conversations_bloc_map_cubit.dart';
|
||||
|
||||
// Map of remoteConversationRecordKey to MessagesCubit
|
||||
// Wraps a MessagesCubit to stream the latest messages to the state
|
||||
// Automatically follows the state of a ActiveConversationsBlocMapCubit.
|
||||
class ActiveConversationMessagesBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
AsyncValue<IList<proto.Message>>, MessagesCubit>
|
||||
with
|
||||
StateFollower<ActiveConversationsBlocMapState, TypedKey,
|
||||
AsyncValue<ActiveConversationState>> {
|
||||
ActiveConversationMessagesBlocMapCubit({
|
||||
required ActiveAccountInfo activeAccountInfo,
|
||||
}) : _activeAccountInfo = activeAccountInfo;
|
||||
|
||||
Future<void> _addConversationMessages(
|
||||
{required proto.Contact contact,
|
||||
required proto.Conversation localConversation,
|
||||
required proto.Conversation remoteConversation}) async =>
|
||||
add(() => MapEntry(
|
||||
contact.remoteConversationRecordKey.toVeilid(),
|
||||
MessagesCubit(
|
||||
activeAccountInfo: _activeAccountInfo,
|
||||
remoteIdentityPublicKey: contact.identityPublicKey.toVeilid(),
|
||||
localConversationRecordKey:
|
||||
contact.localConversationRecordKey.toVeilid(),
|
||||
remoteConversationRecordKey:
|
||||
contact.remoteConversationRecordKey.toVeilid(),
|
||||
localMessagesRecordKey: localConversation.messages.toVeilid(),
|
||||
remoteMessagesRecordKey:
|
||||
remoteConversation.messages.toVeilid())));
|
||||
|
||||
/// StateFollower /////////////////////////
|
||||
|
||||
@override
|
||||
IMap<TypedKey, AsyncValue<ActiveConversationState>> getStateMap(
|
||||
ActiveConversationsBlocMapState state) =>
|
||||
state;
|
||||
|
||||
@override
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(
|
||||
TypedKey key, AsyncValue<ActiveConversationState> value) async {
|
||||
await value.when(
|
||||
data: (state) => _addConversationMessages(
|
||||
contact: state.contact,
|
||||
localConversation: state.localConversation,
|
||||
remoteConversation: state.remoteConversation),
|
||||
loading: () => addState(key, const AsyncValue.loading()),
|
||||
error: (error, stackTrace) =>
|
||||
addState(key, AsyncValue.error(error, stackTrace)));
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
final ActiveAccountInfo _activeAccountInfo;
|
||||
}
|
||||
|
|
@ -34,6 +34,9 @@ typedef ActiveConversationsBlocMapState
|
|||
// Map of remoteConversationRecordKey to ActiveConversationCubit
|
||||
// Wraps a conversation cubit to only expose completely built conversations
|
||||
// Automatically follows the state of a ChatListCubit.
|
||||
// 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.
|
||||
class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
|
||||
with
|
||||
|
|
@ -82,7 +85,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
return IMap();
|
||||
}
|
||||
return IMap.fromIterable(stateValue,
|
||||
keyMapper: (e) => e.remoteConversationKey.toVeilid(),
|
||||
keyMapper: (e) => e.remoteConversationRecordKey.toVeilid(),
|
||||
valueMapper: (e) => e);
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +102,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
final contactIndex = contactList
|
||||
.indexWhere((c) => c.remoteConversationRecordKey.toVeilid() == key);
|
||||
if (contactIndex == -1) {
|
||||
await addState(key, AsyncValue.error('Contact not found for chat'));
|
||||
await addState(key, AsyncValue.error('Contact not found'));
|
||||
return;
|
||||
}
|
||||
final contact = contactList[contactIndex];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc_tools/bloc_tools.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../chat/chat.dart';
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import 'active_conversations_bloc_map_cubit.dart';
|
||||
import 'chat_list_cubit.dart';
|
||||
|
||||
// Map of remoteConversationRecordKey to MessagesCubit
|
||||
// Wraps a MessagesCubit to stream the latest messages to the state
|
||||
// Automatically follows the state of a ActiveConversationsBlocMapCubit.
|
||||
class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
AsyncValue<IList<proto.Message>>, SingleContactMessagesCubit>
|
||||
with
|
||||
StateFollower<ActiveConversationsBlocMapState, TypedKey,
|
||||
AsyncValue<ActiveConversationState>> {
|
||||
ActiveSingleContactChatBlocMapCubit(
|
||||
{required ActiveAccountInfo activeAccountInfo,
|
||||
required ContactListCubit contactListCubit,
|
||||
required ChatListCubit chatListCubit})
|
||||
: _activeAccountInfo = activeAccountInfo,
|
||||
_contactListCubit = contactListCubit,
|
||||
_chatListCubit = chatListCubit;
|
||||
|
||||
Future<void> _addConversationMessages(
|
||||
{required proto.Contact contact,
|
||||
required proto.Chat chat,
|
||||
required proto.Conversation localConversation,
|
||||
required proto.Conversation remoteConversation}) async =>
|
||||
add(() => MapEntry(
|
||||
contact.remoteConversationRecordKey.toVeilid(),
|
||||
SingleContactMessagesCubit(
|
||||
activeAccountInfo: _activeAccountInfo,
|
||||
remoteIdentityPublicKey: contact.identityPublicKey.toVeilid(),
|
||||
localConversationRecordKey:
|
||||
contact.localConversationRecordKey.toVeilid(),
|
||||
remoteConversationRecordKey:
|
||||
contact.remoteConversationRecordKey.toVeilid(),
|
||||
localMessagesRecordKey: localConversation.messages.toVeilid(),
|
||||
remoteMessagesRecordKey: remoteConversation.messages.toVeilid(),
|
||||
reconciledChatRecord: chat.reconciledChatRecord.toVeilid(),
|
||||
)));
|
||||
|
||||
/// StateFollower /////////////////////////
|
||||
|
||||
@override
|
||||
IMap<TypedKey, AsyncValue<ActiveConversationState>> getStateMap(
|
||||
ActiveConversationsBlocMapState state) =>
|
||||
state;
|
||||
|
||||
@override
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(
|
||||
TypedKey key, AsyncValue<ActiveConversationState> value) async {
|
||||
// Get the contact object for this single contact chat
|
||||
final contactList = _contactListCubit.state.state.data?.value;
|
||||
if (contactList == null) {
|
||||
await addState(key, const AsyncValue.loading());
|
||||
return;
|
||||
}
|
||||
final contactIndex = contactList
|
||||
.indexWhere((c) => c.remoteConversationRecordKey.toVeilid() == key);
|
||||
if (contactIndex == -1) {
|
||||
await addState(
|
||||
key, AsyncValue.error('Contact not found for conversation'));
|
||||
return;
|
||||
}
|
||||
final contact = contactList[contactIndex];
|
||||
|
||||
// Get the chat object for this single contact chat
|
||||
final chatList = _chatListCubit.state.state.data?.value;
|
||||
if (chatList == null) {
|
||||
await addState(key, const AsyncValue.loading());
|
||||
return;
|
||||
}
|
||||
final chatIndex = chatList
|
||||
.indexWhere((c) => c.remoteConversationRecordKey.toVeilid() == key);
|
||||
if (contactIndex == -1) {
|
||||
await addState(key, AsyncValue.error('Chat not found for conversation'));
|
||||
return;
|
||||
}
|
||||
final chat = chatList[chatIndex];
|
||||
|
||||
await value.when(
|
||||
data: (state) => _addConversationMessages(
|
||||
contact: contact,
|
||||
chat: chat,
|
||||
localConversation: state.localConversation,
|
||||
remoteConversation: state.remoteConversation),
|
||||
loading: () => addState(key, const AsyncValue.loading()),
|
||||
error: (error, stackTrace) =>
|
||||
addState(key, AsyncValue.error(error, stackTrace)));
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
final ActiveAccountInfo _activeAccountInfo;
|
||||
final ContactListCubit _contactListCubit;
|
||||
final ChatListCubit _chatListCubit;
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ 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';
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -16,7 +17,8 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||
required ActiveAccountInfo activeAccountInfo,
|
||||
required proto.Account account,
|
||||
required this.activeChatCubit,
|
||||
}) : super(
|
||||
}) : _activeAccountInfo = activeAccountInfo,
|
||||
super(
|
||||
open: () => _open(activeAccountInfo, account),
|
||||
decodeElement: proto.Chat.fromBuffer);
|
||||
|
||||
|
|
@ -50,15 +52,24 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||
throw Exception('Failed to get chat');
|
||||
}
|
||||
final c = proto.Chat.fromBuffer(cbuf);
|
||||
if (c.remoteConversationKey == remoteConversationRecordKeyProto) {
|
||||
if (c.remoteConversationRecordKey == remoteConversationRecordKeyProto) {
|
||||
// Nothing to do here
|
||||
return;
|
||||
}
|
||||
}
|
||||
final accountRecordKey = _activeAccountInfo
|
||||
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Make a record that can store the reconciled version of the chat
|
||||
final reconciledChatRecord =
|
||||
await (await DHTShortArray.create(parent: accountRecordKey))
|
||||
.scope((r) async => r.recordPointer);
|
||||
|
||||
// Create conversation type Chat
|
||||
final chat = proto.Chat()
|
||||
..type = proto.ChatType.SINGLE_CONTACT
|
||||
..remoteConversationKey = remoteConversationRecordKeyProto;
|
||||
..remoteConversationRecordKey = remoteConversationRecordKeyProto
|
||||
..reconciledChatRecord = reconciledChatRecord.toProto();
|
||||
|
||||
// Add chat
|
||||
final added = await shortArray.tryAddItem(chat.writeToBuffer());
|
||||
|
|
@ -71,8 +82,9 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||
/// Delete a chat
|
||||
Future<void> deleteChat(
|
||||
{required TypedKey remoteConversationRecordKey}) async {
|
||||
// Create conversation type Chat
|
||||
final remoteConversationKey = remoteConversationRecordKey.toProto();
|
||||
final accountRecordKey =
|
||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Remove Chat from account's list
|
||||
// if this fails, don't keep retrying, user can try again later
|
||||
|
|
@ -86,8 +98,18 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||
throw Exception('Failed to get chat');
|
||||
}
|
||||
final c = proto.Chat.fromBuffer(cbuf);
|
||||
if (c.remoteConversationKey == remoteConversationKey) {
|
||||
await shortArray.tryRemoveItem(i);
|
||||
if (c.remoteConversationRecordKey == remoteConversationKey) {
|
||||
// Found the right chat
|
||||
if (await shortArray.tryRemoveItem(i) != null) {
|
||||
try {
|
||||
await (await DHTShortArray.openOwned(
|
||||
c.reconciledChatRecord.toVeilid(),
|
||||
parent: accountRecordKey))
|
||||
.delete();
|
||||
} on Exception catch (e) {
|
||||
log.debug('error removing reconciled chat record: $e', e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -95,4 +117,5 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||
}
|
||||
|
||||
final ActiveChatCubit activeChatCubit;
|
||||
final ActiveAccountInfo _activeAccountInfo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export 'active_conversation_messages_bloc_map_cubit.dart';
|
||||
export 'active_single_contact_chat_bloc_map_cubit.dart';
|
||||
export 'active_conversations_bloc_map_cubit.dart';
|
||||
export 'chat_list_cubit.dart';
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class ChatSingleContactListWidget extends StatelessWidget {
|
|||
initialList: chatList.toList(),
|
||||
builder: (l, i, c) {
|
||||
final contact =
|
||||
contactMap[c.remoteConversationKey];
|
||||
contactMap[c.remoteConversationRecordKey];
|
||||
if (contact == null) {
|
||||
return const Text('...');
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ class ChatSingleContactListWidget extends StatelessWidget {
|
|||
final lowerValue = value.toLowerCase();
|
||||
return chatList.where((c) {
|
||||
final contact =
|
||||
contactMap[c.remoteConversationKey];
|
||||
contactMap[c.remoteConversationRecordKey];
|
||||
if (contact == null) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue