This commit is contained in:
Christien Rioux 2023-08-08 23:33:31 -07:00
parent d965f674fc
commit e76e3cf0ba
11 changed files with 148 additions and 66 deletions

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>

View File

@ -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<ChatComponent> {
List<types.Message> _messages = [];
final _unfocusNode = FocusNode();
late final types.User _localUser;
late final types.User _remoteUser;
@ -52,8 +52,6 @@ class ChatComponentState extends ConsumerState<ChatComponent> {
widget.activeChatContact.identityPublicKey)
.toString(),
firstName: widget.activeChatContact.remoteProfile.name);
unawaited(_loadMessages());
}
@override
@ -62,29 +60,6 @@ class ChatComponentState extends ConsumerState<ChatComponent> {
super.dispose();
}
externalize messages so they auto refresh and fix speed.
Future<void> _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<ChatComponent> {
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<ChatComponent> {
localConversationRecordKey: localConversationRecordKey,
remoteIdentityPublicKey: remoteIdentityPublicKey,
message: protoMessage);
ref.invalidate(activeConversationMessagesProvider);
}
Future<void> _handleSendPressed(types.PartialText message) async {
@ -149,6 +126,17 @@ class ChatComponentState extends ConsumerState<ChatComponent> {
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 = <types.Message>[];
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<ChatComponent> {
decoration: const BoxDecoration(),
child: Chat(
theme: chatTheme,
messages: _messages,
messages: messages,
//onAttachmentPressed: _handleAttachmentPressed,
//onMessageTap: _handleMessageTap,
//onPreviewDataFetched: _handlePreviewDataFetched,

View File

@ -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) {

View File

@ -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) / ),

View File

@ -143,7 +143,7 @@ class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
TextField(
controller: _messageTextController,
inputFormatters: [
LengthLimitingTextInputFormatter(256),
LengthLimitingTextInputFormatter(128),
],
decoration: InputDecoration(
border: const OutlineInputBorder(),

View File

@ -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<DHTRecordCrypto> getConversationCrypto({
required ActiveAccountInfo activeAccountInfo,
@ -173,7 +178,7 @@ Future<void> 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<bool> mergeLocalConversationMessages(
{required ActiveAccountInfo activeAccountInfo,
required TypedKey localConversationRecordKey,
required TypedKey remoteIdentityPublicKey,
required IList<proto.Message> newMessages}) async {
required IList<Message> newMessages}) async {
final conversation = await readLocalConversation(
activeAccountInfo: activeAccountInfo,
localConversationRecordKey: localConversationRecordKey,
@ -207,7 +212,7 @@ Future<bool> 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<bool> mergeLocalConversationMessages(
return changed;
}
Future<IList<proto.Message>?> getLocalConversationMessages({
Future<IList<Message>?> getLocalConversationMessages({
required ActiveAccountInfo activeAccountInfo,
required TypedKey localConversationRecordKey,
required TypedKey remoteIdentityPublicKey,
@ -281,9 +286,9 @@ Future<IList<proto.Message>?> getLocalConversationMessages({
return (await DHTShortArray.openRead(messagesRecordKey,
parent: localConversationRecordKey, crypto: crypto))
.scope((messages) async {
var out = IList<proto.Message>();
var out = IList<Message>();
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<IList<proto.Message>?> getLocalConversationMessages({
});
}
Future<IList<proto.Message>?> getRemoteConversationMessages({
Future<IList<Message>?> getRemoteConversationMessages({
required ActiveAccountInfo activeAccountInfo,
required TypedKey remoteConversationRecordKey,
required TypedKey remoteIdentityPublicKey,
@ -314,9 +319,9 @@ Future<IList<proto.Message>?> getRemoteConversationMessages({
return (await DHTShortArray.openRead(messagesRecordKey,
parent: remoteConversationRecordKey, crypto: crypto))
.scope((messages) async {
var out = IList<proto.Message>();
var out = IList<Message>();
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<IList<proto.Message>?> getRemoteConversationMessages({
return out;
});
}
@riverpod
class ActiveConversationMessages extends _$ActiveConversationMessages {
/// Get message for active converation
@override
FutureOr<IList<Message>?> 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,
);
}
}

View File

@ -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<Message>?>.internal(
ActiveConversationMessages.new,
name: r'activeConversationMessagesProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$activeConversationMessagesHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ActiveConversationMessages
= AutoDisposeAsyncNotifier<IList<Message>?>;
// 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

View File

@ -6,7 +6,7 @@ part of 'router_notifier.dart';
// RiverpodGenerator
// **************************************************************************
String _$routerNotifierHash() => r'8e7b9debfa144253e25871edf920bf315f28a861';
String _$routerNotifierHash() => r'6493fee7e11afead973a5bece304b3c94f7fa1c6';
/// See also [RouterNotifier].
@ProviderFor(RouterNotifier)

View File

@ -155,11 +155,14 @@ class BackgroundTickerState extends ConsumerState<BackgroundTicker> {
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);
}
}
}
}

View File

@ -1,3 +1,4 @@
import 'package:charcode/charcode.dart';
import 'package:veilid/veilid.dart';
Future<VeilidConfig> getVeilidChatConfig() async {
@ -18,5 +19,14 @@ Future<VeilidConfig> 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;
}

View File

@ -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'