From e76e3cf0ba771bca133ef3d6da5c671fcc213d23 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 8 Aug 2023 23:33:31 -0700 Subject: [PATCH] 1.0.2 --- ios/Runner/Info.plist | 2 + lib/components/chat_component.dart | 48 +++++-------- .../contact_invitation_display.dart | 37 +++++----- .../contact_invitation_item_widget.dart | 9 ++- lib/components/send_invite_dialog.dart | 2 +- lib/providers/conversation.dart | 70 ++++++++++++++++--- lib/providers/conversation.g.dart | 27 +++++++ lib/router/router_notifier.g.dart | 2 +- lib/tick.dart | 5 +- lib/veilid_support/config.dart | 10 +++ pubspec.yaml | 2 +- 11 files changed, 148 insertions(+), 66 deletions(-) create mode 100644 lib/providers/conversation.g.dart diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 308c43d..4a278a8 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion diff --git a/lib/components/chat_component.dart b/lib/components/chat_component.dart index 585d6cf..6475d63 100644 --- a/lib/components/chat_component.dart +++ b/lib/components/chat_component.dart @@ -14,6 +14,7 @@ import '../providers/account.dart'; import '../providers/chat.dart'; import '../providers/conversation.dart'; import '../tools/theme_service.dart'; +import '../tools/widget_helpers.dart'; import '../veilid_support/veilid_support.dart'; class ChatComponent extends ConsumerStatefulWidget { @@ -32,7 +33,6 @@ class ChatComponent extends ConsumerStatefulWidget { } class ChatComponentState extends ConsumerState { - List _messages = []; final _unfocusNode = FocusNode(); late final types.User _localUser; late final types.User _remoteUser; @@ -52,8 +52,6 @@ class ChatComponentState extends ConsumerState { widget.activeChatContact.identityPublicKey) .toString(), firstName: widget.activeChatContact.remoteProfile.name); - - unawaited(_loadMessages()); } @override @@ -62,29 +60,6 @@ class ChatComponentState extends ConsumerState { super.dispose(); } - externalize messages so they auto refresh and fix speed. - - Future _loadMessages() async { - final localConversationRecordKey = proto.TypedKeyProto.fromProto( - widget.activeChatContact.localConversationRecordKey); - final remoteIdentityPublicKey = proto.TypedKeyProto.fromProto( - widget.activeChatContact.identityPublicKey); - final protoMessages = await getLocalConversationMessages( - activeAccountInfo: widget.activeAccountInfo, - localConversationRecordKey: localConversationRecordKey, - remoteIdentityPublicKey: remoteIdentityPublicKey); - if (protoMessages == null) { - return; - } - setState(() { - _messages = []; - for (final protoMessage in protoMessages) { - final message = protoMessageToMessage(protoMessage); - _messages.insert(0, message); - } - }); - } - types.Message protoMessageToMessage(proto.Message message) { final isLocal = message.author == widget.activeAccountInfo.localAccount.identityMaster @@ -107,9 +82,9 @@ class ChatComponentState extends ConsumerState { final message = protoMessageToMessage(protoMessage); - setState(() { - _messages.insert(0, message); - }); + // setState(() { + // _messages.insert(0, message); + // }); // Now add the message to the conversation messages final localConversationRecordKey = proto.TypedKeyProto.fromProto( @@ -122,6 +97,8 @@ class ChatComponentState extends ConsumerState { localConversationRecordKey: localConversationRecordKey, remoteIdentityPublicKey: remoteIdentityPublicKey, message: protoMessage); + + ref.invalidate(activeConversationMessagesProvider); } Future _handleSendPressed(types.PartialText message) async { @@ -149,6 +126,17 @@ class ChatComponentState extends ConsumerState { final textTheme = Theme.of(context).textTheme; final contactName = widget.activeChatContact.editedProfile.name; + final protoMessages = + ref.watch(activeConversationMessagesProvider).asData?.value; + if (protoMessages == null) { + return waitingPage(context); + } + final messages = []; + for (final protoMessage in protoMessages) { + final message = protoMessageToMessage(protoMessage); + messages.insert(0, message); + } + return DefaultTextStyle( style: textTheme.bodySmall!, child: Align( @@ -185,7 +173,7 @@ class ChatComponentState extends ConsumerState { decoration: const BoxDecoration(), child: Chat( theme: chatTheme, - messages: _messages, + messages: messages, //onAttachmentPressed: _handleAttachmentPressed, //onMessageTap: _handleMessageTap, //onPreviewDataFetched: _handlePreviewDataFetched, diff --git a/lib/components/contact_invitation_display.dart b/lib/components/contact_invitation_display.dart index f23bece..24047c6 100644 --- a/lib/components/contact_invitation_display.dart +++ b/lib/components/contact_invitation_display.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:basic_utils/basic_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -76,13 +77,12 @@ class ContactInvitationDisplayDialogState final textTheme = theme.textTheme; final signedContactInvitationBytesV = ref.watch(_generateFutureProvider); - final cardsize = MediaQuery.of(context).size.shortestSide - 24; + final cardsize = MediaQuery.of(context).size.shortestSide - 48; return Dialog( backgroundColor: Colors.white, - child: SizedBox( - width: cardsize, - height: cardsize, + child: FittedBox( + fit: BoxFit.scaleDown, child: signedContactInvitationBytesV.when( loading: () => waitingPage(context), data: (data) { @@ -96,16 +96,16 @@ class ContactInvitationDisplayDialogState mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + translate( + 'send_invite_dialog.contact_invitation'), + style: textTheme.headlineMedium! + .copyWith(color: Colors.black))) + .paddingAll(8), FittedBox( fit: BoxFit.scaleDown, - child: Text( - translate( - 'send_invite_dialog.contact_invitation'), - style: textTheme.headlineMedium! - .copyWith(color: Colors.black))), - ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 300, minHeight: 300), child: QrImageView.withQr( size: 300, qr: QrCode.fromUint8List( @@ -113,11 +113,12 @@ class ContactInvitationDisplayDialogState errorCorrectLevel: QrErrorCorrectLevel.L))), FittedBox( - fit: BoxFit.scaleDown, - child: Text(widget.message, - softWrap: true, - style: textTheme.headlineSmall! - .copyWith(color: Colors.black))), + fit: BoxFit.scaleDown, + child: Text(widget.message, + softWrap: true, + style: textTheme.headlineSmall! + .copyWith(color: Colors.black))) + .paddingAll(8), ElevatedButton.icon( icon: const Icon(Icons.copy), label: Text(translate( @@ -131,7 +132,7 @@ class ContactInvitationDisplayDialogState text: makeTextInvite(widget.message, data))); }, - ), + ).paddingAll(8), ])); }, error: (e, s) { diff --git a/lib/components/contact_invitation_item_widget.dart b/lib/components/contact_invitation_item_widget.dart index 92e472d..20f4cfa 100644 --- a/lib/components/contact_invitation_item_widget.dart +++ b/lib/components/contact_invitation_item_widget.dart @@ -114,9 +114,12 @@ class ContactInvitationItemWidget extends ConsumerWidget { )); } }, - subtitle: Text(contactInvitationRecord.message.isEmpty - ? translate('contact_list.invitation') - : contactInvitationRecord.message), + title: Text( + contactInvitationRecord.message.isEmpty + ? translate('contact_list.invitation') + : contactInvitationRecord.message, + softWrap: true, + ), iconColor: scale.tertiaryScale.background, textColor: scale.tertiaryScale.text, //Text(Timestamp.fromInt64(contactInvitationRecord.expiration) / ), diff --git a/lib/components/send_invite_dialog.dart b/lib/components/send_invite_dialog.dart index 35b85a0..5404108 100644 --- a/lib/components/send_invite_dialog.dart +++ b/lib/components/send_invite_dialog.dart @@ -143,7 +143,7 @@ class SendInviteDialogState extends ConsumerState { TextField( controller: _messageTextController, inputFormatters: [ - LengthLimitingTextInputFormatter(256), + LengthLimitingTextInputFormatter(128), ], decoration: InputDecoration( border: const OutlineInputBorder(), diff --git a/lib/providers/conversation.dart b/lib/providers/conversation.dart index 7caa63a..e2e91e7 100644 --- a/lib/providers/conversation.dart +++ b/lib/providers/conversation.dart @@ -1,17 +1,22 @@ import 'dart:convert'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../entities/identity.dart'; import '../entities/proto.dart' as proto; -import '../entities/proto.dart' show Conversation; +import '../entities/proto.dart' show Conversation, Message; import '../log/loggy.dart'; +import '../tools/external_stream_state.dart'; import '../veilid_support/veilid_support.dart'; import 'account.dart'; +import 'chat.dart'; +import 'contact.dart'; -//part 'conversation.g.dart'; +part 'conversation.g.dart'; Future getConversationCrypto({ required ActiveAccountInfo activeAccountInfo, @@ -173,7 +178,7 @@ Future addLocalConversationMessage( {required ActiveAccountInfo activeAccountInfo, required TypedKey localConversationRecordKey, required TypedKey remoteIdentityPublicKey, - required proto.Message message}) async { + required Message message}) async { final conversation = await readLocalConversation( activeAccountInfo: activeAccountInfo, localConversationRecordKey: localConversationRecordKey, @@ -199,7 +204,7 @@ Future mergeLocalConversationMessages( {required ActiveAccountInfo activeAccountInfo, required TypedKey localConversationRecordKey, required TypedKey remoteIdentityPublicKey, - required IList newMessages}) async { + required IList newMessages}) async { final conversation = await readLocalConversation( activeAccountInfo: activeAccountInfo, localConversationRecordKey: localConversationRecordKey, @@ -207,7 +212,7 @@ Future mergeLocalConversationMessages( if (conversation == null) { return false; } - bool changed = false; + var changed = false; final messagesRecordKey = proto.TypedKeyProto.fromProto(conversation.messages); final crypto = await getConversationCrypto( @@ -260,7 +265,7 @@ Future mergeLocalConversationMessages( return changed; } -Future?> getLocalConversationMessages({ +Future?> getLocalConversationMessages({ required ActiveAccountInfo activeAccountInfo, required TypedKey localConversationRecordKey, required TypedKey remoteIdentityPublicKey, @@ -281,9 +286,9 @@ Future?> getLocalConversationMessages({ return (await DHTShortArray.openRead(messagesRecordKey, parent: localConversationRecordKey, crypto: crypto)) .scope((messages) async { - var out = IList(); + var out = IList(); for (var i = 0; i < messages.length; i++) { - final msg = await messages.getItemProtobuf(proto.Message.fromBuffer, i); + final msg = await messages.getItemProtobuf(Message.fromBuffer, i); if (msg == null) { throw Exception('Failed to get message'); } @@ -293,7 +298,7 @@ Future?> getLocalConversationMessages({ }); } -Future?> getRemoteConversationMessages({ +Future?> getRemoteConversationMessages({ required ActiveAccountInfo activeAccountInfo, required TypedKey remoteConversationRecordKey, required TypedKey remoteIdentityPublicKey, @@ -314,9 +319,9 @@ Future?> getRemoteConversationMessages({ return (await DHTShortArray.openRead(messagesRecordKey, parent: remoteConversationRecordKey, crypto: crypto)) .scope((messages) async { - var out = IList(); + var out = IList(); for (var i = 0; i < messages.length; i++) { - final msg = await messages.getItemProtobuf(proto.Message.fromBuffer, i); + final msg = await messages.getItemProtobuf(Message.fromBuffer, i); if (msg == null) { throw Exception('Failed to get message'); } @@ -325,3 +330,46 @@ Future?> getRemoteConversationMessages({ return out; }); } + +@riverpod +class ActiveConversationMessages extends _$ActiveConversationMessages { + /// Get message for active converation + @override + FutureOr?> build() async { + final activeChat = activeChatState.currentState; + if (activeChat == null) { + return null; + } + + final activeAccountInfo = + await ref.watch(fetchActiveAccountProvider.future); + if (activeAccountInfo == null) { + return null; + } + + final contactList = ref.watch(fetchContactListProvider).asData?.value ?? + const IListConst([]); + + final activeChatContactIdx = contactList.indexWhere( + (c) => + proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) == + activeChat, + ); + if (activeChatContactIdx == -1) { + return null; + } + final activeChatContact = contactList[activeChatContactIdx]; + final remoteIdentityPublicKey = + proto.TypedKeyProto.fromProto(activeChatContact.identityPublicKey); + // final remoteConversationRecordKey = proto.TypedKeyProto.fromProto( + // activeChatContact.remoteConversationRecordKey); + final localConversationRecordKey = proto.TypedKeyProto.fromProto( + activeChatContact.localConversationRecordKey); + + return await getLocalConversationMessages( + activeAccountInfo: activeAccountInfo, + localConversationRecordKey: localConversationRecordKey, + remoteIdentityPublicKey: remoteIdentityPublicKey, + ); + } +} diff --git a/lib/providers/conversation.g.dart b/lib/providers/conversation.g.dart new file mode 100644 index 0000000..95338f9 --- /dev/null +++ b/lib/providers/conversation.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'conversation.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activeConversationMessagesHash() => + r'd65cd8bf71122806320325e7f0e5f8e751d13b55'; + +/// See also [ActiveConversationMessages]. +@ProviderFor(ActiveConversationMessages) +final activeConversationMessagesProvider = AutoDisposeAsyncNotifierProvider< + ActiveConversationMessages, IList?>.internal( + ActiveConversationMessages.new, + name: r'activeConversationMessagesProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$activeConversationMessagesHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ActiveConversationMessages + = AutoDisposeAsyncNotifier?>; +// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/lib/router/router_notifier.g.dart b/lib/router/router_notifier.g.dart index 78acd57..73bdb5d 100644 --- a/lib/router/router_notifier.g.dart +++ b/lib/router/router_notifier.g.dart @@ -6,7 +6,7 @@ part of 'router_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$routerNotifierHash() => r'8e7b9debfa144253e25871edf920bf315f28a861'; +String _$routerNotifierHash() => r'6493fee7e11afead973a5bece304b3c94f7fa1c6'; /// See also [RouterNotifier]. @ProviderFor(RouterNotifier) diff --git a/lib/tick.dart b/lib/tick.dart index 0b5e9b4..3b682c6 100644 --- a/lib/tick.dart +++ b/lib/tick.dart @@ -155,11 +155,14 @@ class BackgroundTickerState extends ConsumerState { remoteIdentityPublicKey: remoteIdentityPublicKey, remoteConversationRecordKey: remoteConversationRecordKey); if (newMessages != null) { - await mergeLocalConversationMessages( + final changed = await mergeLocalConversationMessages( activeAccountInfo: activeAccountInfo, localConversationRecordKey: localConversationRecordKey, remoteIdentityPublicKey: remoteIdentityPublicKey, newMessages: newMessages); + if (changed) { + ref.invalidate(activeConversationMessagesProvider); + } } } } diff --git a/lib/veilid_support/config.dart b/lib/veilid_support/config.dart index f359fcb..2e24b30 100644 --- a/lib/veilid_support/config.dart +++ b/lib/veilid_support/config.dart @@ -1,3 +1,4 @@ +import 'package:charcode/charcode.dart'; import 'package:veilid/veilid.dart'; Future getVeilidChatConfig() async { @@ -18,5 +19,14 @@ Future getVeilidChatConfig() async { config.copyWith(blockStore: config.blockStore.copyWith(delete: true)); } + // xxx hack + config = config.copyWith( + network: config.network.copyWith( + dht: config.network.dht.copyWith( + getValueCount: 2, + getValueTimeoutMs: 5000, + setValueCount: 2, + setValueTimeoutMs: 5000))); + return config; } diff --git a/pubspec.yaml b/pubspec.yaml index 0f31c27..ffe5b20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: veilidchat description: VeilidChat publish_to: 'none' -version: 1.0.0+1 +version: 1.0.1+2 environment: sdk: '>=3.0.5 <4.0.0'