mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-01-11 07:39:32 -05:00
more refactor, conversation work
This commit is contained in:
parent
20047a956f
commit
7bd426ce98
@ -1,2 +1,3 @@
|
||||
export 'repository/contact_invitation_repository.dart';
|
||||
export 'cubits/cubits.dart';
|
||||
export 'models/models.dart';
|
||||
export 'views/views.dart';
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.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 '../../tools/tools.dart';
|
||||
import '../models/models.dart';
|
||||
@ -33,19 +33,19 @@ class InvitationStatus {
|
||||
//////////////////////////////////////////////////
|
||||
// Mutable state for per-account contact invitations
|
||||
|
||||
class ContactInvitationListCubit extends DHTShortArrayCubit<proto.ContactInvitation> {
|
||||
class ContactInvitationListCubit
|
||||
extends DHTShortArrayCubit<proto.ContactInvitationRecord> {
|
||||
ContactInvitationListCubit({
|
||||
required ActiveAccountInfo activeAccountInfo,
|
||||
required proto.Account account,
|
||||
required DHTShortArray dhtRecord,
|
||||
}) : _activeAccountInfo = activeAccountInfo,
|
||||
_account = account,
|
||||
_dhtRecord = dhtRecord,
|
||||
super(shortArray: dhtRecord, decodeElement: proto.ContactInvitation.fromBuffer);
|
||||
xxx convert the rest of this to cubit
|
||||
static Future<ContactInvitationRepository> open(
|
||||
ActiveAccountInfo activeAccountInfo, proto.Account account) async {
|
||||
super(
|
||||
open: () => _open(activeAccountInfo, account),
|
||||
decodeElement: proto.ContactInvitationRecord.fromBuffer);
|
||||
|
||||
static Future<DHTShortArray> _open(
|
||||
ActiveAccountInfo activeAccountInfo, proto.Account account) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
@ -57,28 +57,9 @@ xxx convert the rest of this to cubit
|
||||
contactInvitationListRecordKey,
|
||||
parent: accountRecordKey);
|
||||
|
||||
return ContactInvitationRepository._(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
account: account,
|
||||
dhtRecord: dhtRecord);
|
||||
return dhtRecord;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _dhtRecord.close();
|
||||
await super.close();
|
||||
}
|
||||
|
||||
// Future<void> refresh() async {
|
||||
// for (var i = 0; i < _dhtRecord.length; i++) {
|
||||
// final cir = await _dhtRecord.getItem(i);
|
||||
// if (cir == null) {
|
||||
// throw Exception('Failed to get contact invitation record');
|
||||
// }
|
||||
// _records = _records.add(proto.ContactInvitationRecord.fromBuffer(cir));
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<Uint8List> createInvitation(
|
||||
{required EncryptionKeyType encryptionKeyType,
|
||||
required String encryptionKey,
|
||||
@ -98,7 +79,8 @@ xxx convert the rest of this to cubit
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
// Create local chat DHT record with the account record key as its parent
|
||||
// Create local conversation DHT record with the account record key as its
|
||||
// parent.
|
||||
// Do not set the encryption of this key yet as it will not yet be written
|
||||
// to and it will be eventually encrypted with the DH of the contact's
|
||||
// identity key
|
||||
@ -163,7 +145,7 @@ xxx convert the rest of this to cubit
|
||||
|
||||
// Add ContactInvitationRecord to account's list
|
||||
// if this fails, don't keep retrying, user can try again later
|
||||
if (await _dhtRecord.tryAddItem(cinvrec.writeToBuffer()) == false) {
|
||||
if (await shortArray.tryAddItem(cinvrec.writeToBuffer()) == false) {
|
||||
throw Exception('Failed to add contact invitation record');
|
||||
}
|
||||
});
|
||||
@ -180,15 +162,15 @@ xxx convert the rest of this to cubit
|
||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Remove ContactInvitationRecord from account's list
|
||||
for (var i = 0; i < _dhtRecord.length; i++) {
|
||||
final item = await _dhtRecord.getItemProtobuf(
|
||||
for (var i = 0; i < shortArray.length; i++) {
|
||||
final item = await shortArray.getItemProtobuf(
|
||||
proto.ContactInvitationRecord.fromBuffer, i);
|
||||
if (item == null) {
|
||||
throw Exception('Failed to get contact invitation record');
|
||||
}
|
||||
if (item.contactRequestInbox.recordKey ==
|
||||
contactInvitationRecord.contactRequestInbox.recordKey) {
|
||||
await _dhtRecord.tryRemoveItem(i);
|
||||
await shortArray.tryRemoveItem(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -229,11 +211,11 @@ xxx convert the rest of this to cubit
|
||||
|
||||
final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind);
|
||||
|
||||
// See if we're chatting to ourselves, if so, don't delete it here
|
||||
final isSelf = _contactIdentityMaster.identityPublicKey ==
|
||||
_activeAccountInfo.localAccount.identityMaster.identityPublicKey;
|
||||
xxx this doesn't work and the upper one doesnt either
|
||||
final isSelf = _records.indexWhere((cir) =>
|
||||
// Compare the invitation's contact request
|
||||
// inbox with our list of extant invitations
|
||||
// If we're chatting to ourselves,
|
||||
// we are validating an invitation we have created
|
||||
final isSelf = state.data!.value.indexWhere((cir) =>
|
||||
proto.TypedKeyProto.fromProto(cir.contactRequestInbox.recordKey) ==
|
||||
contactRequestInboxKey) !=
|
||||
-1;
|
||||
@ -283,10 +265,7 @@ xxx this doesn't work and the upper one doesnt either
|
||||
|
||||
out = ValidContactInvitation(
|
||||
activeAccountInfo: _activeAccountInfo,
|
||||
signedContactInvitation: signedContactInvitation,
|
||||
contactInvitation: contactInvitation,
|
||||
contactRequestInboxKey: contactRequestInboxKey,
|
||||
contactRequest: contactRequest,
|
||||
contactRequestPrivate: contactRequestPrivate,
|
||||
contactIdentityMaster: contactIdentityMaster,
|
||||
writer: writer);
|
||||
@ -296,13 +275,12 @@ xxx this doesn't work and the upper one doesnt either
|
||||
}
|
||||
|
||||
Future<InvitationStatus?> checkInvitationStatus(
|
||||
{required ActiveAccountInfo activeAccountInfo,
|
||||
required proto.ContactInvitationRecord contactInvitationRecord}) async {
|
||||
{required proto.ContactInvitationRecord contactInvitationRecord}) async {
|
||||
// Open the contact request inbox
|
||||
try {
|
||||
final pool = DHTRecordPool.instance;
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
final accountRecordKey = _activeAccountInfo
|
||||
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
final writerKey =
|
||||
proto.CryptoKeyProto.fromProto(contactInvitationRecord.writerKey);
|
||||
final writerSecret =
|
||||
@ -350,11 +328,14 @@ xxx this doesn't work and the upper one doesnt either
|
||||
// Pull profile from remote conversation key
|
||||
final remoteConversationRecordKey = proto.TypedKeyProto.fromProto(
|
||||
contactResponse.remoteConversationRecordKey);
|
||||
final remoteConversation = await readRemoteConversation(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
|
||||
final conversation = ConversationManager(
|
||||
activeAccountInfo: _activeAccountInfo,
|
||||
remoteIdentityPublicKey:
|
||||
contactIdentityMaster.identityPublicTypedKey(),
|
||||
remoteConversationRecordKey: remoteConversationRecordKey);
|
||||
|
||||
final remoteConversation = await conversation.readRemoteConversation();
|
||||
if (remoteConversation == null) {
|
||||
log.info('Remote conversation could not be read. Waiting...');
|
||||
return null;
|
||||
@ -362,11 +343,9 @@ xxx this doesn't work and the upper one doesnt either
|
||||
// Complete the local conversation now that we have the remote profile
|
||||
final localConversationRecordKey = proto.TypedKeyProto.fromProto(
|
||||
contactInvitationRecord.localConversationRecordKey);
|
||||
return createConversation(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
remoteIdentityPublicKey:
|
||||
contactIdentityMaster.identityPublicTypedKey(),
|
||||
return conversation.initLocalConversation(
|
||||
existingConversationRecordKey: localConversationRecordKey,
|
||||
profile: xxx LOCAL PROFILE HERE NOT REMOTE
|
||||
// ignore: prefer_expression_function_bodies
|
||||
callback: (localConversation) async {
|
||||
return InvitationStatus(
|
||||
@ -400,9 +379,6 @@ xxx this doesn't work and the upper one doesnt either
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
final ActiveAccountInfo _activeAccountInfo;
|
||||
final proto.Account _account;
|
||||
final DHTShortArray _dhtRecord;
|
||||
//IList<proto.ContactInvitationRecord> _records;
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
export 'accepted_contact.dart';
|
||||
export '../repository/valid_contact_invitation.dart';
|
||||
export 'valid_contact_invitation.dart';
|
||||
|
@ -4,8 +4,8 @@ import 'package:veilid_support/veilid_support.dart';
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
import '../models/models.dart';
|
||||
import 'contact_invitation_repository.dart';
|
||||
import 'models.dart';
|
||||
import '../cubits/contact_invitation_list_cubit.dart';
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
///
|
||||
@ -14,18 +14,12 @@ class ValidContactInvitation {
|
||||
@internal
|
||||
ValidContactInvitation(
|
||||
{required ActiveAccountInfo activeAccountInfo,
|
||||
required proto.SignedContactInvitation signedContactInvitation,
|
||||
required proto.ContactInvitation contactInvitation,
|
||||
required TypedKey contactRequestInboxKey,
|
||||
required proto.ContactRequest contactRequest,
|
||||
required proto.ContactRequestPrivate contactRequestPrivate,
|
||||
required IdentityMaster contactIdentityMaster,
|
||||
required KeyPair writer})
|
||||
: _activeAccountInfo = activeAccountInfo,
|
||||
_signedContactInvitation = signedContactInvitation,
|
||||
_contactInvitation = contactInvitation,
|
||||
_contactRequestInboxKey = contactRequestInboxKey,
|
||||
_contactRequest = contactRequest,
|
||||
_contactRequestPrivate = contactRequestPrivate,
|
||||
_contactIdentityMaster = contactIdentityMaster,
|
||||
_writer = writer;
|
||||
@ -140,8 +134,5 @@ class ValidContactInvitation {
|
||||
final TypedKey _contactRequestInboxKey;
|
||||
final IdentityMaster _contactIdentityMaster;
|
||||
final KeyPair _writer;
|
||||
proto.SignedContactInvitation _signedContactInvitation;
|
||||
proto.ContactInvitation _contactInvitation;
|
||||
proto.ContactRequest _contactRequest;
|
||||
proto.ContactRequestPrivate _contactRequestPrivate;
|
||||
final proto.ContactRequestPrivate _contactRequestPrivate;
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
export 'models/models.dart';
|
||||
export 'views/views.dart';
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Each Contact in the ContactList has at most one Conversation between the
|
||||
// remote contact and the local account
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
@ -11,32 +12,86 @@ import '../../account_manager/account_manager.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
import 'chat.dart';
|
||||
|
||||
class Conversation {
|
||||
Conversation._(
|
||||
class ConversationManager {
|
||||
ConversationManager(
|
||||
{required ActiveAccountInfo activeAccountInfo,
|
||||
required TypedKey localConversationRecordKey,
|
||||
required TypedKey remoteIdentityPublicKey,
|
||||
required TypedKey remoteConversationRecordKey})
|
||||
TypedKey? localConversationRecordKey,
|
||||
TypedKey? remoteConversationRecordKey})
|
||||
: _activeAccountInfo = activeAccountInfo,
|
||||
_localConversationRecordKey = localConversationRecordKey,
|
||||
_remoteIdentityPublicKey = remoteIdentityPublicKey,
|
||||
_remoteConversationRecordKey = remoteConversationRecordKey;
|
||||
|
||||
Future<Conversation> open() async {}
|
||||
// Initialize a local conversation
|
||||
// If we were the initiator of the conversation there may be an
|
||||
// incomplete 'existingConversationRecord' that we need to fill
|
||||
// in now that we have the remote identity key
|
||||
Future<T> initLocalConversation<T>(
|
||||
{required proto.Profile profile,
|
||||
required FutureOr<T> Function(DHTRecord) callback,
|
||||
TypedKey? existingConversationRecordKey}) async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
final accountRecordKey =
|
||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
Future<void> close() async {
|
||||
//
|
||||
final crypto = await getConversationCrypto();
|
||||
final writer = _activeAccountInfo.conversationWriter;
|
||||
|
||||
// Open with SMPL scheme for identity writer
|
||||
late final DHTRecord localConversationRecord;
|
||||
if (existingConversationRecordKey != null) {
|
||||
localConversationRecord = await pool.openWrite(
|
||||
existingConversationRecordKey, writer,
|
||||
parent: accountRecordKey, crypto: crypto);
|
||||
} else {
|
||||
final localConversationRecordCreate = await pool.create(
|
||||
parent: accountRecordKey,
|
||||
crypto: crypto,
|
||||
schema: DHTSchema.smpl(
|
||||
oCnt: 0, members: [DHTSchemaMember(mKey: writer.key, mCnt: 1)]));
|
||||
await localConversationRecordCreate.close();
|
||||
localConversationRecord = await pool.openWrite(
|
||||
localConversationRecordCreate.key, writer,
|
||||
parent: accountRecordKey, crypto: crypto);
|
||||
}
|
||||
final out = localConversationRecord
|
||||
// ignore: prefer_expression_function_bodies
|
||||
.deleteScope((localConversation) async {
|
||||
// Make messages log
|
||||
return (await DHTShortArray.create(
|
||||
parent: localConversation.key,
|
||||
crypto: crypto,
|
||||
smplWriter: writer))
|
||||
.deleteScope((messages) async {
|
||||
// Write local conversation key
|
||||
final conversation = proto.Conversation()
|
||||
..profile = profile
|
||||
..identityMasterJson = jsonEncode(
|
||||
_activeAccountInfo.localAccount.identityMaster.toJson())
|
||||
..messages = messages.record.key.toProto();
|
||||
|
||||
//
|
||||
final update = await localConversation.tryWriteProtobuf(
|
||||
proto.Conversation.fromBuffer, conversation);
|
||||
if (update != null) {
|
||||
throw Exception('Failed to write local conversation');
|
||||
}
|
||||
return await callback(localConversation);
|
||||
});
|
||||
});
|
||||
// If success, save the new local conversation record key in this object
|
||||
_localConversationRecordKey = localConversationRecord.key;
|
||||
return out;
|
||||
}
|
||||
|
||||
Future<proto.Conversation?> readRemoteConversation() async {
|
||||
final accountRecordKey =
|
||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
final pool = await DHTRecordPool.instance();
|
||||
final pool = DHTRecordPool.instance;
|
||||
|
||||
final crypto = await getConversationCrypto();
|
||||
return (await pool.openRead(_remoteConversationRecordKey,
|
||||
return (await pool.openRead(_remoteConversationRecordKey!,
|
||||
parent: accountRecordKey, crypto: crypto))
|
||||
.scope((remoteConversation) async {
|
||||
//
|
||||
@ -49,10 +104,10 @@ class Conversation {
|
||||
Future<proto.Conversation?> readLocalConversation() async {
|
||||
final accountRecordKey =
|
||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
final pool = await DHTRecordPool.instance();
|
||||
final pool = DHTRecordPool.instance;
|
||||
|
||||
final crypto = await getConversationCrypto();
|
||||
return (await pool.openRead(_localConversationRecordKey,
|
||||
return (await pool.openRead(_localConversationRecordKey!,
|
||||
parent: accountRecordKey, crypto: crypto))
|
||||
.scope((localConversation) async {
|
||||
//
|
||||
@ -70,12 +125,12 @@ class Conversation {
|
||||
}) async {
|
||||
final accountRecordKey =
|
||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
final pool = await DHTRecordPool.instance();
|
||||
final pool = DHTRecordPool.instance;
|
||||
|
||||
final crypto = await getConversationCrypto();
|
||||
final writer = _activeAccountInfo.getConversationWriter();
|
||||
final writer = _activeAccountInfo.conversationWriter;
|
||||
|
||||
return (await pool.openWrite(_localConversationRecordKey, writer,
|
||||
return (await pool.openWrite(_localConversationRecordKey!, writer,
|
||||
parent: accountRecordKey, crypto: crypto))
|
||||
.scope((localConversation) async {
|
||||
//
|
||||
@ -97,7 +152,7 @@ class Conversation {
|
||||
final messagesRecordKey =
|
||||
proto.TypedKeyProto.fromProto(conversation.messages);
|
||||
final crypto = await getConversationCrypto();
|
||||
final writer = _activeAccountInfo.getConversationWriter();
|
||||
final writer = _activeAccountInfo.conversationWriter;
|
||||
|
||||
await (await DHTShortArray.openWrite(messagesRecordKey, writer,
|
||||
parent: _localConversationRecordKey, crypto: crypto))
|
||||
@ -116,7 +171,7 @@ class Conversation {
|
||||
final messagesRecordKey =
|
||||
proto.TypedKeyProto.fromProto(conversation.messages);
|
||||
final crypto = await getConversationCrypto();
|
||||
final writer = _activeAccountInfo.getConversationWriter();
|
||||
final writer = _activeAccountInfo.conversationWriter;
|
||||
|
||||
newMessages = newMessages.sort((a, b) => Timestamp.fromInt64(a.timestamp)
|
||||
.compareTo(Timestamp.fromInt64(b.timestamp)));
|
||||
@ -195,9 +250,8 @@ class Conversation {
|
||||
if (conversationCrypto != null) {
|
||||
return conversationCrypto;
|
||||
}
|
||||
final veilid = await eventualVeilid.future;
|
||||
final identitySecret = _activeAccountInfo.userLogin.identitySecret;
|
||||
final cs = await veilid.getCryptoSystem(identitySecret.kind);
|
||||
final cs = await Veilid.instance.getCryptoSystem(identitySecret.kind);
|
||||
final sharedSecret =
|
||||
await cs.cachedDH(_remoteIdentityPublicKey.value, identitySecret.value);
|
||||
|
||||
@ -232,119 +286,60 @@ class Conversation {
|
||||
}
|
||||
|
||||
final ActiveAccountInfo _activeAccountInfo;
|
||||
final TypedKey _localConversationRecordKey;
|
||||
final TypedKey _remoteIdentityPublicKey;
|
||||
final TypedKey _remoteConversationRecordKey;
|
||||
TypedKey? _localConversationRecordKey;
|
||||
TypedKey? _remoteConversationRecordKey;
|
||||
//
|
||||
DHTRecordCrypto? _conversationCrypto;
|
||||
}
|
||||
|
||||
// Create a conversation
|
||||
// If we were the initiator of the conversation there may be an
|
||||
// incomplete 'existingConversationRecord' that we need to fill
|
||||
// in now that we have the remote identity key
|
||||
Future<T> createConversation<T>(
|
||||
{required ActiveAccountInfo activeAccountInfo,
|
||||
required TypedKey remoteIdentityPublicKey,
|
||||
required FutureOr<T> Function(DHTRecord) callback,
|
||||
TypedKey? existingConversationRecordKey}) async {
|
||||
final pool = await DHTRecordPool.instance();
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
final crypto = await getConversationCrypto(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
remoteIdentityPublicKey: remoteIdentityPublicKey);
|
||||
final writer = activeAccountInfo.getConversationWriter();
|
||||
// //
|
||||
// //
|
||||
// //
|
||||
// //
|
||||
|
||||
// Open with SMPL scheme for identity writer
|
||||
late final DHTRecord localConversationRecord;
|
||||
if (existingConversationRecordKey != null) {
|
||||
localConversationRecord = await pool.openWrite(
|
||||
existingConversationRecordKey, writer,
|
||||
parent: accountRecordKey, crypto: crypto);
|
||||
} else {
|
||||
final localConversationRecordCreate = await pool.create(
|
||||
parent: accountRecordKey,
|
||||
crypto: crypto,
|
||||
schema: DHTSchema.smpl(
|
||||
oCnt: 0, members: [DHTSchemaMember(mKey: writer.key, mCnt: 1)]));
|
||||
await localConversationRecordCreate.close();
|
||||
localConversationRecord = await pool.openWrite(
|
||||
localConversationRecordCreate.key, writer,
|
||||
parent: accountRecordKey, crypto: crypto);
|
||||
}
|
||||
return localConversationRecord
|
||||
// ignore: prefer_expression_function_bodies
|
||||
.deleteScope((localConversation) async {
|
||||
// Make messages log
|
||||
return (await DHTShortArray.create(
|
||||
parent: localConversation.key, crypto: crypto, smplWriter: writer))
|
||||
.deleteScope((messages) async {
|
||||
// Write local conversation key
|
||||
final conversation = proto.Conversation()
|
||||
..profile = activeAccountInfo.account.profile
|
||||
..identityMasterJson =
|
||||
jsonEncode(activeAccountInfo.localAccount.identityMaster.toJson())
|
||||
..messages = messages.record.key.toProto();
|
||||
// @riverpod
|
||||
// class ActiveConversationMessages extends _$ActiveConversationMessages {
|
||||
// /// Get message for active conversation
|
||||
// @override
|
||||
// FutureOr<IList<proto.Message>?> build() async {
|
||||
// await eventualVeilid.future;
|
||||
|
||||
//
|
||||
final update = await localConversation.tryWriteProtobuf(
|
||||
proto.Conversation.fromBuffer, conversation);
|
||||
if (update != null) {
|
||||
throw Exception('Failed to write local conversation');
|
||||
}
|
||||
return await callback(localConversation);
|
||||
});
|
||||
});
|
||||
}
|
||||
// final activeChat = ref.watch(activeChatStateProvider);
|
||||
// if (activeChat == null) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// final activeAccountInfo =
|
||||
// await ref.watch(fetchActiveAccountProvider.future);
|
||||
// if (activeAccountInfo == null) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
@riverpod
|
||||
class ActiveConversationMessages extends _$ActiveConversationMessages {
|
||||
/// Get message for active conversation
|
||||
@override
|
||||
FutureOr<IList<proto.Message>?> build() async {
|
||||
await eventualVeilid.future;
|
||||
// final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
||||
// const IListConst([]);
|
||||
|
||||
final activeChat = ref.watch(activeChatStateProvider);
|
||||
if (activeChat == null) {
|
||||
return null;
|
||||
}
|
||||
// final activeChatContactIdx = contactList.indexWhere(
|
||||
// (c) =>
|
||||
// proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) ==
|
||||
// activeChat,
|
||||
// );
|
||||
// if (activeChatContactIdx == -1) {
|
||||
// return null;
|
||||
// }
|
||||
// final activeChatContact = contactList[activeChatContactIdx];
|
||||
// final remoteIdentityPublicKey =
|
||||
// proto.TypedKeyProto.fromProto(activeChatContact.identityPublicKey);
|
||||
// // final remoteConversationRecordKey = proto.TypedKeyProto.fromProto(
|
||||
// // activeChatContact.remoteConversationRecordKey);
|
||||
// final localConversationRecordKey = proto.TypedKeyProto.fromProto(
|
||||
// activeChatContact.localConversationRecordKey);
|
||||
|
||||
final activeAccountInfo =
|
||||
await ref.watch(fetchActiveAccountProvider.future);
|
||||
if (activeAccountInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
||||
const IListConst([]);
|
||||
|
||||
final activeChatContactIdx = contactList.indexWhere(
|
||||
(c) =>
|
||||
proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) ==
|
||||
activeChat,
|
||||
);
|
||||
if (activeChatContactIdx == -1) {
|
||||
return null;
|
||||
}
|
||||
final activeChatContact = contactList[activeChatContactIdx];
|
||||
final remoteIdentityPublicKey =
|
||||
proto.TypedKeyProto.fromProto(activeChatContact.identityPublicKey);
|
||||
// final remoteConversationRecordKey = proto.TypedKeyProto.fromProto(
|
||||
// activeChatContact.remoteConversationRecordKey);
|
||||
final localConversationRecordKey = proto.TypedKeyProto.fromProto(
|
||||
activeChatContact.localConversationRecordKey);
|
||||
|
||||
return await getLocalConversationMessages(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
localConversationRecordKey: localConversationRecordKey,
|
||||
remoteIdentityPublicKey: remoteIdentityPublicKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
// return await getLocalConversationMessages(
|
||||
// activeAccountInfo: activeAccountInfo,
|
||||
// localConversationRecordKey: localConversationRecordKey,
|
||||
// remoteIdentityPublicKey: remoteIdentityPublicKey,
|
||||
// );
|
||||
// }
|
||||
// }
|
1
lib/contacts/models/models.dart
Normal file
1
lib/contacts/models/models.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'conversation.dart';
|
@ -61,12 +61,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
return BlocProvider(
|
||||
create: (context) => AccountRecordCubit(
|
||||
record: accountInfo.activeAccountInfo!.accountRecord),
|
||||
child: context.watch<AccountRecordCubit>().state.builder(
|
||||
(context, account) => HomeAccountReady(
|
||||
localAccounts: localAccounts,
|
||||
activeUserLogin: activeUserLogin,
|
||||
activeAccountInfo: accountInfo.activeAccountInfo!,
|
||||
account: account)));
|
||||
child: HomeAccountReady());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:equatable/equatable.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:go_router/go_router.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../../account_manager/account_manager.dart';
|
||||
import '../../../contact_invitation/contact_invitation.dart';
|
||||
@ -19,11 +17,13 @@ import 'main_pager/main_pager.dart';
|
||||
class HomeAccountReady extends StatefulWidget {
|
||||
const HomeAccountReady(
|
||||
{required ActiveAccountInfo activeAccountInfo,
|
||||
required Account account,
|
||||
required proto.Account account,
|
||||
super.key})
|
||||
: _accountReadyContext = accountReadyContext;
|
||||
: _activeAccountInfo = activeAccountInfo,
|
||||
_account = account;
|
||||
|
||||
final AccountReadyContext _accountReadyContext;
|
||||
final ActiveAccountInfo _activeAccountInfo;
|
||||
final proto.Account _account;
|
||||
|
||||
@override
|
||||
HomeAccountReadyState createState() => HomeAccountReadyState();
|
||||
@ -31,32 +31,10 @@ class HomeAccountReady extends StatefulWidget {
|
||||
|
||||
class HomeAccountReadyState extends State<HomeAccountReady>
|
||||
with TickerProviderStateMixin {
|
||||
//
|
||||
ContactInvitationRepository? _contactInvitationRepository;
|
||||
|
||||
//
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Async initialize repositories for the active user
|
||||
// xxx: this should not be necessary
|
||||
// xxx: but RepositoryProvider doesn't call dispose()
|
||||
Future.delayed(Duration.zero, () async {
|
||||
//
|
||||
final cir = await ContactInvitationRepository.open(
|
||||
widget.activeAccountInfo, widget._accountReadyContext.account);
|
||||
|
||||
setState(() {
|
||||
_contactInvitationRepository = cir;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_contactInvitationRepository?.dispose();
|
||||
}
|
||||
|
||||
Widget buildUnlockAccount(
|
||||
@ -87,11 +65,11 @@ class HomeAccountReadyState extends State<HomeAccountReady>
|
||||
context.go('/home/settings');
|
||||
}).paddingLTRB(0, 0, 8, 0),
|
||||
ProfileWidget(
|
||||
name: widget._accountReadyContext.account.profile.name,
|
||||
pronouns: widget._accountReadyContext.account.profile.pronouns,
|
||||
name: widget._account.profile.name,
|
||||
pronouns: widget._account.profile.pronouns,
|
||||
).expanded(),
|
||||
]).paddingAll(8),
|
||||
MainPager().expanded()
|
||||
const MainPager().expanded()
|
||||
]);
|
||||
}
|
||||
|
||||
@ -129,18 +107,14 @@ class HomeAccountReadyState extends State<HomeAccountReady>
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_contactInvitationRepository == null) {
|
||||
return waitingPage(context);
|
||||
}
|
||||
|
||||
return RepositoryProvider.value(
|
||||
value: _contactInvitationRepository,
|
||||
child: responsiveVisibility(
|
||||
context: context,
|
||||
phone: false,
|
||||
)
|
||||
? buildTablet(context)
|
||||
: buildPhone(context));
|
||||
}
|
||||
Widget build(BuildContext context) => BlocProvider(
|
||||
create: (context) => ContactInvitationListCubit(
|
||||
activeAccountInfo: widget._activeAccountInfo,
|
||||
account: widget._account),
|
||||
child: responsiveVisibility(
|
||||
context: context,
|
||||
phone: false,
|
||||
)
|
||||
? buildTablet(context)
|
||||
: buildPhone(context));
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../../../account_manager/account_manager.dart';
|
||||
import '../../../../proto/proto.dart' as proto;
|
||||
import '../../../../contact_invitation/contact_invitation.dart';
|
||||
import '../../../../theme/theme.dart';
|
||||
|
||||
class AccountPage extends StatefulWidget {
|
||||
@ -39,19 +39,17 @@ class AccountPageState extends State<AccountPage> {
|
||||
final textTheme = theme.textTheme;
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
final records = widget.account.contactInvitationRecords;
|
||||
|
||||
final contactInvitationRecordList =
|
||||
ref.watch(fetchContactInvitationRecordsProvider).asData?.value ??
|
||||
context.watch<ContactInvitationListCubit>().state.data?.value ??
|
||||
const IListConst([]);
|
||||
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
||||
final contactList = context.watch<ContactListCubit>().state.data?.value ??
|
||||
const IListConst([]);
|
||||
|
||||
return SizedBox(
|
||||
child: Column(children: <Widget>[
|
||||
if (contactInvitationRecordList.isNotEmpty)
|
||||
ExpansionTile(
|
||||
tilePadding: EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||
tilePadding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||
backgroundColor: scale.primaryScale.border,
|
||||
collapsedBackgroundColor: scale.primaryScale.border,
|
||||
shape: RoundedRectangleBorder(
|
||||
|
@ -1,7 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
@ -9,41 +7,22 @@ import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:preload_page_view/preload_page_view.dart';
|
||||
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
||||
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../../../proto/proto.dart' as proto;
|
||||
import '../../../../contact_invitation/contact_invitation.dart';
|
||||
import '../../../../theme/theme.dart';
|
||||
import '../../../../tools/tools.dart';
|
||||
import '../../../account_manager/account_manager.dart';
|
||||
import '../../../contact_invitation/contact_invitation.dart';
|
||||
import '../../../theme/theme.dart';
|
||||
import 'account_page.dart';
|
||||
import 'bottom_sheet_action_button.dart';
|
||||
import 'chats_page.dart';
|
||||
|
||||
class MainPager extends StatefulWidget {
|
||||
const MainPager(
|
||||
{required this.localAccounts,
|
||||
required this.activeUserLogin,
|
||||
required this.account,
|
||||
super.key});
|
||||
|
||||
final IList<LocalAccount> localAccounts;
|
||||
final TypedKey activeUserLogin;
|
||||
final proto.Account account;
|
||||
const MainPager({super.key});
|
||||
|
||||
@override
|
||||
MainPagerState createState() => MainPagerState();
|
||||
|
||||
static MainPagerState? of(BuildContext context) =>
|
||||
context.findAncestorStateOfType<MainPagerState>();
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(IterableProperty<LocalAccount>('localAccounts', localAccounts))
|
||||
..add(DiagnosticsProperty<TypedKey>('activeUserLogin', activeUserLogin))
|
||||
..add(DiagnosticsProperty<proto.Account>('account', account));
|
||||
}
|
||||
}
|
||||
|
||||
class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
|
||||
@ -187,12 +166,9 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
|
||||
_currentPage = index;
|
||||
});
|
||||
},
|
||||
children: [
|
||||
AccountPage(
|
||||
localAccounts: widget.localAccounts,
|
||||
activeUserLogin: widget.activeUserLogin,
|
||||
account: widget.account),
|
||||
const ChatsPage(),
|
||||
children: const [
|
||||
AccountPage(),
|
||||
ChatsPage(),
|
||||
])),
|
||||
// appBar: AppBar(
|
||||
// toolbarHeight: 24,
|
||||
|
@ -1,55 +0,0 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:circular_profile_avatar/circular_profile_avatar.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../entities/local_account.dart';
|
||||
import '../providers/logins.dart';
|
||||
|
||||
class AccountBubble extends ConsumerWidget {
|
||||
const AccountBubble({required this.account, super.key});
|
||||
final LocalAccount account;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
windowManager.setTitleBarStyle(TitleBarStyle.normal);
|
||||
final logins = ref.watch(loginsProvider);
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: CircularProfileAvatar('',
|
||||
child: Container(color: Theme.of(context).disabledColor))),
|
||||
const Expanded(child: Text('Placeholder'))
|
||||
]));
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<LocalAccount>('account', account));
|
||||
}
|
||||
}
|
||||
|
||||
class AddAccountBubble extends ConsumerWidget {
|
||||
const AddAccountBubble({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
windowManager.setTitleBarStyle(TitleBarStyle.normal);
|
||||
final logins = ref.watch(loginsProvider);
|
||||
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
CircularProfileAvatar('',
|
||||
borderWidth: 4,
|
||||
borderColor: Theme.of(context).unselectedWidgetColor,
|
||||
child: Container(
|
||||
color: Colors.blue, child: const Icon(Icons.add, size: 50))),
|
||||
const Text('Add Account').paddingLTRB(0, 4, 0, 0)
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../../entities/local_account.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
import '../../../packages/veilid_support/veilid_support.dart';
|
||||
import 'account.dart';
|
||||
import 'conversation.dart';
|
||||
|
||||
part 'contact_invite.g.dart';
|
||||
|
||||
/// Get the active account contact invitation list
|
||||
@riverpod
|
||||
Future<IList<proto.ContactInvitationRecord>?> fetchContactInvitationRecords(
|
||||
FetchContactInvitationRecordsRef ref) async {
|
||||
// See if we've logged into this account or if it is locked
|
||||
final activeAccountInfo = await ref.watch(fetchActiveAccountProvider.future);
|
||||
if (activeAccountInfo == null) {
|
||||
return null;
|
||||
}
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Decode the contact invitation list from the DHT
|
||||
IList<proto.ContactInvitationRecord> out = const IListConst([]);
|
||||
|
||||
try {
|
||||
await (await DHTShortArray.openOwned(
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||
activeAccountInfo.account.contactInvitationRecords),
|
||||
parent: accountRecordKey))
|
||||
.scope((cirList) async {
|
||||
for (var i = 0; i < cirList.length; i++) {
|
||||
final cir = await cirList.getItem(i);
|
||||
if (cir == null) {
|
||||
throw Exception('Failed to get contact invitation record');
|
||||
}
|
||||
out = out.add(proto.ContactInvitationRecord.fromBuffer(cir));
|
||||
}
|
||||
});
|
||||
} on VeilidAPIExceptionTryAgain catch (_) {
|
||||
// Try again later
|
||||
ref.invalidateSelf();
|
||||
return null;
|
||||
} on Exception catch (_) {
|
||||
// Try again later
|
||||
ref.invalidateSelf();
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../veilid_support.dart';
|
||||
|
||||
@ -41,6 +42,19 @@ class DHTShortArrayCubit<T> extends Cubit<AsyncValue<IList<T>>> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> refresh({bool forceRefresh = false}) async {
|
||||
var out = IList<T>();
|
||||
// xxx could be parallelized but we need to watch out for rate limits
|
||||
for (var i = 0; i < _shortArray.length; i++) {
|
||||
final cir = await _shortArray.getItem(i, forceRefresh: forceRefresh);
|
||||
if (cir == null) {
|
||||
throw Exception('Failed to get short array element');
|
||||
}
|
||||
out = out.add(_decodeElement(cir));
|
||||
}
|
||||
emit(AsyncValue.data(out));
|
||||
}
|
||||
|
||||
void _update() {
|
||||
// Run at most one background update process
|
||||
_wantsUpdate = true;
|
||||
@ -91,6 +105,9 @@ class DHTShortArrayCubit<T> extends Cubit<AsyncValue<IList<T>>> {
|
||||
await super.close();
|
||||
}
|
||||
|
||||
@protected
|
||||
DHTShortArray get shortArray => _shortArray;
|
||||
|
||||
late final DHTShortArray _shortArray;
|
||||
final T Function(List<int> data) _decodeElement;
|
||||
StreamSubscription<void>? _subscription;
|
||||
|
Loading…
Reference in New Issue
Block a user