mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
ui cleanup
This commit is contained in:
parent
7b400ed08b
commit
152c8bdff4
@ -4,7 +4,6 @@ import 'package:async_tools/async_tools.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_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:protobuf/protobuf.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';
|
||||||
@ -82,6 +81,16 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
await _sentMessagesCubit?.close();
|
await _sentMessagesCubit?.close();
|
||||||
await _rcvdMessagesCubit?.close();
|
await _rcvdMessagesCubit?.close();
|
||||||
await _reconciledMessagesCubit?.close();
|
await _reconciledMessagesCubit?.close();
|
||||||
|
|
||||||
|
// If the local conversation record is gone, then delete the reconciled
|
||||||
|
// messages table as well
|
||||||
|
final conversationDead = await DHTRecordPool.instance
|
||||||
|
.isDeletedRecordKey(_localConversationRecordKey);
|
||||||
|
if (conversationDead) {
|
||||||
|
await SingleContactMessagesCubit.cleanupAndDeleteMessages(
|
||||||
|
localConversationRecordKey: _localConversationRecordKey);
|
||||||
|
}
|
||||||
|
|
||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,8 +301,14 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
previousMessage = message;
|
previousMessage = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// _sendingMessages = messages;
|
||||||
|
|
||||||
|
// _renderState();
|
||||||
|
|
||||||
await _sentMessagesCubit!.operateAppendEventual((writer) =>
|
await _sentMessagesCubit!.operateAppendEventual((writer) =>
|
||||||
writer.addAll(messages.map((m) => m.writeToBuffer()).toList()));
|
writer.addAll(messages.map((m) => m.writeToBuffer()).toList()));
|
||||||
|
|
||||||
|
// _sendingMessages = const IList.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Produce a state for this cubit from the input cubits and queues
|
// Produce a state for this cubit from the input cubits and queues
|
||||||
@ -304,7 +319,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
// Get all sent messages
|
// Get all sent messages
|
||||||
final sentMessages = _sentMessagesCubit?.state.state.asData?.value;
|
final sentMessages = _sentMessagesCubit?.state.state.asData?.value;
|
||||||
//Get all items in the unsent queue
|
//Get all items in the unsent queue
|
||||||
final unsentMessages = _unsentMessagesQueue.queue;
|
//final unsentMessages = _unsentMessagesQueue.queue;
|
||||||
|
|
||||||
// If we aren't ready to render a state, say we're loading
|
// If we aren't ready to render a state, say we're loading
|
||||||
if (reconciledMessages == null || sentMessages == null) {
|
if (reconciledMessages == null || sentMessages == null) {
|
||||||
@ -329,7 +344,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
// );
|
// );
|
||||||
|
|
||||||
final renderedElements = <RenderStateElement>[];
|
final renderedElements = <RenderStateElement>[];
|
||||||
|
final renderedIds = <String>{};
|
||||||
for (final m in reconciledMessages.windowElements) {
|
for (final m in reconciledMessages.windowElements) {
|
||||||
final isLocal =
|
final isLocal =
|
||||||
m.content.author.toVeilid() == _accountInfo.identityTypedPublicKey;
|
m.content.author.toVeilid() == _accountInfo.identityTypedPublicKey;
|
||||||
@ -346,13 +361,22 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
sent: sent,
|
sent: sent,
|
||||||
sentOffline: sentOffline,
|
sentOffline: sentOffline,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
renderedIds.add(m.content.authorUniqueIdString);
|
||||||
}
|
}
|
||||||
for (final m in unsentMessages) {
|
|
||||||
renderedElements.add(RenderStateElement(
|
// Render in-flight messages at the bottom
|
||||||
message: (m.deepCopy())..id = m.timestamp.toBytes(),
|
// for (final m in _sendingMessages) {
|
||||||
isLocal: true,
|
// if (renderedIds.contains(m.authorUniqueIdString)) {
|
||||||
));
|
// continue;
|
||||||
}
|
// }
|
||||||
|
// renderedElements.add(RenderStateElement(
|
||||||
|
// message: m,
|
||||||
|
// isLocal: true,
|
||||||
|
// sent: true,
|
||||||
|
// sentOffline: true,
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
// Render the state
|
// Render the state
|
||||||
final messages = renderedElements
|
final messages = renderedElements
|
||||||
@ -426,7 +450,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
late final MessageReconciliation _reconciliation;
|
late final MessageReconciliation _reconciliation;
|
||||||
|
|
||||||
late final PersistentQueue<proto.Message> _unsentMessagesQueue;
|
late final PersistentQueue<proto.Message> _unsentMessagesQueue;
|
||||||
|
// IList<proto.Message> _sendingMessages = const IList.empty();
|
||||||
StreamSubscription<DHTLogBusyState<proto.Message>>? _sentSubscription;
|
StreamSubscription<DHTLogBusyState<proto.Message>>? _sentSubscription;
|
||||||
StreamSubscription<DHTLogBusyState<proto.Message>>? _rcvdSubscription;
|
StreamSubscription<DHTLogBusyState<proto.Message>>? _rcvdSubscription;
|
||||||
StreamSubscription<TableDBArrayProtobufBusyState<proto.ReconciledMessage>>?
|
StreamSubscription<TableDBArrayProtobufBusyState<proto.ReconciledMessage>>?
|
||||||
|
@ -8,7 +8,6 @@ import 'package:veilid_support/veilid_support.dart';
|
|||||||
import '../../account_manager/account_manager.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../../chat/chat.dart';
|
import '../../chat/chat.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../../tools/tools.dart';
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
|
|
||||||
@ -58,9 +57,20 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
|||||||
final remoteConversationRecordKey =
|
final remoteConversationRecordKey =
|
||||||
contact.remoteConversationRecordKey.toVeilid();
|
contact.remoteConversationRecordKey.toVeilid();
|
||||||
|
|
||||||
|
// Create 1:1 conversation type Chat
|
||||||
|
final chatMember = proto.ChatMember()
|
||||||
|
..remoteIdentityPublicKey = remoteIdentityPublicKey.toProto()
|
||||||
|
..remoteConversationRecordKey = remoteConversationRecordKey.toProto();
|
||||||
|
|
||||||
|
final directChat = proto.DirectChat()
|
||||||
|
..settings = await getDefaultChatSettings(contact)
|
||||||
|
..localConversationRecordKey = localConversationRecordKey.toProto()
|
||||||
|
..remoteMember = chatMember;
|
||||||
|
|
||||||
|
final chat = proto.Chat()..direct = directChat;
|
||||||
|
|
||||||
// Add Chat to account's list
|
// Add Chat to account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
await operateWriteEventual((writer) async {
|
||||||
await operateWrite((writer) async {
|
|
||||||
// See if we have added this chat already
|
// See if we have added this chat already
|
||||||
for (var i = 0; i < writer.length; i++) {
|
for (var i = 0; i < writer.length; i++) {
|
||||||
final cbuf = await writer.get(i);
|
final cbuf = await writer.get(i);
|
||||||
@ -89,18 +99,6 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create 1:1 conversation type Chat
|
|
||||||
final chatMember = proto.ChatMember()
|
|
||||||
..remoteIdentityPublicKey = remoteIdentityPublicKey.toProto()
|
|
||||||
..remoteConversationRecordKey = remoteConversationRecordKey.toProto();
|
|
||||||
|
|
||||||
final directChat = proto.DirectChat()
|
|
||||||
..settings = await getDefaultChatSettings(contact)
|
|
||||||
..localConversationRecordKey = localConversationRecordKey.toProto()
|
|
||||||
..remoteMember = chatMember;
|
|
||||||
|
|
||||||
final chat = proto.Chat()..direct = directChat;
|
|
||||||
|
|
||||||
// Add chat
|
// Add chat
|
||||||
await writer.add(chat.writeToBuffer());
|
await writer.add(chat.writeToBuffer());
|
||||||
});
|
});
|
||||||
@ -110,37 +108,22 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
|||||||
Future<void> deleteChat(
|
Future<void> deleteChat(
|
||||||
{required TypedKey localConversationRecordKey}) async {
|
{required TypedKey localConversationRecordKey}) async {
|
||||||
// Remove Chat from account's list
|
// Remove Chat from account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
await operateWriteEventual((writer) async {
|
||||||
final deletedItem =
|
if (_activeChatCubit.state == localConversationRecordKey) {
|
||||||
// Ensure followers get their changes before we return
|
_activeChatCubit.setActiveChat(null);
|
||||||
await syncFollowers(() => operateWrite((writer) async {
|
|
||||||
if (_activeChatCubit.state == localConversationRecordKey) {
|
|
||||||
_activeChatCubit.setActiveChat(null);
|
|
||||||
}
|
|
||||||
for (var i = 0; i < writer.length; i++) {
|
|
||||||
final c = await writer.getProtobuf(proto.Chat.fromBuffer, i);
|
|
||||||
if (c == null) {
|
|
||||||
throw Exception('Failed to get chat');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c.localConversationRecordKey ==
|
|
||||||
localConversationRecordKey) {
|
|
||||||
await writer.remove(i);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}));
|
|
||||||
// Since followers are synced, we can safetly remove the reconciled
|
|
||||||
// chat record now
|
|
||||||
if (deletedItem != null) {
|
|
||||||
try {
|
|
||||||
await SingleContactMessagesCubit.cleanupAndDeleteMessages(
|
|
||||||
localConversationRecordKey: localConversationRecordKey);
|
|
||||||
} on Exception catch (e) {
|
|
||||||
log.debug('error removing reconciled chat table: $e', e);
|
|
||||||
}
|
}
|
||||||
}
|
for (var i = 0; i < writer.length; i++) {
|
||||||
|
final c = await writer.getProtobuf(proto.Chat.fromBuffer, i);
|
||||||
|
if (c == null) {
|
||||||
|
throw Exception('Failed to get chat');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.localConversationRecordKey == localConversationRecordKey) {
|
||||||
|
await writer.remove(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// StateMapFollowable /////////////////////////
|
/// StateMapFollowable /////////////////////////
|
||||||
|
@ -152,8 +152,7 @@ class ContactInvitationListCubit
|
|||||||
..message = message;
|
..message = message;
|
||||||
|
|
||||||
// Add ContactInvitationRecord to account's list
|
// Add ContactInvitationRecord to account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
await operateWriteEventual((writer) async {
|
||||||
await operateWrite((writer) async {
|
|
||||||
await writer.add(cinvrec.writeToBuffer());
|
await writer.add(cinvrec.writeToBuffer());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -46,7 +46,6 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
|
|||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
//final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
|
|
||||||
final signedContactInvitationBytesV =
|
final signedContactInvitationBytesV =
|
||||||
@ -58,6 +57,9 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
|
|||||||
return PopControl(
|
return PopControl(
|
||||||
dismissible: !signedContactInvitationBytesV.isLoading,
|
dismissible: !signedContactInvitationBytesV.isLoading,
|
||||||
child: Dialog(
|
child: Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
side: const BorderSide(width: 2),
|
||||||
|
borderRadius: BorderRadius.circular(16)),
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
@ -90,6 +92,10 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
|
|||||||
.paddingAll(8),
|
.paddingAll(8),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.copy),
|
icon: const Icon(Icons.copy),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.black,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
side: const BorderSide()),
|
||||||
label: Text(translate(
|
label: Text(translate(
|
||||||
'create_invitation_dialog.copy_invitation')),
|
'create_invitation_dialog.copy_invitation')),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
@ -6,7 +6,6 @@ import 'package:protobuf/protobuf.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 '../../conversation/conversation.dart';
|
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
|
|
||||||
@ -17,8 +16,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
ContactListCubit({
|
ContactListCubit({
|
||||||
required AccountInfo accountInfo,
|
required AccountInfo accountInfo,
|
||||||
required OwnedDHTRecordPointer contactListRecordPointer,
|
required OwnedDHTRecordPointer contactListRecordPointer,
|
||||||
}) : _accountInfo = accountInfo,
|
}) : super(
|
||||||
super(
|
|
||||||
open: () =>
|
open: () =>
|
||||||
_open(accountInfo.accountRecordKey, contactListRecordPointer),
|
_open(accountInfo.accountRecordKey, contactListRecordPointer),
|
||||||
decodeElement: proto.Contact.fromBuffer);
|
decodeElement: proto.Contact.fromBuffer);
|
||||||
@ -98,8 +96,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
..showAvailability = false;
|
..showAvailability = false;
|
||||||
|
|
||||||
// Add Contact to account's list
|
// Add Contact to account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
await operateWriteEventual((writer) async {
|
||||||
await operateWrite((writer) async {
|
|
||||||
await writer.add(contact.writeToBuffer());
|
await writer.add(contact.writeToBuffer());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -107,7 +104,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
Future<void> deleteContact(
|
Future<void> deleteContact(
|
||||||
{required TypedKey localConversationRecordKey}) async {
|
{required TypedKey localConversationRecordKey}) async {
|
||||||
// Remove Contact from account's list
|
// Remove Contact from account's list
|
||||||
final deletedItem = await operateWrite((writer) async {
|
final deletedItem = await operateWriteEventual((writer) async {
|
||||||
for (var i = 0; i < writer.length; i++) {
|
for (var i = 0; i < writer.length; i++) {
|
||||||
final item = await writer.getProtobuf(proto.Contact.fromBuffer, i);
|
final item = await writer.getProtobuf(proto.Contact.fromBuffer, i);
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
@ -124,18 +121,11 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
|
|
||||||
if (deletedItem != null) {
|
if (deletedItem != null) {
|
||||||
try {
|
try {
|
||||||
// Make a conversation cubit to manipulate the conversation
|
// Mark the conversation records for deletion
|
||||||
final conversationCubit = ConversationCubit(
|
await DHTRecordPool.instance
|
||||||
accountInfo: _accountInfo,
|
.deleteRecord(deletedItem.localConversationRecordKey.toVeilid());
|
||||||
remoteIdentityPublicKey: deletedItem.identityPublicKey.toVeilid(),
|
await DHTRecordPool.instance
|
||||||
localConversationRecordKey:
|
.deleteRecord(deletedItem.remoteConversationRecordKey.toVeilid());
|
||||||
deletedItem.localConversationRecordKey.toVeilid(),
|
|
||||||
remoteConversationRecordKey:
|
|
||||||
deletedItem.remoteConversationRecordKey.toVeilid(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete the local and remote conversation records
|
|
||||||
await conversationCubit.delete();
|
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
log.debug('error deleting conversation records: $e', e);
|
log.debug('error deleting conversation records: $e', e);
|
||||||
}
|
}
|
||||||
@ -144,5 +134,4 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
|
|
||||||
final _contactProfileUpdateMap =
|
final _contactProfileUpdateMap =
|
||||||
SingleStateProcessorMap<TypedKey, proto.Profile?>();
|
SingleStateProcessorMap<TypedKey, proto.Profile?>();
|
||||||
final AccountInfo _accountInfo;
|
|
||||||
}
|
}
|
||||||
|
@ -74,13 +74,13 @@ class ContactItemWidget extends StatelessWidget {
|
|||||||
final contactListCubit = context.read<ContactListCubit>();
|
final contactListCubit = context.read<ContactListCubit>();
|
||||||
final chatListCubit = context.read<ChatListCubit>();
|
final chatListCubit = context.read<ChatListCubit>();
|
||||||
|
|
||||||
// Remove any chats for this contact
|
|
||||||
await chatListCubit.deleteChat(
|
|
||||||
localConversationRecordKey: localConversationRecordKey);
|
|
||||||
|
|
||||||
// Delete the contact itself
|
// Delete the contact itself
|
||||||
await contactListCubit.deleteContact(
|
await contactListCubit.deleteContact(
|
||||||
localConversationRecordKey: localConversationRecordKey);
|
localConversationRecordKey: localConversationRecordKey);
|
||||||
|
|
||||||
|
// Remove any chats for this contact
|
||||||
|
await chatListCubit.deleteChat(
|
||||||
|
localConversationRecordKey: localConversationRecordKey);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -28,7 +28,6 @@ class _SingleContactChatState extends Equatable {
|
|||||||
final TypedKey remoteMessagesRecordKey;
|
final TypedKey remoteMessagesRecordKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement props
|
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
remoteIdentityPublicKey,
|
remoteIdentityPublicKey,
|
||||||
localConversationRecordKey,
|
localConversationRecordKey,
|
||||||
|
@ -14,7 +14,6 @@ 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';
|
|
||||||
|
|
||||||
const _sfUpdateAccountChange = 'updateAccountChange';
|
const _sfUpdateAccountChange = 'updateAccountChange';
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
final accountRecordKey = _accountInfo.accountRecordKey;
|
final accountRecordKey = _accountInfo.accountRecordKey;
|
||||||
final writer = _accountInfo.identityWriter;
|
final writer = _accountInfo.identityWriter;
|
||||||
|
|
||||||
// Open with SMPL scheme for identity writer
|
// Open with SMPL schema for identity writer
|
||||||
late final DHTRecord localConversationRecord;
|
late final DHTRecord localConversationRecord;
|
||||||
if (existingConversationRecordKey != null) {
|
if (existingConversationRecordKey != null) {
|
||||||
localConversationRecord = await pool.openRecordWrite(
|
localConversationRecord = await pool.openRecordWrite(
|
||||||
@ -171,57 +170,6 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete the conversation keys associated with this conversation
|
|
||||||
Future<bool> delete() async {
|
|
||||||
final pool = DHTRecordPool.instance;
|
|
||||||
|
|
||||||
await _initWait();
|
|
||||||
final localConversationCubit = _localConversationCubit;
|
|
||||||
final remoteConversationCubit = _remoteConversationCubit;
|
|
||||||
|
|
||||||
final deleteSet = DelayedWaitSet<void>();
|
|
||||||
|
|
||||||
if (localConversationCubit != null) {
|
|
||||||
final data = localConversationCubit.state.asData;
|
|
||||||
if (data == null) {
|
|
||||||
log.warning('could not delete local conversation');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteSet.add(() async {
|
|
||||||
_localConversationCubit = null;
|
|
||||||
await localConversationCubit.close();
|
|
||||||
final conversation = data.value;
|
|
||||||
final messagesKey = conversation.messages.toVeilid();
|
|
||||||
await pool.deleteRecord(messagesKey);
|
|
||||||
await pool.deleteRecord(_localConversationRecordKey!);
|
|
||||||
_localConversationRecordKey = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remoteConversationCubit != null) {
|
|
||||||
final data = remoteConversationCubit.state.asData;
|
|
||||||
if (data == null) {
|
|
||||||
log.warning('could not delete remote conversation');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteSet.add(() async {
|
|
||||||
_remoteConversationCubit = null;
|
|
||||||
await remoteConversationCubit.close();
|
|
||||||
final conversation = data.value;
|
|
||||||
final messagesKey = conversation.messages.toVeilid();
|
|
||||||
await pool.deleteRecord(messagesKey);
|
|
||||||
await pool.deleteRecord(_remoteConversationRecordKey!);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit the delete futures
|
|
||||||
await deleteSet();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Force refresh of conversation keys
|
/// Force refresh of conversation keys
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
await _initWait();
|
await _initWait();
|
||||||
|
@ -71,11 +71,22 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
shortname = abbrev;
|
shortname = abbrev;
|
||||||
}
|
}
|
||||||
|
|
||||||
final avatar = AvatarImage(
|
final avatar = Container(
|
||||||
size: 32,
|
height: 34,
|
||||||
backgroundColor: loggedIn ? scale.primary : scale.elementBackground,
|
width: 34,
|
||||||
foregroundColor: loggedIn ? scale.primaryText : scale.subtleText,
|
decoration: BoxDecoration(
|
||||||
child: Text(shortname, style: theme.textTheme.titleLarge));
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: loggedIn ? scale.border : scale.subtleBorder,
|
||||||
|
width: 2,
|
||||||
|
strokeAlign: BorderSide.strokeAlignOutside),
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
child: AvatarImage(
|
||||||
|
//size: 32,
|
||||||
|
backgroundColor: loggedIn ? scale.primary : scale.elementBackground,
|
||||||
|
foregroundColor: loggedIn ? scale.primaryText : scale.subtleText,
|
||||||
|
child: Text(shortname, style: theme.textTheme.titleLarge)));
|
||||||
|
|
||||||
return AnimatedPadding(
|
return AnimatedPadding(
|
||||||
padding: EdgeInsets.fromLTRB(selected ? 0 : 0, 0, selected ? 0 : 8, 0),
|
padding: EdgeInsets.fromLTRB(selected ? 0 : 0, 0, selected ? 0 : 8, 0),
|
||||||
@ -234,6 +245,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
//final textTheme = theme.textTheme;
|
//final textTheme = theme.textTheme;
|
||||||
final localAccounts = context.watch<LocalAccountsCubit>().state;
|
final localAccounts = context.watch<LocalAccountsCubit>().state;
|
||||||
final perAccountCollectionBlocMapState =
|
final perAccountCollectionBlocMapState =
|
||||||
@ -269,13 +281,17 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
|||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
SvgPicture.asset(
|
SvgPicture.asset(
|
||||||
height: 48,
|
height: 48,
|
||||||
'assets/images/icon.svg',
|
'assets/images/icon.svg',
|
||||||
).paddingLTRB(0, 0, 16, 0),
|
colorFilter: scaleConfig.useVisualIndicators
|
||||||
|
? grayColorFilter
|
||||||
|
: null)
|
||||||
|
.paddingLTRB(0, 0, 16, 0),
|
||||||
SvgPicture.asset(
|
SvgPicture.asset(
|
||||||
height: 48,
|
height: 48,
|
||||||
'assets/images/title.svg',
|
'assets/images/title.svg',
|
||||||
),
|
colorFilter:
|
||||||
|
scaleConfig.useVisualIndicators ? grayColorFilter : null),
|
||||||
])),
|
])),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
_getAccountList(
|
_getAccountList(
|
||||||
|
@ -89,11 +89,9 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
|
|||||||
onError: errorScale.primaryText,
|
onError: errorScale.primaryText,
|
||||||
// errorContainer: errorScale.hoverElementBackground,
|
// errorContainer: errorScale.hoverElementBackground,
|
||||||
// onErrorContainer: errorScale.subtleText,
|
// onErrorContainer: errorScale.subtleText,
|
||||||
background: grayScale.appBackground, // reviewed
|
|
||||||
onBackground: grayScale.appText, // reviewed
|
|
||||||
surface: primaryScale.primary, // reviewed
|
surface: primaryScale.primary, // reviewed
|
||||||
onSurface: primaryScale.primaryText, // reviewed
|
onSurface: primaryScale.primaryText, // reviewed
|
||||||
surfaceVariant: secondaryScale.primary,
|
surfaceContainerHighest: secondaryScale.primary,
|
||||||
onSurfaceVariant: secondaryScale.primaryText, // ?? reviewed a little
|
onSurfaceVariant: secondaryScale.primaryText, // ?? reviewed a little
|
||||||
outline: primaryScale.border,
|
outline: primaryScale.border,
|
||||||
outlineVariant: secondaryScale.border,
|
outlineVariant: secondaryScale.border,
|
||||||
|
@ -5,7 +5,6 @@ import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.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_spinkit/flutter_spinkit.dart';
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
|
||||||
import 'package:motion_toast/motion_toast.dart';
|
import 'package:motion_toast/motion_toast.dart';
|
||||||
import 'package:quickalert/quickalert.dart';
|
import 'package:quickalert/quickalert.dart';
|
||||||
|
|
||||||
@ -122,36 +121,45 @@ Future<void> showErrorModal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void showErrorToast(BuildContext context, String message) {
|
void showErrorToast(BuildContext context, String message) {
|
||||||
MotionToast.error(
|
final theme = Theme.of(context);
|
||||||
title: Text(translate('toast.error')),
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
|
|
||||||
|
MotionToast(
|
||||||
|
//title: Text(translate('toast.error')),
|
||||||
description: Text(message),
|
description: Text(message),
|
||||||
|
constraints: BoxConstraints.loose(const Size(400, 100)),
|
||||||
|
contentPadding: const EdgeInsets.all(16),
|
||||||
|
primaryColor: scale.errorScale.elementBackground,
|
||||||
|
secondaryColor: scale.errorScale.calloutBackground,
|
||||||
|
borderRadius: 16,
|
||||||
|
toastDuration: const Duration(seconds: 4),
|
||||||
|
animationDuration: const Duration(milliseconds: 1000),
|
||||||
|
displayBorder: scaleConfig.useVisualIndicators,
|
||||||
|
icon: Icons.error,
|
||||||
).show(context);
|
).show(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showInfoToast(BuildContext context, String message) {
|
void showInfoToast(BuildContext context, String message) {
|
||||||
MotionToast.info(
|
final theme = Theme.of(context);
|
||||||
title: Text(translate('toast.info')),
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
|
|
||||||
|
MotionToast(
|
||||||
|
//title: Text(translate('toast.info')),
|
||||||
description: Text(message),
|
description: Text(message),
|
||||||
|
constraints: BoxConstraints.loose(const Size(400, 100)),
|
||||||
|
contentPadding: const EdgeInsets.all(16),
|
||||||
|
primaryColor: scale.tertiaryScale.elementBackground,
|
||||||
|
secondaryColor: scale.tertiaryScale.calloutBackground,
|
||||||
|
borderRadius: 16,
|
||||||
|
toastDuration: const Duration(seconds: 2),
|
||||||
|
animationDuration: const Duration(milliseconds: 500),
|
||||||
|
displayBorder: scaleConfig.useVisualIndicators,
|
||||||
|
icon: Icons.info,
|
||||||
).show(context);
|
).show(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget insetBorder(
|
|
||||||
// {required BuildContext context,
|
|
||||||
// required bool enabled,
|
|
||||||
// required Color color,
|
|
||||||
// required Widget child}) {
|
|
||||||
// if (!enabled) {
|
|
||||||
// return child;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return Stack({
|
|
||||||
// children: [] {
|
|
||||||
// DecoratedBox(decoration: BoxDecoration()
|
|
||||||
// child,
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
Widget styledTitleContainer({
|
Widget styledTitleContainer({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required String title,
|
required String title,
|
||||||
@ -230,3 +238,26 @@ Widget styledBottomSheet({
|
|||||||
bool get isPlatformDark =>
|
bool get isPlatformDark =>
|
||||||
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
||||||
Brightness.dark;
|
Brightness.dark;
|
||||||
|
|
||||||
|
const grayColorFilter = ColorFilter.matrix(<double>[
|
||||||
|
0.2126,
|
||||||
|
0.7152,
|
||||||
|
0.0722,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.2126,
|
||||||
|
0.7152,
|
||||||
|
0.0722,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.2126,
|
||||||
|
0.7152,
|
||||||
|
0.0722,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
]);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
part of 'dht_record_pool.dart';
|
part of 'dht_record_pool.dart';
|
||||||
|
|
||||||
const _sfListen = 'listen';
|
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class DHTRecordWatchChange extends Equatable {
|
class DHTRecordWatchChange extends Equatable {
|
||||||
const DHTRecordWatchChange(
|
const DHTRecordWatchChange(
|
||||||
@ -41,7 +39,7 @@ enum DHTRecordRefreshMode {
|
|||||||
class DHTRecord implements DHTDeleteable<DHTRecord> {
|
class DHTRecord implements DHTDeleteable<DHTRecord> {
|
||||||
DHTRecord._(
|
DHTRecord._(
|
||||||
{required VeilidRoutingContext routingContext,
|
{required VeilidRoutingContext routingContext,
|
||||||
required SharedDHTRecordData sharedDHTRecordData,
|
required _SharedDHTRecordData sharedDHTRecordData,
|
||||||
required int defaultSubkey,
|
required int defaultSubkey,
|
||||||
required KeyPair? writer,
|
required KeyPair? writer,
|
||||||
required VeilidCrypto crypto,
|
required VeilidCrypto crypto,
|
||||||
@ -241,7 +239,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord> {
|
|||||||
// if so, shortcut and don't bother decrypting it
|
// if so, shortcut and don't bother decrypting it
|
||||||
if (newValueData.data.equals(encryptedNewValue)) {
|
if (newValueData.data.equals(encryptedNewValue)) {
|
||||||
if (isUpdated) {
|
if (isUpdated) {
|
||||||
DHTRecordPool.instance.processLocalValueChange(key, newValue, subkey);
|
DHTRecordPool.instance._processLocalValueChange(key, newValue, subkey);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -251,7 +249,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord> {
|
|||||||
await (crypto ?? _crypto).decrypt(newValueData.data);
|
await (crypto ?? _crypto).decrypt(newValueData.data);
|
||||||
if (isUpdated) {
|
if (isUpdated) {
|
||||||
DHTRecordPool.instance
|
DHTRecordPool.instance
|
||||||
.processLocalValueChange(key, decryptedNewValue, subkey);
|
._processLocalValueChange(key, decryptedNewValue, subkey);
|
||||||
}
|
}
|
||||||
return decryptedNewValue;
|
return decryptedNewValue;
|
||||||
}
|
}
|
||||||
@ -298,7 +296,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord> {
|
|||||||
|
|
||||||
final isUpdated = newValueData.seq != lastSeq;
|
final isUpdated = newValueData.seq != lastSeq;
|
||||||
if (isUpdated) {
|
if (isUpdated) {
|
||||||
DHTRecordPool.instance.processLocalValueChange(key, newValue, subkey);
|
DHTRecordPool.instance._processLocalValueChange(key, newValue, subkey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,7 +417,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord> {
|
|||||||
// Set up watch requirements which will get picked up by the next tick
|
// Set up watch requirements which will get picked up by the next tick
|
||||||
final oldWatchState = watchState;
|
final oldWatchState = watchState;
|
||||||
watchState =
|
watchState =
|
||||||
WatchState(subkeys: subkeys, expiration: expiration, count: count);
|
_WatchState(subkeys: subkeys, expiration: expiration, count: count);
|
||||||
if (oldWatchState != watchState) {
|
if (oldWatchState != watchState) {
|
||||||
_sharedDHTRecordData.needsWatchStateUpdate = true;
|
_sharedDHTRecordData.needsWatchStateUpdate = true;
|
||||||
}
|
}
|
||||||
@ -544,7 +542,7 @@ class DHTRecord implements DHTDeleteable<DHTRecord> {
|
|||||||
|
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
final SharedDHTRecordData _sharedDHTRecordData;
|
final _SharedDHTRecordData _sharedDHTRecordData;
|
||||||
final VeilidRoutingContext _routingContext;
|
final VeilidRoutingContext _routingContext;
|
||||||
final int _defaultSubkey;
|
final int _defaultSubkey;
|
||||||
final KeyPair? _writer;
|
final KeyPair? _writer;
|
||||||
@ -554,5 +552,5 @@ class DHTRecord implements DHTDeleteable<DHTRecord> {
|
|||||||
int _openCount;
|
int _openCount;
|
||||||
StreamController<DHTRecordWatchChange>? _watchController;
|
StreamController<DHTRecordWatchChange>? _watchController;
|
||||||
@internal
|
@internal
|
||||||
WatchState? watchState;
|
_WatchState? watchState;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,77 @@
|
|||||||
|
part of 'dht_record_pool.dart';
|
||||||
|
|
||||||
|
const int _watchBackoffMultiplier = 2;
|
||||||
|
const int _watchBackoffMax = 30;
|
||||||
|
|
||||||
|
const int? _defaultWatchDurationSecs = null; // 600
|
||||||
|
const int _watchRenewalNumerator = 4;
|
||||||
|
const int _watchRenewalDenominator = 5;
|
||||||
|
|
||||||
|
// DHT crypto domain
|
||||||
|
const String _cryptoDomainDHT = 'dht';
|
||||||
|
|
||||||
|
// Singlefuture keys
|
||||||
|
const _sfPollWatch = '_pollWatch';
|
||||||
|
const _sfListen = 'listen';
|
||||||
|
|
||||||
|
/// Watch state
|
||||||
|
@immutable
|
||||||
|
class _WatchState extends Equatable {
|
||||||
|
const _WatchState(
|
||||||
|
{required this.subkeys,
|
||||||
|
required this.expiration,
|
||||||
|
required this.count,
|
||||||
|
this.realExpiration,
|
||||||
|
this.renewalTime});
|
||||||
|
final List<ValueSubkeyRange>? subkeys;
|
||||||
|
final Timestamp? expiration;
|
||||||
|
final int? count;
|
||||||
|
final Timestamp? realExpiration;
|
||||||
|
final Timestamp? renewalTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props =>
|
||||||
|
[subkeys, expiration, count, realExpiration, renewalTime];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data shared amongst all DHTRecord instances
|
||||||
|
class _SharedDHTRecordData {
|
||||||
|
_SharedDHTRecordData(
|
||||||
|
{required this.recordDescriptor,
|
||||||
|
required this.defaultWriter,
|
||||||
|
required this.defaultRoutingContext});
|
||||||
|
DHTRecordDescriptor recordDescriptor;
|
||||||
|
KeyPair? defaultWriter;
|
||||||
|
VeilidRoutingContext defaultRoutingContext;
|
||||||
|
bool needsWatchStateUpdate = false;
|
||||||
|
_WatchState? unionWatchState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per opened record data
|
||||||
|
class _OpenedRecordInfo {
|
||||||
|
_OpenedRecordInfo(
|
||||||
|
{required DHTRecordDescriptor recordDescriptor,
|
||||||
|
required KeyPair? defaultWriter,
|
||||||
|
required VeilidRoutingContext defaultRoutingContext})
|
||||||
|
: shared = _SharedDHTRecordData(
|
||||||
|
recordDescriptor: recordDescriptor,
|
||||||
|
defaultWriter: defaultWriter,
|
||||||
|
defaultRoutingContext: defaultRoutingContext);
|
||||||
|
_SharedDHTRecordData shared;
|
||||||
|
Set<DHTRecord> records = {};
|
||||||
|
|
||||||
|
String get debugNames {
|
||||||
|
final r = records.toList()
|
||||||
|
..sort((a, b) => a.key.toString().compareTo(b.key.toString()));
|
||||||
|
return '[${r.map((x) => x.debugName).join(',')}]';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get details {
|
||||||
|
final r = records.toList()
|
||||||
|
..sort((a, b) => a.key.toString().compareTo(b.key.toString()));
|
||||||
|
return '[${r.map((x) => "writer=${x._writer} "
|
||||||
|
"defaultSubkey=${x._defaultSubkey}").join(',')}]';
|
||||||
|
}
|
||||||
|
|
||||||
|
String get sharedDetails => shared.toString();
|
||||||
|
}
|
@ -8,8 +8,7 @@ import 'package:protobuf/protobuf.dart';
|
|||||||
import 'table_db.dart';
|
import 'table_db.dart';
|
||||||
|
|
||||||
class PersistentQueue<T extends GeneratedMessage>
|
class PersistentQueue<T extends GeneratedMessage>
|
||||||
/*extends Cubit<AsyncValue<IList<T>>>*/ with
|
with TableDBBackedFromBuffer<IList<T>> {
|
||||||
TableDBBackedFromBuffer<IList<T>> {
|
|
||||||
//
|
//
|
||||||
PersistentQueue(
|
PersistentQueue(
|
||||||
{required String table,
|
{required String table,
|
||||||
|
Loading…
Reference in New Issue
Block a user