From 95ed8b28a099f906b8283faa0b17817b25eac48c Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 3 Aug 2023 00:49:48 -0400 Subject: [PATCH] contacts work --- .../contact_invitation_display.dart | 59 +++++--- lib/components/send_invite_dialog.dart | 44 +++++- lib/entities/proto.dart | 142 +++++++++--------- lib/providers/account.dart | 2 +- lib/providers/account.g.dart | 2 +- lib/providers/contact.dart | 18 +-- 6 files changed, 162 insertions(+), 105 deletions(-) diff --git a/lib/components/contact_invitation_display.dart b/lib/components/contact_invitation_display.dart index 61ef4d0..a07af96 100644 --- a/lib/components/contact_invitation_display.dart +++ b/lib/components/contact_invitation_display.dart @@ -1,7 +1,9 @@ +import 'dart:async'; +import 'dart:ffi'; + import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; @@ -10,37 +12,42 @@ import '../tools/tools.dart'; class ContactInvitationDisplayDialog extends ConsumerStatefulWidget { const ContactInvitationDisplayDialog({ + required this.name, + required this.message, + required this.generator, super.key, }); - // EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none; - // _encryptionKey = ''; + final String name; + final String message; + final Future generator; @override ContactInvitationDisplayDialogState createState() => ContactInvitationDisplayDialogState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(StringProperty('name', name)) + ..add(StringProperty('message', message)) + ..add(DiagnosticsProperty?>('generator', generator)); + } } class ContactInvitationDisplayDialogState extends ConsumerState { final focusNode = FocusNode(); final formKey = GlobalKey(); - Future? _generateFuture; + late final FutureProvider _generateFutureProvider; @override void initState() { super.initState(); - if (_generateFuture == null) { - _generateFuture = _generate(); - } - } - Future _generate() async { - // Generate invitation - - setState(() { - _generateFuture = null; - }); + _generateFutureProvider = + FutureProvider((ref) async => widget.generator); } @override @@ -54,21 +61,35 @@ class ContactInvitationDisplayDialogState Widget build(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; - + final signedContactInvitationBytesV = ref.watch(_generateFutureProvider); final cardsize = MediaQuery.of(context).size.shortestSide - 24; - // return Dialog( backgroundColor: Colors.white, child: SizedBox( width: cardsize, height: cardsize, - child: Form( + child: signedContactInvitationBytesV.when( + loading: () => waitingPage(context), + data: (data) => Form( key: formKey, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, - children: [Text("Contact Invitation")])) - .withModalHUD(context, _generateFuture != null))); + children: [Text("Contact Invitation")])), + error: (e, s) { + Navigator.of(context).pop(); + showErrorToast( + context, "Failed to generate contact invitation: $e"); + return Text(""); + }))); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('focusNode', focusNode)) + ..add(DiagnosticsProperty>('formKey', formKey)); } } diff --git a/lib/components/send_invite_dialog.dart b/lib/components/send_invite_dialog.dart index 441961d..6a30e54 100644 --- a/lib/components/send_invite_dialog.dart +++ b/lib/components/send_invite_dialog.dart @@ -1,11 +1,18 @@ +import 'dart:async'; +import 'dart:typed_data'; + import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:quickalert/quickalert.dart'; import '../entities/local_account.dart'; +import '../providers/account.dart'; +import '../providers/contact.dart'; import '../tools/tools.dart'; +import '../veilid_support/veilid_support.dart'; import 'contact_invitation_display.dart'; import 'enter_pin.dart'; @@ -17,11 +24,12 @@ class SendInviteDialog extends ConsumerStatefulWidget { } class SendInviteDialogState extends ConsumerState { - final messageTextController = TextEditingController( + final _messageTextController = TextEditingController( text: translate('send_invite_dialog.connect_with_me')); EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none; String _encryptionKey = ''; + Timestamp? _expiration; @override void initState() { @@ -84,13 +92,34 @@ class SendInviteDialogState extends ConsumerState { } Future _onGenerateButtonPressed() async { + final navigator = Navigator.of(context); + + // Start generation + final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future); + if (activeAccountInfo == null) { + navigator.pop(); + return; + } + final generator = createContactInvitation( + activeAccountInfo: activeAccountInfo, + encryptionKeyType: _encryptionKeyType, + encryptionKey: _encryptionKey, + expiration: _expiration); + // ignore: use_build_context_synchronously + if (!context.mounted) { + return; + } await showDialog( context: context, - builder: (context) => ContactInvitationDisplayDialog()); + builder: (context) => ContactInvitationDisplayDialog( + name: activeAccountInfo.localAccount.name, + message: _messageTextController.text, + generator: generator, + )); // if (ret == null) { // return; // } - Navigator.of(context).pop(); + navigator.pop(); } @override @@ -111,7 +140,7 @@ class SendInviteDialogState extends ConsumerState { translate('send_invite_dialog.message_to_contact'), ).paddingAll(8), TextField( - controller: messageTextController, + controller: _messageTextController, decoration: InputDecoration( border: const OutlineInputBorder(), hintText: translate('send_invite_dialog.enter_message_hint'), @@ -159,4 +188,11 @@ class SendInviteDialogState extends ConsumerState { ), ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty( + 'messageTextController', _messageTextController)); + } } diff --git a/lib/entities/proto.dart b/lib/entities/proto.dart index eeecfce..947280b 100644 --- a/lib/entities/proto.dart +++ b/lib/entities/proto.dart @@ -10,30 +10,30 @@ export 'proto/veilidchat.pb.dart'; /// extension CryptoKeyProto on CryptoKey { proto.CryptoKey toProto() { - final b = decode(); + final b = decode().buffer.asByteData(); final out = proto.CryptoKey() - ..u0 = b[0] - ..u1 = b[1] - ..u2 = b[2] - ..u3 = b[3] - ..u4 = b[4] - ..u5 = b[5] - ..u6 = b[6] - ..u7 = b[7]; + ..u0 = b.getUint32(0 * 4) + ..u1 = b.getUint32(1 * 4) + ..u2 = b.getUint32(2 * 4) + ..u3 = b.getUint32(3 * 4) + ..u4 = b.getUint32(4 * 4) + ..u5 = b.getUint32(5 * 4) + ..u6 = b.getUint32(6 * 4) + ..u7 = b.getUint32(7 * 4); return out; } static CryptoKey fromProto(proto.CryptoKey p) { - final b = Uint8List(8); - b[0] = p.u0; - b[1] = p.u1; - b[2] = p.u2; - b[3] = p.u3; - b[4] = p.u4; - b[5] = p.u5; - b[6] = p.u6; - b[7] = p.u7; - return CryptoKey.fromBytes(b); + final b = ByteData(32) + ..setUint32(0 * 4, p.u0) + ..setUint32(1 * 4, p.u1) + ..setUint32(2 * 4, p.u2) + ..setUint32(3 * 4, p.u3) + ..setUint32(4 * 4, p.u4) + ..setUint32(5 * 4, p.u5) + ..setUint32(6 * 4, p.u6) + ..setUint32(7 * 4, p.u7); + return CryptoKey.fromBytes(Uint8List.view(b.buffer)); } } @@ -41,73 +41,73 @@ extension CryptoKeyProto on CryptoKey { /// extension SignatureProto on Signature { proto.Signature toProto() { - final b = decode(); + final b = decode().buffer.asByteData(); final out = proto.Signature() - ..u0 = b[0] - ..u1 = b[1] - ..u2 = b[2] - ..u3 = b[3] - ..u4 = b[4] - ..u5 = b[5] - ..u6 = b[6] - ..u7 = b[7] - ..u8 = b[8] - ..u9 = b[9] - ..u10 = b[10] - ..u11 = b[11] - ..u12 = b[12] - ..u13 = b[13] - ..u14 = b[14] - ..u15 = b[15]; + ..u0 = b.getUint32(0 * 4) + ..u1 = b.getUint32(1 * 4) + ..u2 = b.getUint32(2 * 4) + ..u3 = b.getUint32(3 * 4) + ..u4 = b.getUint32(4 * 4) + ..u5 = b.getUint32(5 * 4) + ..u6 = b.getUint32(6 * 4) + ..u7 = b.getUint32(7 * 4) + ..u8 = b.getUint32(8 * 4) + ..u9 = b.getUint32(9 * 4) + ..u10 = b.getUint32(10 * 4) + ..u11 = b.getUint32(11 * 4) + ..u12 = b.getUint32(12 * 4) + ..u13 = b.getUint32(13 * 4) + ..u14 = b.getUint32(14 * 4) + ..u15 = b.getUint32(15 * 4); return out; } static Signature fromProto(proto.Signature p) { - final b = Uint8List(16); - b[0] = p.u0; - b[1] = p.u1; - b[2] = p.u2; - b[3] = p.u3; - b[4] = p.u4; - b[5] = p.u5; - b[6] = p.u6; - b[7] = p.u7; - b[8] = p.u8; - b[9] = p.u9; - b[10] = p.u10; - b[11] = p.u11; - b[12] = p.u12; - b[13] = p.u13; - b[14] = p.u14; - b[15] = p.u15; - return Signature.fromBytes(b); + final b = ByteData(64) + ..setUint32(0 * 4, p.u0) + ..setUint32(1 * 4, p.u1) + ..setUint32(2 * 4, p.u2) + ..setUint32(3 * 4, p.u3) + ..setUint32(4 * 4, p.u4) + ..setUint32(5 * 4, p.u5) + ..setUint32(6 * 4, p.u6) + ..setUint32(7 * 4, p.u7) + ..setUint32(8 * 4, p.u8) + ..setUint32(9 * 4, p.u9) + ..setUint32(10 * 4, p.u10) + ..setUint32(11 * 4, p.u11) + ..setUint32(12 * 4, p.u12) + ..setUint32(13 * 4, p.u13) + ..setUint32(14 * 4, p.u14) + ..setUint32(15 * 4, p.u15); + return Signature.fromBytes(Uint8List.view(b.buffer)); } } /// Nonce protobuf marshaling /// extension NonceProto on Nonce { - proto.Signature toProto() { - final b = decode(); - final out = proto.Signature() - ..u0 = b[0] - ..u1 = b[1] - ..u2 = b[2] - ..u3 = b[3] - ..u4 = b[4] - ..u5 = b[5]; + proto.Nonce toProto() { + final b = decode().buffer.asByteData(); + final out = proto.Nonce() + ..u0 = b.getUint32(0 * 4) + ..u1 = b.getUint32(1 * 4) + ..u2 = b.getUint32(2 * 4) + ..u3 = b.getUint32(3 * 4) + ..u4 = b.getUint32(4 * 4) + ..u5 = b.getUint32(5 * 4); return out; } static Nonce fromProto(proto.Nonce p) { - final b = Uint8List(6); - b[0] = p.u0; - b[1] = p.u1; - b[2] = p.u2; - b[3] = p.u3; - b[4] = p.u4; - b[5] = p.u5; - return Nonce.fromBytes(b); + final b = ByteData(24) + ..setUint32(0 * 4, p.u0) + ..setUint32(1 * 4, p.u1) + ..setUint32(2 * 4, p.u2) + ..setUint32(3 * 4, p.u3) + ..setUint32(4 * 4, p.u4) + ..setUint32(5 * 4, p.u5); + return Nonce.fromBytes(Uint8List.view(b.buffer)); } } diff --git a/lib/providers/account.dart b/lib/providers/account.dart index 836fb52..31cee94 100644 --- a/lib/providers/account.dart +++ b/lib/providers/account.dart @@ -87,7 +87,7 @@ class ActiveAccountInfo { /// Get the active account info @riverpod -Future fetchActiveAccount(FetchAccountRef ref) async { +Future fetchActiveAccount(FetchActiveAccountRef ref) async { // See if we've logged into this account or if it is locked final activeUserLogin = await ref.watch(loginsProvider.future .select((value) async => (await value).activeUserLogin)); diff --git a/lib/providers/account.g.dart b/lib/providers/account.g.dart index 1c92f78..e39ee37 100644 --- a/lib/providers/account.g.dart +++ b/lib/providers/account.g.dart @@ -130,7 +130,7 @@ class FetchAccountProvider extends AutoDisposeFutureProvider { } String _$fetchActiveAccountHash() => - r'8c7e571c135deeb5cacf56c61459d71f7447baaf'; + r'd074ab2c160bab41ed3dd979b7054603b7d5b2b1'; /// Get the active account info /// diff --git a/lib/providers/contact.dart b/lib/providers/contact.dart index cf75edd..3f70fe4 100644 --- a/lib/providers/contact.dart +++ b/lib/providers/contact.dart @@ -1,19 +1,18 @@ import 'dart:typed_data'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:fixnum/fixnum.dart'; import '../entities/local_account.dart'; import '../entities/proto.dart' as proto; -import '../entities/user_login.dart'; import '../tools/tools.dart'; import '../veilid_support/veilid_support.dart'; import 'account.dart'; Future createContactInvitation( - ActiveAccountInfo activeAccountInfo, - EncryptionKeyType encryptionKeyType, - String encryptionKey, - Timestamp expiration) async { + {required ActiveAccountInfo activeAccountInfo, + required EncryptionKeyType encryptionKeyType, + required String encryptionKey, + required Timestamp? expiration}) async { final pool = await DHTRecordPool.instance(); final accountRecordKey = activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; @@ -46,7 +45,7 @@ Future createContactInvitation( ..accountMasterRecordKey = activeAccountInfo.userLogin.accountMasterRecordKey.toProto() ..chatRecordKey = localChatRecord.key.toProto() - ..expiration = expiration.toInt64(); + ..expiration = expiration?.toInt64() ?? Int64.ZERO; final crprivbytes = crpriv.writeToBuffer(); final encryptedContactRequestPrivate = await cs.encryptNoAuthWithNonce(crprivbytes, writer.secret); @@ -83,10 +82,11 @@ Future createContactInvitation( ..writerKey = writer.key.toProto() ..writerSecret = writer.secret.toProto() ..chatRecordKey = localChatRecord.key.toProto() - ..expiration = expiration.toInt64() + ..expiration = expiration?.toInt64() ?? Int64.ZERO ..invitation = signedContactInvitationBytes; - // Add ContactInvitationRecord to local table + // Add ContactInvitationRecord to local table if possible + // if this fails, don't keep retrying, user can try again later await (await DHTShortArray.openOwned( proto.OwnedDHTRecordPointerProto.fromProto( activeAccountInfo.account.contactInvitationRecords),