mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-01-11 07:39:32 -05:00
checkpoint
This commit is contained in:
parent
3f8b4d2a41
commit
17211f3515
@ -38,9 +38,23 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(TypedKey key, LocalAccount value) async {
|
||||
Future<void> updateState(
|
||||
TypedKey key, LocalAccount? oldValue, LocalAccount newValue) async {
|
||||
// Don't replace unless this is a totally different account
|
||||
// The sub-cubit's subscription will update our state later
|
||||
if (oldValue != null) {
|
||||
if (oldValue.superIdentity.recordKey !=
|
||||
newValue.superIdentity.recordKey) {
|
||||
throw StateError(
|
||||
'should remove LocalAccount and make a new one, not change it, if '
|
||||
'the superidentity record key has changed');
|
||||
}
|
||||
// This never changes anything that should result in rebuildin the
|
||||
// sub-cubit
|
||||
return;
|
||||
}
|
||||
await _addPerAccountCollectionCubit(
|
||||
superIdentityRecordKey: value.superIdentity.recordKey);
|
||||
superIdentityRecordKey: newValue.superIdentity.recordKey);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -136,15 +136,17 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
|
||||
: (accountInfo, contactListRecordPointer));
|
||||
|
||||
// WaitingInvitationsBlocMapCubit
|
||||
final waitingInvitationsBlocMapCubit =
|
||||
waitingInvitationsBlocMapCubitUpdater.update(
|
||||
accountInfo.userLogin == null || contactInvitationListCubit == null
|
||||
? null
|
||||
: (
|
||||
accountInfo,
|
||||
accountRecordCubit!,
|
||||
contactInvitationListCubit
|
||||
));
|
||||
final waitingInvitationsBlocMapCubit = waitingInvitationsBlocMapCubitUpdater
|
||||
.update(accountInfo.userLogin == null ||
|
||||
contactInvitationListCubit == null ||
|
||||
contactListCubit == null
|
||||
? null
|
||||
: (
|
||||
accountInfo,
|
||||
accountRecordCubit!,
|
||||
contactInvitationListCubit,
|
||||
contactListCubit,
|
||||
));
|
||||
|
||||
// ActiveChatCubit
|
||||
final activeChatCubit = activeChatCubitUpdater
|
||||
@ -179,15 +181,11 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
|
||||
final activeSingleContactChatBlocMapCubit =
|
||||
activeSingleContactChatBlocMapCubitUpdater.update(
|
||||
accountInfo.userLogin == null ||
|
||||
activeConversationsBlocMapCubit == null ||
|
||||
chatListCubit == null ||
|
||||
contactListCubit == null
|
||||
activeConversationsBlocMapCubit == null
|
||||
? null
|
||||
: (
|
||||
accountInfo,
|
||||
activeConversationsBlocMapCubit,
|
||||
chatListCubit,
|
||||
contactListCubit
|
||||
));
|
||||
|
||||
// Update available blocs in our state
|
||||
@ -260,11 +258,18 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
|
||||
));
|
||||
final waitingInvitationsBlocMapCubitUpdater = BlocUpdater<
|
||||
WaitingInvitationsBlocMapCubit,
|
||||
(AccountInfo, AccountRecordCubit, ContactInvitationListCubit)>(
|
||||
(
|
||||
AccountInfo,
|
||||
AccountRecordCubit,
|
||||
ContactInvitationListCubit,
|
||||
ContactListCubit
|
||||
)>(
|
||||
create: (params) => WaitingInvitationsBlocMapCubit(
|
||||
accountInfo: params.$1,
|
||||
accountRecordCubit: params.$2,
|
||||
contactInvitationListCubit: params.$3));
|
||||
accountInfo: params.$1,
|
||||
accountRecordCubit: params.$2,
|
||||
contactInvitationListCubit: params.$3,
|
||||
contactListCubit: params.$4,
|
||||
));
|
||||
final activeChatCubitUpdater =
|
||||
BlocUpdater<ActiveChatCubit, bool>(create: (_) => ActiveChatCubit(null));
|
||||
final chatListCubitUpdater = BlocUpdater<ChatListCubit,
|
||||
@ -286,13 +291,9 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
|
||||
(
|
||||
AccountInfo,
|
||||
ActiveConversationsBlocMapCubit,
|
||||
ChatListCubit,
|
||||
ContactListCubit
|
||||
)>(
|
||||
create: (params) => ActiveSingleContactChatBlocMapCubit(
|
||||
accountInfo: params.$1,
|
||||
activeConversationsBlocMapCubit: params.$2,
|
||||
chatListCubit: params.$3,
|
||||
contactListCubit: params.$4,
|
||||
));
|
||||
}
|
||||
|
@ -116,7 +116,14 @@ class _EditAccountPageState extends State<EditAccountPage> {
|
||||
});
|
||||
try {
|
||||
// Look up account cubit for this specific account
|
||||
final accountRecordCubit = context.read<AccountRecordCubit>();
|
||||
final perAccountCollectionBlocMapCubit =
|
||||
context.read<PerAccountCollectionBlocMapCubit>();
|
||||
final accountRecordCubit = await perAccountCollectionBlocMapCubit
|
||||
.operate(widget.superIdentityRecordKey,
|
||||
closure: (c) async => c.accountRecordCubit);
|
||||
if (accountRecordCubit == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update account profile DHT record
|
||||
// This triggers ConversationCubits to update
|
||||
|
@ -12,6 +12,7 @@ import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../conversation/conversation.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../models/chat_component_state.dart';
|
||||
@ -28,10 +29,12 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
|
||||
ChatComponentCubit._({
|
||||
required AccountInfo accountInfo,
|
||||
required AccountRecordCubit accountRecordCubit,
|
||||
required ContactListCubit contactListCubit,
|
||||
required List<ActiveConversationCubit> conversationCubits,
|
||||
required SingleContactMessagesCubit messagesCubit,
|
||||
}) : _accountInfo = accountInfo,
|
||||
_accountRecordCubit = accountRecordCubit,
|
||||
_contactListCubit = contactListCubit,
|
||||
_conversationCubits = conversationCubits,
|
||||
_messagesCubit = messagesCubit,
|
||||
super(ChatComponentState(
|
||||
@ -51,11 +54,13 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
|
||||
factory ChatComponentCubit.singleContact(
|
||||
{required AccountInfo accountInfo,
|
||||
required AccountRecordCubit accountRecordCubit,
|
||||
required ContactListCubit contactListCubit,
|
||||
required ActiveConversationCubit activeConversationCubit,
|
||||
required SingleContactMessagesCubit messagesCubit}) =>
|
||||
ChatComponentCubit._(
|
||||
accountInfo: accountInfo,
|
||||
accountRecordCubit: accountRecordCubit,
|
||||
contactListCubit: contactListCubit,
|
||||
conversationCubits: [activeConversationCubit],
|
||||
messagesCubit: messagesCubit,
|
||||
);
|
||||
@ -82,6 +87,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
|
||||
await _initWait();
|
||||
await _accountRecordSubscription.cancel();
|
||||
await _messagesSubscription.cancel();
|
||||
await _conversationSubscriptions.values.map((v) => v.cancel()).wait;
|
||||
await super.close();
|
||||
}
|
||||
|
||||
@ -146,12 +152,12 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
|
||||
// Private Implementation
|
||||
|
||||
void _onChangedAccountRecord(AsyncValue<proto.Account> avAccount) {
|
||||
// Update local 'User'
|
||||
final account = avAccount.asData?.value;
|
||||
if (account == null) {
|
||||
emit(state.copyWith(localUser: null));
|
||||
return;
|
||||
}
|
||||
// Make local 'User'
|
||||
final localUser = types.User(
|
||||
id: _localUserIdentityKey.toString(),
|
||||
firstName: account.profile.name,
|
||||
@ -168,15 +174,40 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
|
||||
TypedKey remoteIdentityPublicKey,
|
||||
AsyncValue<ActiveConversationState> avConversationState,
|
||||
) {
|
||||
//
|
||||
// Update remote 'User'
|
||||
final activeConversationState = avConversationState.asData?.value;
|
||||
if (activeConversationState == null) {
|
||||
// Don't change user information on loading state
|
||||
return;
|
||||
}
|
||||
emit(state.copyWith(
|
||||
remoteUsers: state.remoteUsers.add(
|
||||
remoteIdentityPublicKey,
|
||||
_convertRemoteUser(
|
||||
remoteIdentityPublicKey, activeConversationState))));
|
||||
}
|
||||
|
||||
types.User _convertRemoteUser(TypedKey remoteIdentityPublicKey,
|
||||
ActiveConversationState activeConversationState) =>
|
||||
types.User(
|
||||
id: remoteIdentityPublicKey.toString(),
|
||||
firstName: activeConversationState.contact.displayName,
|
||||
metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey});
|
||||
ActiveConversationState activeConversationState) {
|
||||
// See if we have a contact for this remote user
|
||||
final contacts = _contactListCubit.state.state.asData?.value;
|
||||
if (contacts != null) {
|
||||
final contactIdx = contacts.indexWhere((x) =>
|
||||
x.value.identityPublicKey.toVeilid() == remoteIdentityPublicKey);
|
||||
if (contactIdx != -1) {
|
||||
final contact = contacts[contactIdx].value;
|
||||
return types.User(
|
||||
id: remoteIdentityPublicKey.toString(),
|
||||
firstName: contact.displayName,
|
||||
metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey});
|
||||
}
|
||||
}
|
||||
|
||||
return types.User(
|
||||
id: remoteIdentityPublicKey.toString(),
|
||||
firstName: activeConversationState.remoteConversation.profile.name,
|
||||
metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey});
|
||||
}
|
||||
|
||||
types.User _convertUnknownUser(TypedKey remoteIdentityPublicKey) =>
|
||||
types.User(
|
||||
@ -376,6 +407,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
|
||||
final _initWait = WaitSet<void>();
|
||||
final AccountInfo _accountInfo;
|
||||
final AccountRecordCubit _accountRecordCubit;
|
||||
final ContactListCubit _contactListCubit;
|
||||
final List<ActiveConversationCubit> _conversationCubits;
|
||||
final SingleContactMessagesCubit _messagesCubit;
|
||||
|
||||
|
@ -9,6 +9,7 @@ import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../conversation/conversation.dart';
|
||||
import '../../theme/theme.dart';
|
||||
import '../chat.dart';
|
||||
@ -28,10 +29,13 @@ class ChatComponentWidget extends StatelessWidget {
|
||||
// Get the account record cubit
|
||||
final accountRecordCubit = context.read<AccountRecordCubit>();
|
||||
|
||||
// Get the contact list cubit
|
||||
final contactListCubit = context.watch<ContactListCubit>();
|
||||
|
||||
// Get the active conversation cubit
|
||||
final activeConversationCubit = context
|
||||
.select<ActiveConversationsBlocMapCubit, ActiveConversationCubit?>(
|
||||
(x) => x.tryOperate(localConversationRecordKey,
|
||||
(x) => x.tryOperateSync(localConversationRecordKey,
|
||||
closure: (cubit) => cubit));
|
||||
if (activeConversationCubit == null) {
|
||||
return waitingPage();
|
||||
@ -41,7 +45,7 @@ class ChatComponentWidget extends StatelessWidget {
|
||||
final messagesCubit = context.select<
|
||||
ActiveSingleContactChatBlocMapCubit,
|
||||
SingleContactMessagesCubit?>(
|
||||
(x) => x.tryOperate(localConversationRecordKey,
|
||||
(x) => x.tryOperateSync(localConversationRecordKey,
|
||||
closure: (cubit) => cubit));
|
||||
if (messagesCubit == null) {
|
||||
return waitingPage();
|
||||
@ -49,9 +53,11 @@ class ChatComponentWidget extends StatelessWidget {
|
||||
|
||||
// Make chat component state
|
||||
return BlocProvider(
|
||||
key: key,
|
||||
create: (context) => ChatComponentCubit.singleContact(
|
||||
accountInfo: accountInfo,
|
||||
accountRecordCubit: accountRecordCubit,
|
||||
contactListCubit: contactListCubit,
|
||||
activeConversationCubit: activeConversationCubit,
|
||||
messagesCubit: messagesCubit,
|
||||
),
|
||||
|
@ -54,6 +54,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
// Make local copy so we don't share the buffer
|
||||
final localConversationRecordKey =
|
||||
contact.localConversationRecordKey.toVeilid();
|
||||
final remoteIdentityPublicKey = contact.identityPublicKey.toVeilid();
|
||||
final remoteConversationRecordKey =
|
||||
contact.remoteConversationRecordKey.toVeilid();
|
||||
|
||||
@ -67,18 +68,38 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
throw Exception('Failed to get chat');
|
||||
}
|
||||
final c = proto.Chat.fromBuffer(cbuf);
|
||||
if (c.localConversationRecordKey ==
|
||||
contact.localConversationRecordKey) {
|
||||
// Nothing to do here
|
||||
return;
|
||||
|
||||
switch (c.whichKind()) {
|
||||
case proto.Chat_Kind.direct:
|
||||
if (c.direct.localConversationRecordKey ==
|
||||
contact.localConversationRecordKey) {
|
||||
// Nothing to do here
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case proto.Chat_Kind.group:
|
||||
if (c.group.localConversationRecordKey ==
|
||||
contact.localConversationRecordKey) {
|
||||
throw StateError('direct conversation record key should'
|
||||
' not be used for group chats!');
|
||||
}
|
||||
break;
|
||||
case proto.Chat_Kind.notSet:
|
||||
throw StateError('unknown chat kind');
|
||||
}
|
||||
}
|
||||
|
||||
// Create 1:1 conversation type Chat
|
||||
final chat = proto.Chat()
|
||||
final chatMember = proto.ChatMember()
|
||||
..remoteIdentityPublicKey = remoteIdentityPublicKey.toProto()
|
||||
..remoteConversationRecordKey = remoteConversationRecordKey.toProto();
|
||||
|
||||
final directChat = proto.DirectChat()
|
||||
..settings = await getDefaultChatSettings(contact)
|
||||
..localConversationRecordKey = localConversationRecordKey.toProto()
|
||||
..remoteConversationRecordKey = remoteConversationRecordKey.toProto();
|
||||
..remoteMember = chatMember;
|
||||
|
||||
final chat = proto.Chat()..direct = directChat;
|
||||
|
||||
// Add chat
|
||||
await writer.add(chat.writeToBuffer());
|
||||
@ -88,9 +109,6 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
/// Delete a chat
|
||||
Future<void> deleteChat(
|
||||
{required TypedKey localConversationRecordKey}) async {
|
||||
final localConversationRecordKeyProto =
|
||||
localConversationRecordKey.toProto();
|
||||
|
||||
// Remove Chat from account's list
|
||||
// if this fails, don't keep retrying, user can try again later
|
||||
final deletedItem =
|
||||
@ -104,9 +122,9 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
if (c == null) {
|
||||
throw Exception('Failed to get chat');
|
||||
}
|
||||
|
||||
if (c.localConversationRecordKey ==
|
||||
localConversationRecordKeyProto) {
|
||||
// Found the right chat
|
||||
localConversationRecordKey) {
|
||||
await writer.remove(i);
|
||||
return c;
|
||||
}
|
||||
@ -133,7 +151,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
return IMap();
|
||||
}
|
||||
return IMap.fromIterable(stateValue,
|
||||
keyMapper: (e) => e.value.localConversationRecordKey.toVeilid(),
|
||||
keyMapper: (e) => e.value.localConversationRecordKey,
|
||||
valueMapper: (e) => e.value);
|
||||
}
|
||||
|
||||
|
96
lib/chat_list/views/chat_list_widget.dart
Normal file
96
lib/chat_list/views/chat_list_widget.dart
Normal file
@ -0,0 +1,96 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:searchable_listview/searchable_listview.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../proto/proto.dart';
|
||||
import '../../theme/theme.dart';
|
||||
import '../chat_list.dart';
|
||||
|
||||
class ChatListWidget extends StatelessWidget {
|
||||
const ChatListWidget({super.key});
|
||||
|
||||
Widget _itemBuilderDirect(proto.DirectChat direct,
|
||||
IMap<proto.TypedKey, proto.Contact> contactMap, bool busy) {
|
||||
final contact = contactMap[direct.localConversationRecordKey];
|
||||
if (contact == null) {
|
||||
return const Text('...');
|
||||
}
|
||||
return ChatSingleContactItemWidget(contact: contact, disabled: busy)
|
||||
.paddingLTRB(0, 4, 0, 0);
|
||||
}
|
||||
|
||||
List<proto.Chat> _itemFilter(IMap<proto.TypedKey, proto.Contact> contactMap,
|
||||
IList<DHTShortArrayElementState<Chat>> chatList, String filter) {
|
||||
final lowerValue = filter.toLowerCase();
|
||||
return chatList.map((x) => x.value).where((c) {
|
||||
switch (c.whichKind()) {
|
||||
case proto.Chat_Kind.direct:
|
||||
final contact = contactMap[c.direct.localConversationRecordKey];
|
||||
if (contact == null) {
|
||||
return false;
|
||||
}
|
||||
return contact.nickname.toLowerCase().contains(lowerValue) ||
|
||||
contact.profile.name.toLowerCase().contains(lowerValue) ||
|
||||
contact.profile.pronouns.toLowerCase().contains(lowerValue);
|
||||
case proto.Chat_Kind.group:
|
||||
// xxx: how to filter group chats
|
||||
return true;
|
||||
case proto.Chat_Kind.notSet:
|
||||
throw StateError('unknown chat kind');
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final contactListV = context.watch<ContactListCubit>().state;
|
||||
|
||||
return contactListV.builder((context, contactList) {
|
||||
final contactMap = IMap.fromIterable(contactList,
|
||||
keyMapper: (c) => c.value.localConversationRecordKey,
|
||||
valueMapper: (c) => c.value);
|
||||
|
||||
final chatListV = context.watch<ChatListCubit>().state;
|
||||
return chatListV
|
||||
.builder((context, chatList) => SizedBox.expand(
|
||||
child: styledTitleContainer(
|
||||
context: context,
|
||||
title: translate('chat_list.chats'),
|
||||
child: SizedBox.expand(
|
||||
child: (chatList.isEmpty)
|
||||
? const EmptyChatListWidget()
|
||||
: SearchableList<proto.Chat>(
|
||||
initialList: chatList.map((x) => x.value).toList(),
|
||||
itemBuilder: (c) {
|
||||
switch (c.whichKind()) {
|
||||
case proto.Chat_Kind.direct:
|
||||
return _itemBuilderDirect(
|
||||
c.direct,
|
||||
contactMap,
|
||||
contactListV.busy || chatListV.busy);
|
||||
case proto.Chat_Kind.group:
|
||||
return const Text(
|
||||
'group chats not yet supported!');
|
||||
case proto.Chat_Kind.notSet:
|
||||
throw StateError('unknown chat kind');
|
||||
}
|
||||
},
|
||||
filter: (value) =>
|
||||
_itemFilter(contactMap, chatList, value),
|
||||
spaceBetweenSearchAndList: 4,
|
||||
inputDecoration: InputDecoration(
|
||||
labelText: translate('chat_list.search'),
|
||||
),
|
||||
),
|
||||
).paddingAll(8))))
|
||||
.paddingLTRB(8, 0, 8, 8);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:searchable_listview/searchable_listview.dart';
|
||||
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
import '../chat_list.dart';
|
||||
|
||||
class ChatSingleContactListWidget extends StatelessWidget {
|
||||
const ChatSingleContactListWidget({super.key});
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final contactListV = context.watch<ContactListCubit>().state;
|
||||
|
||||
return contactListV.builder((context, contactList) {
|
||||
final contactMap = IMap.fromIterable(contactList,
|
||||
keyMapper: (c) => c.value.localConversationRecordKey,
|
||||
valueMapper: (c) => c.value);
|
||||
|
||||
final chatListV = context.watch<ChatListCubit>().state;
|
||||
return chatListV
|
||||
.builder((context, chatList) => SizedBox.expand(
|
||||
child: styledTitleContainer(
|
||||
context: context,
|
||||
title: translate('chat_list.chats'),
|
||||
child: SizedBox.expand(
|
||||
child: (chatList.isEmpty)
|
||||
? const EmptyChatListWidget()
|
||||
: SearchableList<proto.Chat>(
|
||||
initialList: chatList.map((x) => x.value).toList(),
|
||||
itemBuilder: (c) {
|
||||
final contact =
|
||||
contactMap[c.localConversationRecordKey];
|
||||
if (contact == null) {
|
||||
return const Text('...');
|
||||
}
|
||||
return ChatSingleContactItemWidget(
|
||||
contact: contact,
|
||||
disabled: contactListV.busy)
|
||||
.paddingLTRB(0, 4, 0, 0);
|
||||
},
|
||||
filter: (value) {
|
||||
final lowerValue = value.toLowerCase();
|
||||
return chatList.map((x) => x.value).where((c) {
|
||||
final contact =
|
||||
contactMap[c.localConversationRecordKey];
|
||||
if (contact == null) {
|
||||
return false;
|
||||
}
|
||||
return contact.nickname
|
||||
.toLowerCase()
|
||||
.contains(lowerValue) ||
|
||||
contact.profile.name
|
||||
.toLowerCase()
|
||||
.contains(lowerValue) ||
|
||||
contact.profile.pronouns
|
||||
.toLowerCase()
|
||||
.contains(lowerValue);
|
||||
}).toList();
|
||||
},
|
||||
spaceBetweenSearchAndList: 4,
|
||||
inputDecoration: InputDecoration(
|
||||
labelText: translate('chat_list.search'),
|
||||
),
|
||||
),
|
||||
).paddingAll(8))))
|
||||
.paddingLTRB(8, 0, 8, 8);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
export 'chat_list_widget.dart';
|
||||
export 'chat_single_contact_item_widget.dart';
|
||||
export 'chat_single_contact_list_widget.dart';
|
||||
export 'empty_chat_list_widget.dart';
|
||||
|
@ -59,8 +59,11 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
|
||||
// Verify
|
||||
final idcs = await contactSuperIdentity.currentInstance.cryptoSystem;
|
||||
final signature = signedContactResponse.identitySignature.toVeilid();
|
||||
await idcs.verify(contactSuperIdentity.currentInstance.publicKey,
|
||||
contactResponseBytes, signature);
|
||||
if (!await idcs.verify(contactSuperIdentity.currentInstance.publicKey,
|
||||
contactResponseBytes, signature)) {
|
||||
// Could not verify signature of contact response
|
||||
return AsyncValue.error('Invalid signature on contact response.');
|
||||
}
|
||||
|
||||
// Check for rejection
|
||||
if (!contactResponse.accept) {
|
||||
|
@ -3,6 +3,7 @@ import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import 'cubits.dart';
|
||||
|
||||
@ -20,13 +21,26 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
WaitingInvitationsBlocMapCubit(
|
||||
{required AccountInfo accountInfo,
|
||||
required AccountRecordCubit accountRecordCubit,
|
||||
required ContactInvitationListCubit contactInvitationListCubit})
|
||||
required ContactInvitationListCubit contactInvitationListCubit,
|
||||
required ContactListCubit contactListCubit})
|
||||
: _accountInfo = accountInfo,
|
||||
_accountRecordCubit = accountRecordCubit {
|
||||
_accountRecordCubit = accountRecordCubit,
|
||||
_contactInvitationListCubit = contactInvitationListCubit,
|
||||
_contactListCubit = contactListCubit {
|
||||
// React to invitation status changes
|
||||
_singleInvitationStatusProcessor.follow(
|
||||
stream, state, _invitationStatusListener);
|
||||
|
||||
// Follow the contact invitation list cubit
|
||||
follow(contactInvitationListCubit);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _singleInvitationStatusProcessor.unfollow();
|
||||
await super.close();
|
||||
}
|
||||
|
||||
Future<void> _addWaitingInvitation(
|
||||
{required proto.ContactInvitationRecord
|
||||
contactInvitationRecord}) async =>
|
||||
@ -40,16 +54,60 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
accountRecordCubit: _accountRecordCubit,
|
||||
contactInvitationRecord: contactInvitationRecord)));
|
||||
|
||||
// Process all accepted or rejected invitations
|
||||
Future<void> _invitationStatusListener(
|
||||
WaitingInvitationsBlocMapState newState) async {
|
||||
for (final entry in newState.entries) {
|
||||
final contactRequestInboxRecordKey = entry.key;
|
||||
final invStatus = entry.value.asData?.value;
|
||||
// Skip invitations that have not yet been accepted or rejected
|
||||
if (invStatus == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete invitation and process the accepted or rejected contact
|
||||
final acceptedContact = invStatus.acceptedContact;
|
||||
if (acceptedContact != null) {
|
||||
await _contactInvitationListCubit.deleteInvitation(
|
||||
accepted: true,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
|
||||
// Accept
|
||||
await _contactListCubit.createContact(
|
||||
profile: acceptedContact.remoteProfile,
|
||||
remoteSuperIdentity: acceptedContact.remoteIdentity,
|
||||
remoteConversationRecordKey:
|
||||
acceptedContact.remoteConversationRecordKey,
|
||||
localConversationRecordKey:
|
||||
acceptedContact.localConversationRecordKey,
|
||||
);
|
||||
} else {
|
||||
// Reject
|
||||
await _contactInvitationListCubit.deleteInvitation(
|
||||
accepted: false,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// StateFollower /////////////////////////
|
||||
|
||||
@override
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(TypedKey key, proto.ContactInvitationRecord value) =>
|
||||
_addWaitingInvitation(contactInvitationRecord: value);
|
||||
Future<void> updateState(
|
||||
TypedKey key,
|
||||
proto.ContactInvitationRecord? oldValue,
|
||||
proto.ContactInvitationRecord newValue) async {
|
||||
await _addWaitingInvitation(contactInvitationRecord: newValue);
|
||||
}
|
||||
|
||||
////
|
||||
final AccountInfo _accountInfo;
|
||||
final AccountRecordCubit _accountRecordCubit;
|
||||
final ContactInvitationListCubit _contactInvitationListCubit;
|
||||
final ContactListCubit _contactListCubit;
|
||||
final _singleInvitationStatusProcessor =
|
||||
SingleStateProcessor<WaitingInvitationsBlocMapState>();
|
||||
}
|
||||
|
@ -13,17 +13,27 @@ import '../conversation.dart';
|
||||
@immutable
|
||||
class ActiveConversationState extends Equatable {
|
||||
const ActiveConversationState({
|
||||
required this.contact,
|
||||
required this.remoteIdentityPublicKey,
|
||||
required this.localConversationRecordKey,
|
||||
required this.remoteConversationRecordKey,
|
||||
required this.localConversation,
|
||||
required this.remoteConversation,
|
||||
});
|
||||
|
||||
final proto.Contact contact;
|
||||
final TypedKey remoteIdentityPublicKey;
|
||||
final TypedKey localConversationRecordKey;
|
||||
final TypedKey remoteConversationRecordKey;
|
||||
final proto.Conversation localConversation;
|
||||
final proto.Conversation remoteConversation;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [contact, localConversation, remoteConversation];
|
||||
List<Object?> get props => [
|
||||
remoteIdentityPublicKey,
|
||||
localConversationRecordKey,
|
||||
remoteConversationRecordKey,
|
||||
localConversation,
|
||||
remoteConversation
|
||||
];
|
||||
}
|
||||
|
||||
typedef ActiveConversationCubit = TransformerCubit<
|
||||
@ -37,9 +47,11 @@ typedef ActiveConversationsBlocMapState
|
||||
// Map of localConversationRecordKey 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.
|
||||
//
|
||||
// TODO: Polling contacts for new inactive chats is yet to be done
|
||||
//
|
||||
class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
|
||||
with StateMapFollower<ChatListCubitState, TypedKey, proto.Chat> {
|
||||
@ -62,14 +74,11 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
// Private Implementation
|
||||
|
||||
// Add an active conversation to be tracked for changes
|
||||
Future<void> _addConversation({required proto.Contact contact}) async =>
|
||||
Future<void> _addDirectConversation(
|
||||
{required TypedKey remoteIdentityPublicKey,
|
||||
required TypedKey localConversationRecordKey,
|
||||
required TypedKey remoteConversationRecordKey}) async =>
|
||||
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(
|
||||
@ -105,14 +114,16 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
data.remoteConversation == null)
|
||||
? const AsyncValue.loading()
|
||||
: AsyncValue.data(ActiveConversationState(
|
||||
contact: contact,
|
||||
localConversation: data.localConversation!,
|
||||
remoteConversation: data.remoteConversation!)),
|
||||
remoteConversation: data.remoteConversation!,
|
||||
remoteIdentityPublicKey: remoteIdentityPublicKey,
|
||||
localConversationRecordKey: localConversationRecordKey,
|
||||
remoteConversationRecordKey:
|
||||
remoteConversationRecordKey)),
|
||||
loading: AsyncValue.loading,
|
||||
error: AsyncValue.error));
|
||||
|
||||
return MapEntry(
|
||||
contact.localConversationRecordKey.toVeilid(), transformedCubit);
|
||||
return MapEntry(localConversationRecordKey, transformedCubit);
|
||||
});
|
||||
|
||||
/// StateFollower /////////////////////////
|
||||
@ -121,20 +132,44 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(TypedKey key, proto.Chat value) async {
|
||||
final contactList = _contactListCubit.state.state.asData?.value;
|
||||
if (contactList == null) {
|
||||
await addState(key, const AsyncValue.loading());
|
||||
return;
|
||||
Future<void> updateState(
|
||||
TypedKey key, proto.Chat? oldValue, proto.Chat newValue) async {
|
||||
switch (newValue.whichKind()) {
|
||||
case proto.Chat_Kind.notSet:
|
||||
throw StateError('unknown chat kind');
|
||||
case proto.Chat_Kind.direct:
|
||||
final localConversationRecordKey =
|
||||
newValue.direct.localConversationRecordKey.toVeilid();
|
||||
final remoteIdentityPublicKey =
|
||||
newValue.direct.remoteMember.remoteIdentityPublicKey.toVeilid();
|
||||
final remoteConversationRecordKey =
|
||||
newValue.direct.remoteMember.remoteConversationRecordKey.toVeilid();
|
||||
|
||||
if (oldValue != null) {
|
||||
final oldLocalConversationRecordKey =
|
||||
oldValue.direct.localConversationRecordKey.toVeilid();
|
||||
final oldRemoteIdentityPublicKey =
|
||||
oldValue.direct.remoteMember.remoteIdentityPublicKey.toVeilid();
|
||||
final oldRemoteConversationRecordKey = oldValue
|
||||
.direct.remoteMember.remoteConversationRecordKey
|
||||
.toVeilid();
|
||||
|
||||
if (oldLocalConversationRecordKey == localConversationRecordKey &&
|
||||
oldRemoteIdentityPublicKey == remoteIdentityPublicKey &&
|
||||
oldRemoteConversationRecordKey == remoteConversationRecordKey) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await _addDirectConversation(
|
||||
remoteIdentityPublicKey: remoteIdentityPublicKey,
|
||||
localConversationRecordKey: localConversationRecordKey,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey);
|
||||
|
||||
break;
|
||||
case proto.Chat_Kind.group:
|
||||
break;
|
||||
}
|
||||
final contactIndex = contactList.indexWhere(
|
||||
(c) => c.value.localConversationRecordKey.toVeilid() == key);
|
||||
if (contactIndex == -1) {
|
||||
await addState(key, AsyncValue.error('Contact not found'));
|
||||
return;
|
||||
}
|
||||
final contact = contactList[contactIndex];
|
||||
await _addConversation(contact: contact.value);
|
||||
}
|
||||
|
||||
////
|
||||
|
@ -2,16 +2,42 @@ import 'dart:async';
|
||||
|
||||
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: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';
|
||||
|
||||
@immutable
|
||||
class _SingleContactChatState extends Equatable {
|
||||
const _SingleContactChatState(
|
||||
{required this.remoteIdentityPublicKey,
|
||||
required this.localConversationRecordKey,
|
||||
required this.remoteConversationRecordKey,
|
||||
required this.localMessagesRecordKey,
|
||||
required this.remoteMessagesRecordKey});
|
||||
|
||||
final TypedKey remoteIdentityPublicKey;
|
||||
final TypedKey localConversationRecordKey;
|
||||
final TypedKey remoteConversationRecordKey;
|
||||
final TypedKey localMessagesRecordKey;
|
||||
final TypedKey remoteMessagesRecordKey;
|
||||
|
||||
@override
|
||||
// TODO: implement props
|
||||
List<Object?> get props => [
|
||||
remoteIdentityPublicKey,
|
||||
localConversationRecordKey,
|
||||
remoteConversationRecordKey,
|
||||
localMessagesRecordKey,
|
||||
remoteMessagesRecordKey
|
||||
];
|
||||
}
|
||||
|
||||
// Map of localConversationRecordKey to MessagesCubit
|
||||
// Wraps a MessagesCubit to stream the latest messages to the state
|
||||
// Automatically follows the state of a ActiveConversationsBlocMapCubit.
|
||||
@ -20,36 +46,42 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
with
|
||||
StateMapFollower<ActiveConversationsBlocMapState, TypedKey,
|
||||
AsyncValue<ActiveConversationState>> {
|
||||
ActiveSingleContactChatBlocMapCubit(
|
||||
{required AccountInfo accountInfo,
|
||||
required ActiveConversationsBlocMapCubit activeConversationsBlocMapCubit,
|
||||
required ContactListCubit contactListCubit,
|
||||
required ChatListCubit chatListCubit})
|
||||
: _accountInfo = accountInfo,
|
||||
_contactListCubit = contactListCubit,
|
||||
_chatListCubit = chatListCubit {
|
||||
ActiveSingleContactChatBlocMapCubit({
|
||||
required AccountInfo accountInfo,
|
||||
required ActiveConversationsBlocMapCubit activeConversationsBlocMapCubit,
|
||||
}) : _accountInfo = accountInfo {
|
||||
// Follow the active conversations bloc map cubit
|
||||
follow(activeConversationsBlocMapCubit);
|
||||
}
|
||||
|
||||
Future<void> _addConversationMessages(
|
||||
{required proto.Contact contact,
|
||||
required proto.Chat chat,
|
||||
required proto.Conversation localConversation,
|
||||
required proto.Conversation remoteConversation}) async =>
|
||||
Future<void> _addConversationMessages(_SingleContactChatState state) async =>
|
||||
add(() => MapEntry(
|
||||
contact.localConversationRecordKey.toVeilid(),
|
||||
state.localConversationRecordKey,
|
||||
SingleContactMessagesCubit(
|
||||
accountInfo: _accountInfo,
|
||||
remoteIdentityPublicKey: contact.identityPublicKey.toVeilid(),
|
||||
localConversationRecordKey:
|
||||
contact.localConversationRecordKey.toVeilid(),
|
||||
remoteConversationRecordKey:
|
||||
contact.remoteConversationRecordKey.toVeilid(),
|
||||
localMessagesRecordKey: localConversation.messages.toVeilid(),
|
||||
remoteMessagesRecordKey: remoteConversation.messages.toVeilid(),
|
||||
remoteIdentityPublicKey: state.remoteIdentityPublicKey,
|
||||
localConversationRecordKey: state.localConversationRecordKey,
|
||||
remoteConversationRecordKey: state.remoteConversationRecordKey,
|
||||
localMessagesRecordKey: state.localMessagesRecordKey,
|
||||
remoteMessagesRecordKey: state.remoteMessagesRecordKey,
|
||||
)));
|
||||
|
||||
_SingleContactChatState? _mapStateValue(
|
||||
AsyncValue<ActiveConversationState> avInputState) {
|
||||
final inputState = avInputState.asData?.value;
|
||||
if (inputState == null) {
|
||||
return null;
|
||||
}
|
||||
return _SingleContactChatState(
|
||||
remoteIdentityPublicKey: inputState.remoteIdentityPublicKey,
|
||||
localConversationRecordKey: inputState.localConversationRecordKey,
|
||||
remoteConversationRecordKey: inputState.remoteConversationRecordKey,
|
||||
localMessagesRecordKey:
|
||||
inputState.localConversation.messages.toVeilid(),
|
||||
remoteMessagesRecordKey:
|
||||
inputState.remoteConversation.messages.toVeilid());
|
||||
}
|
||||
|
||||
/// StateFollower /////////////////////////
|
||||
|
||||
@override
|
||||
@ -57,49 +89,27 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
|
||||
@override
|
||||
Future<void> updateState(
|
||||
TypedKey key, AsyncValue<ActiveConversationState> value) async {
|
||||
// Get the contact object for this single contact chat
|
||||
final contactList = _contactListCubit.state.state.asData?.value;
|
||||
if (contactList == null) {
|
||||
TypedKey key,
|
||||
AsyncValue<ActiveConversationState>? oldValue,
|
||||
AsyncValue<ActiveConversationState> newValue) async {
|
||||
final newState = _mapStateValue(newValue);
|
||||
if (oldValue != null) {
|
||||
final oldState = _mapStateValue(oldValue);
|
||||
if (oldState == newState) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (newState != null) {
|
||||
await _addConversationMessages(newState);
|
||||
} else if (newValue.isLoading) {
|
||||
await addState(key, const AsyncValue.loading());
|
||||
return;
|
||||
} else {
|
||||
final (error, stackTrace) =
|
||||
(newValue.asError!.error, newValue.asError!.stackTrace);
|
||||
await addState(key, AsyncValue.error(error, stackTrace));
|
||||
}
|
||||
final contactIndex = contactList.indexWhere(
|
||||
(c) => c.value.localConversationRecordKey.toVeilid() == key);
|
||||
if (contactIndex == -1) {
|
||||
await addState(
|
||||
key, AsyncValue.error('Contact not found for conversation'));
|
||||
return;
|
||||
}
|
||||
final contact = contactList[contactIndex].value;
|
||||
|
||||
// Get the chat object for this single contact chat
|
||||
final chatList = _chatListCubit.state.state.asData?.value;
|
||||
if (chatList == null) {
|
||||
await addState(key, const AsyncValue.loading());
|
||||
return;
|
||||
}
|
||||
final chatIndex = chatList.indexWhere(
|
||||
(c) => c.value.localConversationRecordKey.toVeilid() == key);
|
||||
if (contactIndex == -1) {
|
||||
await addState(key, AsyncValue.error('Chat not found for conversation'));
|
||||
return;
|
||||
}
|
||||
final chat = chatList[chatIndex].value;
|
||||
|
||||
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 AccountInfo _accountInfo;
|
||||
final ContactListCubit _contactListCubit;
|
||||
final ChatListCubit _chatListCubit;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class ChatsPageState extends State<ChatsPage> {
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: <Widget>[
|
||||
const ChatSingleContactListWidget().expanded(),
|
||||
const ChatListWidget().expanded(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,12 @@ import 'dart:math';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../chat/chat.dart';
|
||||
import '../../contact_invitation/contact_invitation.dart';
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../theme/theme.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import 'active_account_page_controller_wrapper.dart';
|
||||
@ -39,48 +36,6 @@ class HomeScreenState extends State<HomeScreen> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Process all accepted or rejected invitations
|
||||
void _invitationStatusListener(
|
||||
BuildContext context, WaitingInvitationsBlocMapState state) {
|
||||
_singleInvitationStatusProcessor.updateState(state, (newState) async {
|
||||
final contactListCubit = context.read<ContactListCubit>();
|
||||
final contactInvitationListCubit =
|
||||
context.read<ContactInvitationListCubit>();
|
||||
|
||||
for (final entry in newState.entries) {
|
||||
final contactRequestInboxRecordKey = entry.key;
|
||||
final invStatus = entry.value.asData?.value;
|
||||
// Skip invitations that have not yet been accepted or rejected
|
||||
if (invStatus == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete invitation and process the accepted or rejected contact
|
||||
final acceptedContact = invStatus.acceptedContact;
|
||||
if (acceptedContact != null) {
|
||||
await contactInvitationListCubit.deleteInvitation(
|
||||
accepted: true,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
|
||||
// Accept
|
||||
await contactListCubit.createContact(
|
||||
profile: acceptedContact.remoteProfile,
|
||||
remoteSuperIdentity: acceptedContact.remoteIdentity,
|
||||
remoteConversationRecordKey:
|
||||
acceptedContact.remoteConversationRecordKey,
|
||||
localConversationRecordKey:
|
||||
acceptedContact.localConversationRecordKey,
|
||||
);
|
||||
} else {
|
||||
// Reject
|
||||
await contactInvitationListCubit.deleteInvitation(
|
||||
accepted: false,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildAccountReadyDeviceSpecific(BuildContext context) {
|
||||
final hasActiveChat = context.watch<ActiveChatCubit>().state != null;
|
||||
if (responsiveVisibility(
|
||||
@ -110,24 +65,18 @@ class HomeScreenState extends State<HomeScreen> {
|
||||
|
||||
// Re-export all ready blocs to the account display subtree
|
||||
return perAccountCollectionState.provide(
|
||||
child: MultiBlocListener(listeners: [
|
||||
BlocListener<WaitingInvitationsBlocMapCubit,
|
||||
WaitingInvitationsBlocMapState>(
|
||||
listener: _invitationStatusListener,
|
||||
)
|
||||
], child: Builder(builder: _buildAccountReadyDeviceSpecific)));
|
||||
child: Builder(builder: _buildAccountReadyDeviceSpecific));
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildAccountPageView(BuildContext context) {
|
||||
final localAccounts = context.watch<LocalAccountsCubit>().state;
|
||||
final activeLocalAccountCubit =
|
||||
context.watch<ActiveLocalAccountCubit>().state;
|
||||
final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state;
|
||||
final perAccountCollectionBlocMapState =
|
||||
context.watch<PerAccountCollectionBlocMapCubit>().state;
|
||||
|
||||
final activeIndex = localAccounts.indexWhere(
|
||||
(x) => x.superIdentity.recordKey == activeLocalAccountCubit);
|
||||
final activeIndex = localAccounts
|
||||
.indexWhere((x) => x.superIdentity.recordKey == activeLocalAccount);
|
||||
if (activeIndex == -1) {
|
||||
return const HomeNoActive();
|
||||
}
|
||||
@ -208,6 +157,4 @@ class HomeScreenState extends State<HomeScreen> {
|
||||
}
|
||||
|
||||
final _zoomDrawerController = ZoomDrawerController();
|
||||
final _singleInvitationStatusProcessor =
|
||||
SingleStateProcessor<WaitingInvitationsBlocMapState>();
|
||||
}
|
||||
|
@ -34,3 +34,16 @@ extension ContactExt on proto.Contact {
|
||||
String get displayName =>
|
||||
nickname.isNotEmpty ? '$nickname (${profile.name})' : profile.name;
|
||||
}
|
||||
|
||||
extension ChatExt on proto.Chat {
|
||||
TypedKey get localConversationRecordKey {
|
||||
switch (whichKind()) {
|
||||
case proto.Chat_Kind.direct:
|
||||
return direct.localConversationRecordKey.toVeilid();
|
||||
case proto.Chat_Kind.group:
|
||||
return group.localConversationRecordKey.toVeilid();
|
||||
case proto.Chat_Kind.notSet:
|
||||
throw StateError('unknown chat kind');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1255,16 +1255,15 @@ class Conversation extends $pb.GeneratedMessage {
|
||||
$0.TypedKey ensureMessages() => $_ensure(2);
|
||||
}
|
||||
|
||||
class Chat extends $pb.GeneratedMessage {
|
||||
factory Chat() => create();
|
||||
Chat._() : super();
|
||||
factory Chat.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory Chat.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
class ChatMember extends $pb.GeneratedMessage {
|
||||
factory ChatMember() => create();
|
||||
ChatMember._() : super();
|
||||
factory ChatMember.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ChatMember.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Chat', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
|
||||
..aOM<ChatSettings>(1, _omitFieldNames ? '' : 'settings', subBuilder: ChatSettings.create)
|
||||
..aOM<$0.TypedKey>(2, _omitFieldNames ? '' : 'localConversationRecordKey', subBuilder: $0.TypedKey.create)
|
||||
..aOM<$0.TypedKey>(3, _omitFieldNames ? '' : 'remoteConversationRecordKey', subBuilder: $0.TypedKey.create)
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ChatMember', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
|
||||
..aOM<$0.TypedKey>(1, _omitFieldNames ? '' : 'remoteIdentityPublicKey', subBuilder: $0.TypedKey.create)
|
||||
..aOM<$0.TypedKey>(2, _omitFieldNames ? '' : 'remoteConversationRecordKey', subBuilder: $0.TypedKey.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@ -1272,22 +1271,79 @@ class Chat extends $pb.GeneratedMessage {
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
Chat clone() => Chat()..mergeFromMessage(this);
|
||||
ChatMember clone() => ChatMember()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Chat copyWith(void Function(Chat) updates) => super.copyWith((message) => updates(message as Chat)) as Chat;
|
||||
ChatMember copyWith(void Function(ChatMember) updates) => super.copyWith((message) => updates(message as ChatMember)) as ChatMember;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Chat create() => Chat._();
|
||||
Chat createEmptyInstance() => create();
|
||||
static $pb.PbList<Chat> createRepeated() => $pb.PbList<Chat>();
|
||||
static ChatMember create() => ChatMember._();
|
||||
ChatMember createEmptyInstance() => create();
|
||||
static $pb.PbList<ChatMember> createRepeated() => $pb.PbList<ChatMember>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Chat getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Chat>(create);
|
||||
static Chat? _defaultInstance;
|
||||
static ChatMember getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ChatMember>(create);
|
||||
static ChatMember? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$0.TypedKey get remoteIdentityPublicKey => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set remoteIdentityPublicKey($0.TypedKey v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasRemoteIdentityPublicKey() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearRemoteIdentityPublicKey() => clearField(1);
|
||||
@$pb.TagNumber(1)
|
||||
$0.TypedKey ensureRemoteIdentityPublicKey() => $_ensure(0);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$0.TypedKey get remoteConversationRecordKey => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set remoteConversationRecordKey($0.TypedKey v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasRemoteConversationRecordKey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearRemoteConversationRecordKey() => clearField(2);
|
||||
@$pb.TagNumber(2)
|
||||
$0.TypedKey ensureRemoteConversationRecordKey() => $_ensure(1);
|
||||
}
|
||||
|
||||
class DirectChat extends $pb.GeneratedMessage {
|
||||
factory DirectChat() => create();
|
||||
DirectChat._() : super();
|
||||
factory DirectChat.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DirectChat.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DirectChat', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
|
||||
..aOM<ChatSettings>(1, _omitFieldNames ? '' : 'settings', subBuilder: ChatSettings.create)
|
||||
..aOM<$0.TypedKey>(2, _omitFieldNames ? '' : 'localConversationRecordKey', subBuilder: $0.TypedKey.create)
|
||||
..aOM<ChatMember>(3, _omitFieldNames ? '' : 'remoteMember', subBuilder: ChatMember.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DirectChat clone() => DirectChat()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DirectChat copyWith(void Function(DirectChat) updates) => super.copyWith((message) => updates(message as DirectChat)) as DirectChat;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DirectChat create() => DirectChat._();
|
||||
DirectChat createEmptyInstance() => create();
|
||||
static $pb.PbList<DirectChat> createRepeated() => $pb.PbList<DirectChat>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DirectChat getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DirectChat>(create);
|
||||
static DirectChat? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
ChatSettings get settings => $_getN(0);
|
||||
@ -1312,15 +1368,15 @@ class Chat extends $pb.GeneratedMessage {
|
||||
$0.TypedKey ensureLocalConversationRecordKey() => $_ensure(1);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$0.TypedKey get remoteConversationRecordKey => $_getN(2);
|
||||
ChatMember get remoteMember => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set remoteConversationRecordKey($0.TypedKey v) { setField(3, v); }
|
||||
set remoteMember(ChatMember v) { setField(3, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasRemoteConversationRecordKey() => $_has(2);
|
||||
$core.bool hasRemoteMember() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearRemoteConversationRecordKey() => clearField(3);
|
||||
void clearRemoteMember() => clearField(3);
|
||||
@$pb.TagNumber(3)
|
||||
$0.TypedKey ensureRemoteConversationRecordKey() => $_ensure(2);
|
||||
ChatMember ensureRemoteMember() => $_ensure(2);
|
||||
}
|
||||
|
||||
class GroupChat extends $pb.GeneratedMessage {
|
||||
@ -1331,8 +1387,10 @@ class GroupChat extends $pb.GeneratedMessage {
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'GroupChat', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
|
||||
..aOM<ChatSettings>(1, _omitFieldNames ? '' : 'settings', subBuilder: ChatSettings.create)
|
||||
..aOM<$0.TypedKey>(2, _omitFieldNames ? '' : 'localConversationRecordKey', subBuilder: $0.TypedKey.create)
|
||||
..pc<$0.TypedKey>(3, _omitFieldNames ? '' : 'remoteConversationRecordKeys', $pb.PbFieldType.PM, subBuilder: $0.TypedKey.create)
|
||||
..aOM<Membership>(2, _omitFieldNames ? '' : 'membership', subBuilder: Membership.create)
|
||||
..aOM<Permissions>(3, _omitFieldNames ? '' : 'permissions', subBuilder: Permissions.create)
|
||||
..aOM<$0.TypedKey>(4, _omitFieldNames ? '' : 'localConversationRecordKey', subBuilder: $0.TypedKey.create)
|
||||
..pc<ChatMember>(5, _omitFieldNames ? '' : 'remoteMembers', $pb.PbFieldType.PM, subBuilder: ChatMember.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@ -1369,18 +1427,111 @@ class GroupChat extends $pb.GeneratedMessage {
|
||||
ChatSettings ensureSettings() => $_ensure(0);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$0.TypedKey get localConversationRecordKey => $_getN(1);
|
||||
Membership get membership => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set localConversationRecordKey($0.TypedKey v) { setField(2, v); }
|
||||
set membership(Membership v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasLocalConversationRecordKey() => $_has(1);
|
||||
$core.bool hasMembership() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearLocalConversationRecordKey() => clearField(2);
|
||||
void clearMembership() => clearField(2);
|
||||
@$pb.TagNumber(2)
|
||||
$0.TypedKey ensureLocalConversationRecordKey() => $_ensure(1);
|
||||
Membership ensureMembership() => $_ensure(1);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$0.TypedKey> get remoteConversationRecordKeys => $_getList(2);
|
||||
Permissions get permissions => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set permissions(Permissions v) { setField(3, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasPermissions() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearPermissions() => clearField(3);
|
||||
@$pb.TagNumber(3)
|
||||
Permissions ensurePermissions() => $_ensure(2);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$0.TypedKey get localConversationRecordKey => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set localConversationRecordKey($0.TypedKey v) { setField(4, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasLocalConversationRecordKey() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearLocalConversationRecordKey() => clearField(4);
|
||||
@$pb.TagNumber(4)
|
||||
$0.TypedKey ensureLocalConversationRecordKey() => $_ensure(3);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<ChatMember> get remoteMembers => $_getList(4);
|
||||
}
|
||||
|
||||
enum Chat_Kind {
|
||||
direct,
|
||||
group,
|
||||
notSet
|
||||
}
|
||||
|
||||
class Chat extends $pb.GeneratedMessage {
|
||||
factory Chat() => create();
|
||||
Chat._() : super();
|
||||
factory Chat.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory Chat.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static const $core.Map<$core.int, Chat_Kind> _Chat_KindByTag = {
|
||||
1 : Chat_Kind.direct,
|
||||
2 : Chat_Kind.group,
|
||||
0 : Chat_Kind.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Chat', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
|
||||
..oo(0, [1, 2])
|
||||
..aOM<DirectChat>(1, _omitFieldNames ? '' : 'direct', subBuilder: DirectChat.create)
|
||||
..aOM<GroupChat>(2, _omitFieldNames ? '' : 'group', subBuilder: GroupChat.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
Chat clone() => Chat()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Chat copyWith(void Function(Chat) updates) => super.copyWith((message) => updates(message as Chat)) as Chat;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Chat create() => Chat._();
|
||||
Chat createEmptyInstance() => create();
|
||||
static $pb.PbList<Chat> createRepeated() => $pb.PbList<Chat>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Chat getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Chat>(create);
|
||||
static Chat? _defaultInstance;
|
||||
|
||||
Chat_Kind whichKind() => _Chat_KindByTag[$_whichOneof(0)]!;
|
||||
void clearKind() => clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
DirectChat get direct => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set direct(DirectChat v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasDirect() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearDirect() => clearField(1);
|
||||
@$pb.TagNumber(1)
|
||||
DirectChat ensureDirect() => $_ensure(0);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
GroupChat get group => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set group(GroupChat v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasGroup() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearGroup() => clearField(2);
|
||||
@$pb.TagNumber(2)
|
||||
GroupChat ensureGroup() => $_ensure(1);
|
||||
}
|
||||
|
||||
class Profile extends $pb.GeneratedMessage {
|
||||
|
@ -365,41 +365,76 @@ final $typed_data.Uint8List conversationDescriptor = $convert.base64Decode(
|
||||
'JvZmlsZRIuChNzdXBlcl9pZGVudGl0eV9qc29uGAIgASgJUhFzdXBlcklkZW50aXR5SnNvbhIs'
|
||||
'CghtZXNzYWdlcxgDIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIIbWVzc2FnZXM=');
|
||||
|
||||
@$core.Deprecated('Use chatDescriptor instead')
|
||||
const Chat$json = {
|
||||
'1': 'Chat',
|
||||
@$core.Deprecated('Use chatMemberDescriptor instead')
|
||||
const ChatMember$json = {
|
||||
'1': 'ChatMember',
|
||||
'2': [
|
||||
{'1': 'settings', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.ChatSettings', '10': 'settings'},
|
||||
{'1': 'local_conversation_record_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'localConversationRecordKey'},
|
||||
{'1': 'remote_conversation_record_key', '3': 3, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteConversationRecordKey'},
|
||||
{'1': 'remote_identity_public_key', '3': 1, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteIdentityPublicKey'},
|
||||
{'1': 'remote_conversation_record_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteConversationRecordKey'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Chat`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List chatDescriptor = $convert.base64Decode(
|
||||
'CgRDaGF0EjQKCHNldHRpbmdzGAEgASgLMhgudmVpbGlkY2hhdC5DaGF0U2V0dGluZ3NSCHNldH'
|
||||
'RpbmdzElMKHWxvY2FsX2NvbnZlcnNhdGlvbl9yZWNvcmRfa2V5GAIgASgLMhAudmVpbGlkLlR5'
|
||||
'cGVkS2V5Uhpsb2NhbENvbnZlcnNhdGlvblJlY29yZEtleRJVCh5yZW1vdGVfY29udmVyc2F0aW'
|
||||
'9uX3JlY29yZF9rZXkYAyABKAsyEC52ZWlsaWQuVHlwZWRLZXlSG3JlbW90ZUNvbnZlcnNhdGlv'
|
||||
'blJlY29yZEtleQ==');
|
||||
/// Descriptor for `ChatMember`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List chatMemberDescriptor = $convert.base64Decode(
|
||||
'CgpDaGF0TWVtYmVyEk0KGnJlbW90ZV9pZGVudGl0eV9wdWJsaWNfa2V5GAEgASgLMhAudmVpbG'
|
||||
'lkLlR5cGVkS2V5UhdyZW1vdGVJZGVudGl0eVB1YmxpY0tleRJVCh5yZW1vdGVfY29udmVyc2F0'
|
||||
'aW9uX3JlY29yZF9rZXkYAiABKAsyEC52ZWlsaWQuVHlwZWRLZXlSG3JlbW90ZUNvbnZlcnNhdG'
|
||||
'lvblJlY29yZEtleQ==');
|
||||
|
||||
@$core.Deprecated('Use directChatDescriptor instead')
|
||||
const DirectChat$json = {
|
||||
'1': 'DirectChat',
|
||||
'2': [
|
||||
{'1': 'settings', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.ChatSettings', '10': 'settings'},
|
||||
{'1': 'local_conversation_record_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'localConversationRecordKey'},
|
||||
{'1': 'remote_member', '3': 3, '4': 1, '5': 11, '6': '.veilidchat.ChatMember', '10': 'remoteMember'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `DirectChat`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List directChatDescriptor = $convert.base64Decode(
|
||||
'CgpEaXJlY3RDaGF0EjQKCHNldHRpbmdzGAEgASgLMhgudmVpbGlkY2hhdC5DaGF0U2V0dGluZ3'
|
||||
'NSCHNldHRpbmdzElMKHWxvY2FsX2NvbnZlcnNhdGlvbl9yZWNvcmRfa2V5GAIgASgLMhAudmVp'
|
||||
'bGlkLlR5cGVkS2V5Uhpsb2NhbENvbnZlcnNhdGlvblJlY29yZEtleRI7Cg1yZW1vdGVfbWVtYm'
|
||||
'VyGAMgASgLMhYudmVpbGlkY2hhdC5DaGF0TWVtYmVyUgxyZW1vdGVNZW1iZXI=');
|
||||
|
||||
@$core.Deprecated('Use groupChatDescriptor instead')
|
||||
const GroupChat$json = {
|
||||
'1': 'GroupChat',
|
||||
'2': [
|
||||
{'1': 'settings', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.ChatSettings', '10': 'settings'},
|
||||
{'1': 'local_conversation_record_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'localConversationRecordKey'},
|
||||
{'1': 'remote_conversation_record_keys', '3': 3, '4': 3, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteConversationRecordKeys'},
|
||||
{'1': 'membership', '3': 2, '4': 1, '5': 11, '6': '.veilidchat.Membership', '10': 'membership'},
|
||||
{'1': 'permissions', '3': 3, '4': 1, '5': 11, '6': '.veilidchat.Permissions', '10': 'permissions'},
|
||||
{'1': 'local_conversation_record_key', '3': 4, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'localConversationRecordKey'},
|
||||
{'1': 'remote_members', '3': 5, '4': 3, '5': 11, '6': '.veilidchat.ChatMember', '10': 'remoteMembers'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `GroupChat`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List groupChatDescriptor = $convert.base64Decode(
|
||||
'CglHcm91cENoYXQSNAoIc2V0dGluZ3MYASABKAsyGC52ZWlsaWRjaGF0LkNoYXRTZXR0aW5nc1'
|
||||
'IIc2V0dGluZ3MSUwodbG9jYWxfY29udmVyc2F0aW9uX3JlY29yZF9rZXkYAiABKAsyEC52ZWls'
|
||||
'aWQuVHlwZWRLZXlSGmxvY2FsQ29udmVyc2F0aW9uUmVjb3JkS2V5ElcKH3JlbW90ZV9jb252ZX'
|
||||
'JzYXRpb25fcmVjb3JkX2tleXMYAyADKAsyEC52ZWlsaWQuVHlwZWRLZXlSHHJlbW90ZUNvbnZl'
|
||||
'cnNhdGlvblJlY29yZEtleXM=');
|
||||
'IIc2V0dGluZ3MSNgoKbWVtYmVyc2hpcBgCIAEoCzIWLnZlaWxpZGNoYXQuTWVtYmVyc2hpcFIK'
|
||||
'bWVtYmVyc2hpcBI5CgtwZXJtaXNzaW9ucxgDIAEoCzIXLnZlaWxpZGNoYXQuUGVybWlzc2lvbn'
|
||||
'NSC3Blcm1pc3Npb25zElMKHWxvY2FsX2NvbnZlcnNhdGlvbl9yZWNvcmRfa2V5GAQgASgLMhAu'
|
||||
'dmVpbGlkLlR5cGVkS2V5Uhpsb2NhbENvbnZlcnNhdGlvblJlY29yZEtleRI9Cg5yZW1vdGVfbW'
|
||||
'VtYmVycxgFIAMoCzIWLnZlaWxpZGNoYXQuQ2hhdE1lbWJlclINcmVtb3RlTWVtYmVycw==');
|
||||
|
||||
@$core.Deprecated('Use chatDescriptor instead')
|
||||
const Chat$json = {
|
||||
'1': 'Chat',
|
||||
'2': [
|
||||
{'1': 'direct', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.DirectChat', '9': 0, '10': 'direct'},
|
||||
{'1': 'group', '3': 2, '4': 1, '5': 11, '6': '.veilidchat.GroupChat', '9': 0, '10': 'group'},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'kind'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Chat`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List chatDescriptor = $convert.base64Decode(
|
||||
'CgRDaGF0EjAKBmRpcmVjdBgBIAEoCzIWLnZlaWxpZGNoYXQuRGlyZWN0Q2hhdEgAUgZkaXJlY3'
|
||||
'QSLQoFZ3JvdXAYAiABKAsyFS52ZWlsaWRjaGF0Lkdyb3VwQ2hhdEgAUgVncm91cEIGCgRraW5k');
|
||||
|
||||
@$core.Deprecated('Use profileDescriptor instead')
|
||||
const Profile$json = {
|
||||
|
@ -267,15 +267,23 @@ message Conversation {
|
||||
veilid.TypedKey messages = 3;
|
||||
}
|
||||
|
||||
// Either a 1-1 conversation or a group chat
|
||||
// A member of chat which may or may not be associated with a contact
|
||||
message ChatMember {
|
||||
// The identity public key most recently associated with the chat member
|
||||
veilid.TypedKey remote_identity_public_key = 1;
|
||||
// Conversation key for the other party
|
||||
veilid.TypedKey remote_conversation_record_key = 2;
|
||||
}
|
||||
|
||||
// A 1-1 chat
|
||||
// Privately encrypted, this is the local user's copy of the chat
|
||||
message Chat {
|
||||
message DirectChat {
|
||||
// Settings
|
||||
ChatSettings settings = 1;
|
||||
// Conversation key for this user
|
||||
veilid.TypedKey local_conversation_record_key = 2;
|
||||
// Conversation key for the other party
|
||||
veilid.TypedKey remote_conversation_record_key = 3;
|
||||
ChatMember remote_member = 3;
|
||||
}
|
||||
|
||||
// A group chat
|
||||
@ -283,10 +291,22 @@ message Chat {
|
||||
message GroupChat {
|
||||
// Settings
|
||||
ChatSettings settings = 1;
|
||||
// Membership
|
||||
Membership membership = 2;
|
||||
// Permissions
|
||||
Permissions permissions = 3;
|
||||
// Conversation key for this user
|
||||
veilid.TypedKey local_conversation_record_key = 2;
|
||||
veilid.TypedKey local_conversation_record_key = 4;
|
||||
// Conversation keys for the other parties
|
||||
repeated veilid.TypedKey remote_conversation_record_keys = 3;
|
||||
repeated ChatMember remote_members = 5;
|
||||
}
|
||||
|
||||
// Some kind of chat
|
||||
message Chat {
|
||||
oneof kind {
|
||||
DirectChat direct = 1;
|
||||
GroupChat group = 2;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -609,7 +609,7 @@ class _DHTLogSpine {
|
||||
// Don't watch for local changes because this class already handles
|
||||
// notifying listeners and knows when it makes local changes
|
||||
_subscription ??=
|
||||
await _spineRecord.listen(localChanges: false, _onSpineChanged);
|
||||
await _spineRecord.listen(localChanges: true, _onSpineChanged);
|
||||
} on Exception {
|
||||
// If anything fails, try to cancel the watches
|
||||
await cancelWatch();
|
||||
|
@ -12,14 +12,6 @@ class DefaultDHTRecordCubit<T> extends DHTRecordCubit<T> {
|
||||
stateFunction: _makeStateFunction(decodeState),
|
||||
watchFunction: _makeWatchFunction());
|
||||
|
||||
// DefaultDHTRecordCubit.value({
|
||||
// required super.record,
|
||||
// required T Function(List<int> data) decodeState,
|
||||
// }) : super.value(
|
||||
// initialStateFunction: _makeInitialStateFunction(decodeState),
|
||||
// stateFunction: _makeStateFunction(decodeState),
|
||||
// watchFunction: _makeWatchFunction());
|
||||
|
||||
static InitialStateFunction<T> _makeInitialStateFunction<T>(
|
||||
T Function(List<int> data) decodeState) =>
|
||||
(record) async {
|
||||
|
@ -29,20 +29,6 @@ class DHTRecordCubit<T> extends Cubit<AsyncValue<T>> {
|
||||
});
|
||||
}
|
||||
|
||||
// DHTRecordCubit.value({
|
||||
// required DHTRecord record,
|
||||
// required InitialStateFunction<T> initialStateFunction,
|
||||
// required StateFunction<T> stateFunction,
|
||||
// required WatchFunction watchFunction,
|
||||
// }) : _record = record,
|
||||
// _stateFunction = stateFunction,
|
||||
// _wantsCloseRecord = false,
|
||||
// super(const AsyncValue.loading()) {
|
||||
// Future.delayed(Duration.zero, () async {
|
||||
// await _init(initialStateFunction, stateFunction, watchFunction);
|
||||
// });
|
||||
// }
|
||||
|
||||
Future<void> _init(
|
||||
InitialStateFunction<T> initialStateFunction,
|
||||
StateFunction<T> stateFunction,
|
||||
|
Loading…
Reference in New Issue
Block a user