veilidchat/lib/contacts/cubits/contact_list_cubit.dart

174 lines
5.6 KiB
Dart
Raw Normal View History

2024-01-30 17:03:14 -05:00
import 'dart:async';
import 'dart:convert';
2024-06-15 00:01:08 -04:00
import 'package:async_tools/async_tools.dart';
import 'package:protobuf/protobuf.dart';
2024-01-30 17:03:14 -05:00
import 'package:veilid_support/veilid_support.dart';
2024-06-18 21:20:06 -04:00
import '../../account_manager/account_manager.dart';
2024-01-30 17:03:14 -05:00
import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart';
//////////////////////////////////////////////////
// Mutable state for per-account contacts
class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
ContactListCubit({
2024-06-18 21:20:06 -04:00
required AccountInfo accountInfo,
2024-06-15 23:29:15 -04:00
required OwnedDHTRecordPointer contactListRecordPointer,
2024-06-21 22:44:35 -04:00
}) : super(
2024-06-18 21:20:06 -04:00
open: () =>
_open(accountInfo.accountRecordKey, contactListRecordPointer),
2024-01-30 17:03:14 -05:00
decodeElement: proto.Contact.fromBuffer);
2024-06-15 23:29:15 -04:00
static Future<DHTShortArray> _open(TypedKey accountRecordKey,
OwnedDHTRecordPointer contactListRecordPointer) async {
final dhtRecord = await DHTShortArray.openOwned(contactListRecordPointer,
debugName: 'ContactListCubit::_open::ContactList',
2024-01-30 17:03:14 -05:00
parent: accountRecordKey);
return dhtRecord;
}
2024-06-15 00:01:08 -04:00
@override
Future<void> close() async {
await _contactProfileUpdateMap.close();
await super.close();
}
////////////////////////////////////////////////////////////////////////////
// Public Interface
void followContactProfileChanges(TypedKey localConversationRecordKey,
Stream<proto.Profile?> profileStream, proto.Profile? profileState) {
_contactProfileUpdateMap
.follow(localConversationRecordKey, profileStream, profileState,
(remoteProfile) async {
if (remoteProfile == null) {
return;
}
2024-06-15 23:29:15 -04:00
return updateContactProfile(
2024-06-15 00:01:08 -04:00
localConversationRecordKey: localConversationRecordKey,
2024-06-15 23:29:15 -04:00
profile: remoteProfile);
2024-06-15 00:01:08 -04:00
});
}
2024-06-15 23:29:15 -04:00
Future<void> updateContactProfile({
2024-06-15 00:01:08 -04:00
required TypedKey localConversationRecordKey,
2024-06-15 23:29:15 -04:00
required proto.Profile profile,
2024-06-15 00:01:08 -04:00
}) async {
// Update contact's remoteProfile
await operateWriteEventual((writer) async {
for (var pos = 0; pos < writer.length; pos++) {
final c = await writer.getProtobuf(proto.Contact.fromBuffer, pos);
if (c != null &&
c.localConversationRecordKey.toVeilid() ==
localConversationRecordKey) {
2024-06-15 23:29:15 -04:00
if (c.profile == profile) {
2024-06-15 00:01:08 -04:00
// Unchanged
break;
}
2024-06-15 23:29:15 -04:00
final newContact = c.deepCopy()..profile = profile;
2024-06-15 00:01:08 -04:00
final updated = await writer.tryWriteItemProtobuf(
proto.Contact.fromBuffer, pos, newContact);
if (!updated) {
2024-07-31 13:04:43 -04:00
throw const DHTExceptionOutdated();
}
break;
}
}
});
}
Future<void> updateContactFields({
required TypedKey localConversationRecordKey,
String? nickname,
String? notes,
bool? showAvailability,
}) async {
// Update contact's locally-modifiable fields
await operateWriteEventual((writer) async {
for (var pos = 0; pos < writer.length; pos++) {
final c = await writer.getProtobuf(proto.Contact.fromBuffer, pos);
if (c != null &&
c.localConversationRecordKey.toVeilid() ==
localConversationRecordKey) {
final newContact = c.deepCopy();
if (nickname != null) {
newContact.nickname = nickname;
}
if (notes != null) {
newContact.notes = notes;
}
if (showAvailability != null) {
newContact.showAvailability = showAvailability;
}
final updated = await writer.tryWriteItemProtobuf(
proto.Contact.fromBuffer, pos, newContact);
if (!updated) {
throw const DHTExceptionOutdated();
2024-06-15 00:01:08 -04:00
}
2024-06-15 23:29:15 -04:00
break;
2024-06-15 00:01:08 -04:00
}
}
});
}
2024-01-30 17:03:14 -05:00
Future<void> createContact({
2024-06-15 23:29:15 -04:00
required proto.Profile profile,
2024-06-07 14:42:04 -04:00
required SuperIdentity remoteSuperIdentity,
2024-01-30 17:03:14 -05:00
required TypedKey localConversationRecordKey,
2024-06-15 23:29:15 -04:00
required TypedKey remoteConversationRecordKey,
2024-01-30 17:03:14 -05:00
}) async {
// Create Contact
final contact = proto.Contact()
2024-06-15 23:29:15 -04:00
..profile = profile
2024-06-07 14:42:04 -04:00
..superIdentityJson = jsonEncode(remoteSuperIdentity.toJson())
..identityPublicKey =
remoteSuperIdentity.currentInstance.typedPublicKey.toProto()
2024-01-30 17:03:14 -05:00
..localConversationRecordKey = localConversationRecordKey.toProto()
2024-06-15 23:29:15 -04:00
..remoteConversationRecordKey = remoteConversationRecordKey.toProto()
2024-01-30 17:03:14 -05:00
..showAvailability = false;
// Add Contact to account's list
2024-06-21 22:44:35 -04:00
await operateWriteEventual((writer) async {
2024-06-09 14:43:28 -04:00
await writer.add(contact.writeToBuffer());
2024-02-27 12:45:58 -05:00
});
2024-01-30 17:03:14 -05:00
}
2024-06-15 23:29:15 -04:00
Future<void> deleteContact(
{required TypedKey localConversationRecordKey}) async {
2024-01-30 17:03:14 -05:00
// Remove Contact from account's list
2024-06-21 22:44:35 -04:00
final deletedItem = await operateWriteEventual((writer) async {
2024-04-01 09:04:54 -04:00
for (var i = 0; i < writer.length; i++) {
2024-05-28 22:01:50 -04:00
final item = await writer.getProtobuf(proto.Contact.fromBuffer, i);
2024-02-27 12:45:58 -05:00
if (item == null) {
throw Exception('Failed to get contact');
}
2024-06-15 23:29:15 -04:00
if (item.localConversationRecordKey.toVeilid() ==
localConversationRecordKey) {
2024-05-28 22:01:50 -04:00
await writer.remove(i);
2024-05-13 10:03:25 -04:00
return item;
2024-02-27 12:45:58 -05:00
}
2024-01-30 17:03:14 -05:00
}
2024-03-28 22:46:26 -04:00
return null;
});
2024-05-13 10:03:25 -04:00
if (deletedItem != null) {
2024-02-27 12:45:58 -05:00
try {
2024-06-21 22:44:35 -04:00
// Mark the conversation records for deletion
await DHTRecordPool.instance
.deleteRecord(deletedItem.localConversationRecordKey.toVeilid());
await DHTRecordPool.instance
.deleteRecord(deletedItem.remoteConversationRecordKey.toVeilid());
2024-02-27 12:45:58 -05:00
} on Exception catch (e) {
log.debug('error deleting conversation records: $e', e);
2024-01-30 17:03:14 -05:00
}
2024-03-28 22:46:26 -04:00
}
2024-01-30 17:03:14 -05:00
}
2024-06-15 00:01:08 -04:00
final _contactProfileUpdateMap =
SingleStateProcessorMap<TypedKey, proto.Profile?>();
2024-01-30 17:03:14 -05:00
}