mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-01-11 15:49:29 -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';
|
export 'views/views.dart';
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../account_manager/account_manager.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
|
import '../../contacts/contacts.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
@ -33,19 +33,19 @@ class InvitationStatus {
|
|||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// Mutable state for per-account contact invitations
|
// Mutable state for per-account contact invitations
|
||||||
|
|
||||||
class ContactInvitationListCubit extends DHTShortArrayCubit<proto.ContactInvitation> {
|
class ContactInvitationListCubit
|
||||||
|
extends DHTShortArrayCubit<proto.ContactInvitationRecord> {
|
||||||
ContactInvitationListCubit({
|
ContactInvitationListCubit({
|
||||||
required ActiveAccountInfo activeAccountInfo,
|
required ActiveAccountInfo activeAccountInfo,
|
||||||
required proto.Account account,
|
required proto.Account account,
|
||||||
required DHTShortArray dhtRecord,
|
|
||||||
}) : _activeAccountInfo = activeAccountInfo,
|
}) : _activeAccountInfo = activeAccountInfo,
|
||||||
_account = account,
|
_account = account,
|
||||||
_dhtRecord = dhtRecord,
|
super(
|
||||||
super(shortArray: dhtRecord, decodeElement: proto.ContactInvitation.fromBuffer);
|
open: () => _open(activeAccountInfo, account),
|
||||||
xxx convert the rest of this to cubit
|
decodeElement: proto.ContactInvitationRecord.fromBuffer);
|
||||||
static Future<ContactInvitationRepository> open(
|
|
||||||
ActiveAccountInfo activeAccountInfo, proto.Account account) async {
|
|
||||||
|
|
||||||
|
static Future<DHTShortArray> _open(
|
||||||
|
ActiveAccountInfo activeAccountInfo, proto.Account account) async {
|
||||||
final accountRecordKey =
|
final accountRecordKey =
|
||||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
|
|
||||||
@ -57,28 +57,9 @@ xxx convert the rest of this to cubit
|
|||||||
contactInvitationListRecordKey,
|
contactInvitationListRecordKey,
|
||||||
parent: accountRecordKey);
|
parent: accountRecordKey);
|
||||||
|
|
||||||
return ContactInvitationRepository._(
|
return dhtRecord;
|
||||||
activeAccountInfo: activeAccountInfo,
|
|
||||||
account: account,
|
|
||||||
dhtRecord: 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(
|
Future<Uint8List> createInvitation(
|
||||||
{required EncryptionKeyType encryptionKeyType,
|
{required EncryptionKeyType encryptionKeyType,
|
||||||
required String encryptionKey,
|
required String encryptionKey,
|
||||||
@ -98,7 +79,8 @@ xxx convert the rest of this to cubit
|
|||||||
encryptionKey: encryptionKey,
|
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
|
// 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
|
// to and it will be eventually encrypted with the DH of the contact's
|
||||||
// identity key
|
// identity key
|
||||||
@ -163,7 +145,7 @@ xxx convert the rest of this to cubit
|
|||||||
|
|
||||||
// Add ContactInvitationRecord to account's list
|
// Add ContactInvitationRecord to account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
// 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');
|
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;
|
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
|
|
||||||
// Remove ContactInvitationRecord from account's list
|
// Remove ContactInvitationRecord from account's list
|
||||||
for (var i = 0; i < _dhtRecord.length; i++) {
|
for (var i = 0; i < shortArray.length; i++) {
|
||||||
final item = await _dhtRecord.getItemProtobuf(
|
final item = await shortArray.getItemProtobuf(
|
||||||
proto.ContactInvitationRecord.fromBuffer, i);
|
proto.ContactInvitationRecord.fromBuffer, i);
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
throw Exception('Failed to get contact invitation record');
|
throw Exception('Failed to get contact invitation record');
|
||||||
}
|
}
|
||||||
if (item.contactRequestInbox.recordKey ==
|
if (item.contactRequestInbox.recordKey ==
|
||||||
contactInvitationRecord.contactRequestInbox.recordKey) {
|
contactInvitationRecord.contactRequestInbox.recordKey) {
|
||||||
await _dhtRecord.tryRemoveItem(i);
|
await shortArray.tryRemoveItem(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -229,11 +211,11 @@ xxx convert the rest of this to cubit
|
|||||||
|
|
||||||
final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind);
|
final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind);
|
||||||
|
|
||||||
// See if we're chatting to ourselves, if so, don't delete it here
|
// Compare the invitation's contact request
|
||||||
final isSelf = _contactIdentityMaster.identityPublicKey ==
|
// inbox with our list of extant invitations
|
||||||
_activeAccountInfo.localAccount.identityMaster.identityPublicKey;
|
// If we're chatting to ourselves,
|
||||||
xxx this doesn't work and the upper one doesnt either
|
// we are validating an invitation we have created
|
||||||
final isSelf = _records.indexWhere((cir) =>
|
final isSelf = state.data!.value.indexWhere((cir) =>
|
||||||
proto.TypedKeyProto.fromProto(cir.contactRequestInbox.recordKey) ==
|
proto.TypedKeyProto.fromProto(cir.contactRequestInbox.recordKey) ==
|
||||||
contactRequestInboxKey) !=
|
contactRequestInboxKey) !=
|
||||||
-1;
|
-1;
|
||||||
@ -283,10 +265,7 @@ xxx this doesn't work and the upper one doesnt either
|
|||||||
|
|
||||||
out = ValidContactInvitation(
|
out = ValidContactInvitation(
|
||||||
activeAccountInfo: _activeAccountInfo,
|
activeAccountInfo: _activeAccountInfo,
|
||||||
signedContactInvitation: signedContactInvitation,
|
|
||||||
contactInvitation: contactInvitation,
|
|
||||||
contactRequestInboxKey: contactRequestInboxKey,
|
contactRequestInboxKey: contactRequestInboxKey,
|
||||||
contactRequest: contactRequest,
|
|
||||||
contactRequestPrivate: contactRequestPrivate,
|
contactRequestPrivate: contactRequestPrivate,
|
||||||
contactIdentityMaster: contactIdentityMaster,
|
contactIdentityMaster: contactIdentityMaster,
|
||||||
writer: writer);
|
writer: writer);
|
||||||
@ -296,13 +275,12 @@ xxx this doesn't work and the upper one doesnt either
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<InvitationStatus?> checkInvitationStatus(
|
Future<InvitationStatus?> checkInvitationStatus(
|
||||||
{required ActiveAccountInfo activeAccountInfo,
|
{required proto.ContactInvitationRecord contactInvitationRecord}) async {
|
||||||
required proto.ContactInvitationRecord contactInvitationRecord}) async {
|
|
||||||
// Open the contact request inbox
|
// Open the contact request inbox
|
||||||
try {
|
try {
|
||||||
final pool = DHTRecordPool.instance;
|
final pool = DHTRecordPool.instance;
|
||||||
final accountRecordKey =
|
final accountRecordKey = _activeAccountInfo
|
||||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
final writerKey =
|
final writerKey =
|
||||||
proto.CryptoKeyProto.fromProto(contactInvitationRecord.writerKey);
|
proto.CryptoKeyProto.fromProto(contactInvitationRecord.writerKey);
|
||||||
final writerSecret =
|
final writerSecret =
|
||||||
@ -350,11 +328,14 @@ xxx this doesn't work and the upper one doesnt either
|
|||||||
// Pull profile from remote conversation key
|
// Pull profile from remote conversation key
|
||||||
final remoteConversationRecordKey = proto.TypedKeyProto.fromProto(
|
final remoteConversationRecordKey = proto.TypedKeyProto.fromProto(
|
||||||
contactResponse.remoteConversationRecordKey);
|
contactResponse.remoteConversationRecordKey);
|
||||||
final remoteConversation = await readRemoteConversation(
|
|
||||||
activeAccountInfo: activeAccountInfo,
|
final conversation = ConversationManager(
|
||||||
|
activeAccountInfo: _activeAccountInfo,
|
||||||
remoteIdentityPublicKey:
|
remoteIdentityPublicKey:
|
||||||
contactIdentityMaster.identityPublicTypedKey(),
|
contactIdentityMaster.identityPublicTypedKey(),
|
||||||
remoteConversationRecordKey: remoteConversationRecordKey);
|
remoteConversationRecordKey: remoteConversationRecordKey);
|
||||||
|
|
||||||
|
final remoteConversation = await conversation.readRemoteConversation();
|
||||||
if (remoteConversation == null) {
|
if (remoteConversation == null) {
|
||||||
log.info('Remote conversation could not be read. Waiting...');
|
log.info('Remote conversation could not be read. Waiting...');
|
||||||
return null;
|
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
|
// Complete the local conversation now that we have the remote profile
|
||||||
final localConversationRecordKey = proto.TypedKeyProto.fromProto(
|
final localConversationRecordKey = proto.TypedKeyProto.fromProto(
|
||||||
contactInvitationRecord.localConversationRecordKey);
|
contactInvitationRecord.localConversationRecordKey);
|
||||||
return createConversation(
|
return conversation.initLocalConversation(
|
||||||
activeAccountInfo: activeAccountInfo,
|
|
||||||
remoteIdentityPublicKey:
|
|
||||||
contactIdentityMaster.identityPublicTypedKey(),
|
|
||||||
existingConversationRecordKey: localConversationRecordKey,
|
existingConversationRecordKey: localConversationRecordKey,
|
||||||
|
profile: xxx LOCAL PROFILE HERE NOT REMOTE
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
callback: (localConversation) async {
|
callback: (localConversation) async {
|
||||||
return InvitationStatus(
|
return InvitationStatus(
|
||||||
@ -400,9 +379,6 @@ xxx this doesn't work and the upper one doesnt either
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
final ActiveAccountInfo _activeAccountInfo;
|
final ActiveAccountInfo _activeAccountInfo;
|
||||||
final proto.Account _account;
|
final proto.Account _account;
|
||||||
final DHTShortArray _dhtRecord;
|
|
||||||
//IList<proto.ContactInvitationRecord> _records;
|
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
export 'accepted_contact.dart';
|
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 '../../account_manager/account_manager.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../models/models.dart';
|
import 'models.dart';
|
||||||
import 'contact_invitation_repository.dart';
|
import '../cubits/contact_invitation_list_cubit.dart';
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
///
|
///
|
||||||
@ -14,18 +14,12 @@ class ValidContactInvitation {
|
|||||||
@internal
|
@internal
|
||||||
ValidContactInvitation(
|
ValidContactInvitation(
|
||||||
{required ActiveAccountInfo activeAccountInfo,
|
{required ActiveAccountInfo activeAccountInfo,
|
||||||
required proto.SignedContactInvitation signedContactInvitation,
|
|
||||||
required proto.ContactInvitation contactInvitation,
|
|
||||||
required TypedKey contactRequestInboxKey,
|
required TypedKey contactRequestInboxKey,
|
||||||
required proto.ContactRequest contactRequest,
|
|
||||||
required proto.ContactRequestPrivate contactRequestPrivate,
|
required proto.ContactRequestPrivate contactRequestPrivate,
|
||||||
required IdentityMaster contactIdentityMaster,
|
required IdentityMaster contactIdentityMaster,
|
||||||
required KeyPair writer})
|
required KeyPair writer})
|
||||||
: _activeAccountInfo = activeAccountInfo,
|
: _activeAccountInfo = activeAccountInfo,
|
||||||
_signedContactInvitation = signedContactInvitation,
|
|
||||||
_contactInvitation = contactInvitation,
|
|
||||||
_contactRequestInboxKey = contactRequestInboxKey,
|
_contactRequestInboxKey = contactRequestInboxKey,
|
||||||
_contactRequest = contactRequest,
|
|
||||||
_contactRequestPrivate = contactRequestPrivate,
|
_contactRequestPrivate = contactRequestPrivate,
|
||||||
_contactIdentityMaster = contactIdentityMaster,
|
_contactIdentityMaster = contactIdentityMaster,
|
||||||
_writer = writer;
|
_writer = writer;
|
||||||
@ -140,8 +134,5 @@ class ValidContactInvitation {
|
|||||||
final TypedKey _contactRequestInboxKey;
|
final TypedKey _contactRequestInboxKey;
|
||||||
final IdentityMaster _contactIdentityMaster;
|
final IdentityMaster _contactIdentityMaster;
|
||||||
final KeyPair _writer;
|
final KeyPair _writer;
|
||||||
proto.SignedContactInvitation _signedContactInvitation;
|
final proto.ContactRequestPrivate _contactRequestPrivate;
|
||||||
proto.ContactInvitation _contactInvitation;
|
|
||||||
proto.ContactRequest _contactRequest;
|
|
||||||
proto.ContactRequestPrivate _contactRequestPrivate;
|
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
export 'models/models.dart';
|
||||||
export 'views/views.dart';
|
export 'views/views.dart';
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// Each Contact in the ContactList has at most one Conversation between the
|
// Each Contact in the ContactList has at most one Conversation between the
|
||||||
// remote contact and the local account
|
// remote contact and the local account
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
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 '../../proto/proto.dart' as proto;
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
|
|
||||||
import 'chat.dart';
|
class ConversationManager {
|
||||||
|
ConversationManager(
|
||||||
class Conversation {
|
|
||||||
Conversation._(
|
|
||||||
{required ActiveAccountInfo activeAccountInfo,
|
{required ActiveAccountInfo activeAccountInfo,
|
||||||
required TypedKey localConversationRecordKey,
|
|
||||||
required TypedKey remoteIdentityPublicKey,
|
required TypedKey remoteIdentityPublicKey,
|
||||||
required TypedKey remoteConversationRecordKey})
|
TypedKey? localConversationRecordKey,
|
||||||
|
TypedKey? remoteConversationRecordKey})
|
||||||
: _activeAccountInfo = activeAccountInfo,
|
: _activeAccountInfo = activeAccountInfo,
|
||||||
_localConversationRecordKey = localConversationRecordKey,
|
_localConversationRecordKey = localConversationRecordKey,
|
||||||
_remoteIdentityPublicKey = remoteIdentityPublicKey,
|
_remoteIdentityPublicKey = remoteIdentityPublicKey,
|
||||||
_remoteConversationRecordKey = remoteConversationRecordKey;
|
_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;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
Future<void> close() async {
|
|
||||||
//
|
//
|
||||||
|
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 {
|
Future<proto.Conversation?> readRemoteConversation() async {
|
||||||
final accountRecordKey =
|
final accountRecordKey =
|
||||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
final pool = await DHTRecordPool.instance();
|
final pool = DHTRecordPool.instance;
|
||||||
|
|
||||||
final crypto = await getConversationCrypto();
|
final crypto = await getConversationCrypto();
|
||||||
return (await pool.openRead(_remoteConversationRecordKey,
|
return (await pool.openRead(_remoteConversationRecordKey!,
|
||||||
parent: accountRecordKey, crypto: crypto))
|
parent: accountRecordKey, crypto: crypto))
|
||||||
.scope((remoteConversation) async {
|
.scope((remoteConversation) async {
|
||||||
//
|
//
|
||||||
@ -49,10 +104,10 @@ class Conversation {
|
|||||||
Future<proto.Conversation?> readLocalConversation() async {
|
Future<proto.Conversation?> readLocalConversation() async {
|
||||||
final accountRecordKey =
|
final accountRecordKey =
|
||||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
final pool = await DHTRecordPool.instance();
|
final pool = DHTRecordPool.instance;
|
||||||
|
|
||||||
final crypto = await getConversationCrypto();
|
final crypto = await getConversationCrypto();
|
||||||
return (await pool.openRead(_localConversationRecordKey,
|
return (await pool.openRead(_localConversationRecordKey!,
|
||||||
parent: accountRecordKey, crypto: crypto))
|
parent: accountRecordKey, crypto: crypto))
|
||||||
.scope((localConversation) async {
|
.scope((localConversation) async {
|
||||||
//
|
//
|
||||||
@ -70,12 +125,12 @@ class Conversation {
|
|||||||
}) async {
|
}) async {
|
||||||
final accountRecordKey =
|
final accountRecordKey =
|
||||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
final pool = await DHTRecordPool.instance();
|
final pool = DHTRecordPool.instance;
|
||||||
|
|
||||||
final crypto = await getConversationCrypto();
|
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))
|
parent: accountRecordKey, crypto: crypto))
|
||||||
.scope((localConversation) async {
|
.scope((localConversation) async {
|
||||||
//
|
//
|
||||||
@ -97,7 +152,7 @@ class Conversation {
|
|||||||
final messagesRecordKey =
|
final messagesRecordKey =
|
||||||
proto.TypedKeyProto.fromProto(conversation.messages);
|
proto.TypedKeyProto.fromProto(conversation.messages);
|
||||||
final crypto = await getConversationCrypto();
|
final crypto = await getConversationCrypto();
|
||||||
final writer = _activeAccountInfo.getConversationWriter();
|
final writer = _activeAccountInfo.conversationWriter;
|
||||||
|
|
||||||
await (await DHTShortArray.openWrite(messagesRecordKey, writer,
|
await (await DHTShortArray.openWrite(messagesRecordKey, writer,
|
||||||
parent: _localConversationRecordKey, crypto: crypto))
|
parent: _localConversationRecordKey, crypto: crypto))
|
||||||
@ -116,7 +171,7 @@ class Conversation {
|
|||||||
final messagesRecordKey =
|
final messagesRecordKey =
|
||||||
proto.TypedKeyProto.fromProto(conversation.messages);
|
proto.TypedKeyProto.fromProto(conversation.messages);
|
||||||
final crypto = await getConversationCrypto();
|
final crypto = await getConversationCrypto();
|
||||||
final writer = _activeAccountInfo.getConversationWriter();
|
final writer = _activeAccountInfo.conversationWriter;
|
||||||
|
|
||||||
newMessages = newMessages.sort((a, b) => Timestamp.fromInt64(a.timestamp)
|
newMessages = newMessages.sort((a, b) => Timestamp.fromInt64(a.timestamp)
|
||||||
.compareTo(Timestamp.fromInt64(b.timestamp)));
|
.compareTo(Timestamp.fromInt64(b.timestamp)));
|
||||||
@ -195,9 +250,8 @@ class Conversation {
|
|||||||
if (conversationCrypto != null) {
|
if (conversationCrypto != null) {
|
||||||
return conversationCrypto;
|
return conversationCrypto;
|
||||||
}
|
}
|
||||||
final veilid = await eventualVeilid.future;
|
|
||||||
final identitySecret = _activeAccountInfo.userLogin.identitySecret;
|
final identitySecret = _activeAccountInfo.userLogin.identitySecret;
|
||||||
final cs = await veilid.getCryptoSystem(identitySecret.kind);
|
final cs = await Veilid.instance.getCryptoSystem(identitySecret.kind);
|
||||||
final sharedSecret =
|
final sharedSecret =
|
||||||
await cs.cachedDH(_remoteIdentityPublicKey.value, identitySecret.value);
|
await cs.cachedDH(_remoteIdentityPublicKey.value, identitySecret.value);
|
||||||
|
|
||||||
@ -232,119 +286,60 @@ class Conversation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final ActiveAccountInfo _activeAccountInfo;
|
final ActiveAccountInfo _activeAccountInfo;
|
||||||
final TypedKey _localConversationRecordKey;
|
|
||||||
final TypedKey _remoteIdentityPublicKey;
|
final TypedKey _remoteIdentityPublicKey;
|
||||||
final TypedKey _remoteConversationRecordKey;
|
TypedKey? _localConversationRecordKey;
|
||||||
|
TypedKey? _remoteConversationRecordKey;
|
||||||
//
|
//
|
||||||
DHTRecordCrypto? _conversationCrypto;
|
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
|
// @riverpod
|
||||||
late final DHTRecord localConversationRecord;
|
// class ActiveConversationMessages extends _$ActiveConversationMessages {
|
||||||
if (existingConversationRecordKey != null) {
|
// /// Get message for active conversation
|
||||||
localConversationRecord = await pool.openWrite(
|
// @override
|
||||||
existingConversationRecordKey, writer,
|
// FutureOr<IList<proto.Message>?> build() async {
|
||||||
parent: accountRecordKey, crypto: crypto);
|
// await eventualVeilid.future;
|
||||||
} 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();
|
|
||||||
|
|
||||||
//
|
// final activeChat = ref.watch(activeChatStateProvider);
|
||||||
final update = await localConversation.tryWriteProtobuf(
|
// if (activeChat == null) {
|
||||||
proto.Conversation.fromBuffer, conversation);
|
// return null;
|
||||||
if (update != null) {
|
// }
|
||||||
throw Exception('Failed to write local conversation');
|
|
||||||
}
|
|
||||||
return await callback(localConversation);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
// final activeAccountInfo =
|
||||||
//
|
// await ref.watch(fetchActiveAccountProvider.future);
|
||||||
//
|
// if (activeAccountInfo == null) {
|
||||||
//
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
@riverpod
|
// final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
||||||
class ActiveConversationMessages extends _$ActiveConversationMessages {
|
// const IListConst([]);
|
||||||
/// Get message for active conversation
|
|
||||||
@override
|
|
||||||
FutureOr<IList<proto.Message>?> build() async {
|
|
||||||
await eventualVeilid.future;
|
|
||||||
|
|
||||||
final activeChat = ref.watch(activeChatStateProvider);
|
// final activeChatContactIdx = contactList.indexWhere(
|
||||||
if (activeChat == null) {
|
// (c) =>
|
||||||
return null;
|
// 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 =
|
// return await getLocalConversationMessages(
|
||||||
await ref.watch(fetchActiveAccountProvider.future);
|
// activeAccountInfo: activeAccountInfo,
|
||||||
if (activeAccountInfo == null) {
|
// localConversationRecordKey: localConversationRecordKey,
|
||||||
return null;
|
// remoteIdentityPublicKey: remoteIdentityPublicKey,
|
||||||
}
|
// );
|
||||||
|
// }
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
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(
|
return BlocProvider(
|
||||||
create: (context) => AccountRecordCubit(
|
create: (context) => AccountRecordCubit(
|
||||||
record: accountInfo.activeAccountInfo!.accountRecord),
|
record: accountInfo.activeAccountInfo!.accountRecord),
|
||||||
child: context.watch<AccountRecordCubit>().state.builder(
|
child: HomeAccountReady());
|
||||||
(context, account) => HomeAccountReady(
|
|
||||||
localAccounts: localAccounts,
|
|
||||||
activeUserLogin: activeUserLogin,
|
|
||||||
activeAccountInfo: accountInfo.activeAccountInfo!,
|
|
||||||
account: account)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
|
||||||
|
|
||||||
import '../../../account_manager/account_manager.dart';
|
import '../../../account_manager/account_manager.dart';
|
||||||
import '../../../contact_invitation/contact_invitation.dart';
|
import '../../../contact_invitation/contact_invitation.dart';
|
||||||
@ -19,11 +17,13 @@ import 'main_pager/main_pager.dart';
|
|||||||
class HomeAccountReady extends StatefulWidget {
|
class HomeAccountReady extends StatefulWidget {
|
||||||
const HomeAccountReady(
|
const HomeAccountReady(
|
||||||
{required ActiveAccountInfo activeAccountInfo,
|
{required ActiveAccountInfo activeAccountInfo,
|
||||||
required Account account,
|
required proto.Account account,
|
||||||
super.key})
|
super.key})
|
||||||
: _accountReadyContext = accountReadyContext;
|
: _activeAccountInfo = activeAccountInfo,
|
||||||
|
_account = account;
|
||||||
|
|
||||||
final AccountReadyContext _accountReadyContext;
|
final ActiveAccountInfo _activeAccountInfo;
|
||||||
|
final proto.Account _account;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
HomeAccountReadyState createState() => HomeAccountReadyState();
|
HomeAccountReadyState createState() => HomeAccountReadyState();
|
||||||
@ -31,32 +31,10 @@ class HomeAccountReady extends StatefulWidget {
|
|||||||
|
|
||||||
class HomeAccountReadyState extends State<HomeAccountReady>
|
class HomeAccountReadyState extends State<HomeAccountReady>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
//
|
|
||||||
ContactInvitationRepository? _contactInvitationRepository;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.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(
|
Widget buildUnlockAccount(
|
||||||
@ -87,11 +65,11 @@ class HomeAccountReadyState extends State<HomeAccountReady>
|
|||||||
context.go('/home/settings');
|
context.go('/home/settings');
|
||||||
}).paddingLTRB(0, 0, 8, 0),
|
}).paddingLTRB(0, 0, 8, 0),
|
||||||
ProfileWidget(
|
ProfileWidget(
|
||||||
name: widget._accountReadyContext.account.profile.name,
|
name: widget._account.profile.name,
|
||||||
pronouns: widget._accountReadyContext.account.profile.pronouns,
|
pronouns: widget._account.profile.pronouns,
|
||||||
).expanded(),
|
).expanded(),
|
||||||
]).paddingAll(8),
|
]).paddingAll(8),
|
||||||
MainPager().expanded()
|
const MainPager().expanded()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,18 +107,14 @@ class HomeAccountReadyState extends State<HomeAccountReady>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) => BlocProvider(
|
||||||
if (_contactInvitationRepository == null) {
|
create: (context) => ContactInvitationListCubit(
|
||||||
return waitingPage(context);
|
activeAccountInfo: widget._activeAccountInfo,
|
||||||
}
|
account: widget._account),
|
||||||
|
|
||||||
return RepositoryProvider.value(
|
|
||||||
value: _contactInvitationRepository,
|
|
||||||
child: responsiveVisibility(
|
child: responsiveVisibility(
|
||||||
context: context,
|
context: context,
|
||||||
phone: false,
|
phone: false,
|
||||||
)
|
)
|
||||||
? buildTablet(context)
|
? buildTablet(context)
|
||||||
: buildPhone(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:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../../../account_manager/account_manager.dart';
|
import '../../../../contact_invitation/contact_invitation.dart';
|
||||||
import '../../../../proto/proto.dart' as proto;
|
|
||||||
import '../../../../theme/theme.dart';
|
import '../../../../theme/theme.dart';
|
||||||
|
|
||||||
class AccountPage extends StatefulWidget {
|
class AccountPage extends StatefulWidget {
|
||||||
@ -39,19 +39,17 @@ class AccountPageState extends State<AccountPage> {
|
|||||||
final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
final records = widget.account.contactInvitationRecords;
|
|
||||||
|
|
||||||
final contactInvitationRecordList =
|
final contactInvitationRecordList =
|
||||||
ref.watch(fetchContactInvitationRecordsProvider).asData?.value ??
|
context.watch<ContactInvitationListCubit>().state.data?.value ??
|
||||||
const IListConst([]);
|
const IListConst([]);
|
||||||
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
final contactList = context.watch<ContactListCubit>().state.data?.value ??
|
||||||
const IListConst([]);
|
const IListConst([]);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
child: Column(children: <Widget>[
|
child: Column(children: <Widget>[
|
||||||
if (contactInvitationRecordList.isNotEmpty)
|
if (contactInvitationRecordList.isNotEmpty)
|
||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
tilePadding: EdgeInsets.fromLTRB(8, 0, 8, 0),
|
tilePadding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||||
backgroundColor: scale.primaryScale.border,
|
backgroundColor: scale.primaryScale.border,
|
||||||
collapsedBackgroundColor: scale.primaryScale.border,
|
collapsedBackgroundColor: scale.primaryScale.border,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import 'dart:async';
|
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/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.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:preload_page_view/preload_page_view.dart';
|
||||||
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
||||||
import 'package:stylish_bottom_bar/stylish_bottom_bar.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 '../../../../tools/tools.dart';
|
||||||
import '../../../account_manager/account_manager.dart';
|
|
||||||
import '../../../contact_invitation/contact_invitation.dart';
|
|
||||||
import '../../../theme/theme.dart';
|
|
||||||
import 'account_page.dart';
|
import 'account_page.dart';
|
||||||
import 'bottom_sheet_action_button.dart';
|
import 'bottom_sheet_action_button.dart';
|
||||||
import 'chats_page.dart';
|
import 'chats_page.dart';
|
||||||
|
|
||||||
class MainPager extends StatefulWidget {
|
class MainPager extends StatefulWidget {
|
||||||
const MainPager(
|
const MainPager({super.key});
|
||||||
{required this.localAccounts,
|
|
||||||
required this.activeUserLogin,
|
|
||||||
required this.account,
|
|
||||||
super.key});
|
|
||||||
|
|
||||||
final IList<LocalAccount> localAccounts;
|
|
||||||
final TypedKey activeUserLogin;
|
|
||||||
final proto.Account account;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MainPagerState createState() => MainPagerState();
|
MainPagerState createState() => MainPagerState();
|
||||||
|
|
||||||
static MainPagerState? of(BuildContext context) =>
|
static MainPagerState? of(BuildContext context) =>
|
||||||
context.findAncestorStateOfType<MainPagerState>();
|
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 {
|
class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
|
||||||
@ -187,12 +166,9 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
|
|||||||
_currentPage = index;
|
_currentPage = index;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
children: [
|
children: const [
|
||||||
AccountPage(
|
AccountPage(),
|
||||||
localAccounts: widget.localAccounts,
|
ChatsPage(),
|
||||||
activeUserLogin: widget.activeUserLogin,
|
|
||||||
account: widget.account),
|
|
||||||
const ChatsPage(),
|
|
||||||
])),
|
])),
|
||||||
// appBar: AppBar(
|
// appBar: AppBar(
|
||||||
// toolbarHeight: 24,
|
// 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:bloc/bloc.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../../veilid_support.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() {
|
void _update() {
|
||||||
// Run at most one background update process
|
// Run at most one background update process
|
||||||
_wantsUpdate = true;
|
_wantsUpdate = true;
|
||||||
@ -91,6 +105,9 @@ class DHTShortArrayCubit<T> extends Cubit<AsyncValue<IList<T>>> {
|
|||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
DHTShortArray get shortArray => _shortArray;
|
||||||
|
|
||||||
late final DHTShortArray _shortArray;
|
late final DHTShortArray _shortArray;
|
||||||
final T Function(List<int> data) _decodeElement;
|
final T Function(List<int> data) _decodeElement;
|
||||||
StreamSubscription<void>? _subscription;
|
StreamSubscription<void>? _subscription;
|
||||||
|
Loading…
Reference in New Issue
Block a user