mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
contact invitation algorithm
This commit is contained in:
parent
c35056f687
commit
f52094c105
@ -43,12 +43,37 @@
|
||||
"account_page": {
|
||||
"missing_account_title": "Missing Account",
|
||||
"missing_account_text": "Account is missing, removing from list",
|
||||
"invalid_account_title": "Missing Account",
|
||||
"invalid_account_text": "Account is missing, removing from list"
|
||||
"invalid_account_title": "Invalid Account",
|
||||
"invalid_account_text": "Account is invalid, removing from list"
|
||||
},
|
||||
"empty_contact_list": {
|
||||
"invite_people": "Invite people to VeilidChat"
|
||||
},
|
||||
"accounts_menu": {
|
||||
"invite_contact": "Invite Contact",
|
||||
"send_invite": "Send Invite",
|
||||
"receive_invite": "Receive Invite"
|
||||
},
|
||||
"send_invite_dialog": {
|
||||
"connect_with_me": "Connect with me on VeilidChat!",
|
||||
"enter_message_hint": "enter message for contact (optional)",
|
||||
"message_to_contact": "Message to send with invitation (not encrypted)",
|
||||
"generate": "Generate Invite",
|
||||
"message": "Message",
|
||||
"unlocked": "Unlocked",
|
||||
"numeric_pin": "Numeric PIN",
|
||||
"password": "Password",
|
||||
"protect_this_invitation": "Protect this invitation:",
|
||||
"note": "Note:",
|
||||
"note_text": "Contact invitations can be used by anyone. Make sure you send the invitation to your contact over a secure medium, and preferably use a password or pin to ensure that they are the only ones who can unlock the invitation and accept it.",
|
||||
"pin_description": "Choose a PIN to protect the contact invite.\n\nThis level of security is appropriate only for casual connections in public environments for 'shoulder surfing' protection.",
|
||||
"password_description": "Choose a strong password to protect the contact invite.\n\nThis level of security is appropriate when you must be sure the contact invitation is only accepted by its intended recipient. Share this password over a different medium than the invite itself.",
|
||||
"pin_does_not_match": "PIN does not match"
|
||||
},
|
||||
"enter_pin_dialog": {
|
||||
"enter_pin": "Enter PIN",
|
||||
"reenter_pin": "Re-Enter PIN To Confirm"
|
||||
},
|
||||
"themes": {
|
||||
"vapor": "Vapor"
|
||||
}
|
||||
|
@ -3,12 +3,13 @@
|
||||
2. Encrypt secret with requested encryption type
|
||||
3. Create Local Chat DHT record (no content yet, will be encrypted with DH of contact identity key)
|
||||
4. Create ContactRequestPrivate and encrypt with the writer secret
|
||||
5. Create ContactRequest and embed possibly encrypted ContactRequestPrivate
|
||||
5. Create ContactRequest and embed encrypted ContactRequestPrivate
|
||||
6. Create DHT unicast inbox for ContactRequest and store ContactRequest in owner subkey
|
||||
7. Create ContactInvitation and add invitation record to local table
|
||||
7. Create ContactInvitation
|
||||
8. Create SignedContactInvitation embedding ContactInvitation
|
||||
9. Render SignedContactInvitation to shareable encoding (qr code, text blob, etc)
|
||||
10. Share SignedContactInvitation out of band to desired contact, along with password somehow if used
|
||||
9. Create ContactInvitationRecord and add to local table in Account
|
||||
10. Render SignedContactInvitation to shareable encoding (qr code, text blob, etc)
|
||||
11. Share SignedContactInvitation out of band to desired contact, along with password somehow if used
|
||||
|
||||
## Receiving an invitation
|
||||
1. Receive SignedContactInvitation from out of band, and the password somehow if used
|
||||
|
@ -6,6 +6,8 @@ PODS:
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@ -14,15 +16,19 @@ PODS:
|
||||
- FMDB (>= 2.7.5)
|
||||
- system_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- veilid (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- system_info_plus (from `.symlinks/plugins/system_info_plus/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- veilid (from `.symlinks/plugins/veilid/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
@ -34,12 +40,16 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
system_info_plus:
|
||||
:path: ".symlinks/plugins/system_info_plus/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
veilid:
|
||||
:path: ".symlinks/plugins/veilid/ios"
|
||||
|
||||
@ -47,9 +57,11 @@ SPEC CHECKSUMS:
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
veilid: f5c2e662f91907b30cf95762619526ac3e4512fd
|
||||
|
||||
PODFILE CHECKSUM: fcab1959fbc0528061dce4ed4f921740dc46f1e5
|
||||
|
67
lib/components/bottom_sheet_action_button.dart
Normal file
67
lib/components/bottom_sheet_action_button.dart
Normal file
@ -0,0 +1,67 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class BottomSheetActionButton extends ConsumerStatefulWidget {
|
||||
const BottomSheetActionButton(
|
||||
{required this.bottomSheetBuilder,
|
||||
required this.builder,
|
||||
this.foregroundColor,
|
||||
this.backgroundColor,
|
||||
this.shape,
|
||||
super.key});
|
||||
final Color? foregroundColor;
|
||||
final Color? backgroundColor;
|
||||
final ShapeBorder? shape;
|
||||
final Widget Function(BuildContext) builder;
|
||||
final Widget Function(BuildContext) bottomSheetBuilder;
|
||||
|
||||
@override
|
||||
BottomSheetActionButtonState createState() => BottomSheetActionButtonState();
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(ObjectFlagProperty<Widget Function(BuildContext p1)>.has(
|
||||
'bottomSheetBuilder', bottomSheetBuilder))
|
||||
..add(ColorProperty('foregroundColor', foregroundColor))
|
||||
..add(ColorProperty('backgroundColor', backgroundColor))
|
||||
..add(DiagnosticsProperty<ShapeBorder?>('shape', shape))
|
||||
..add(ObjectFlagProperty<Widget? Function(BuildContext p1)>.has(
|
||||
'builder', builder));
|
||||
}
|
||||
}
|
||||
|
||||
class BottomSheetActionButtonState
|
||||
extends ConsumerState<BottomSheetActionButton> {
|
||||
bool _showFab = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
//
|
||||
return _showFab
|
||||
? FloatingActionButton(
|
||||
shape: widget.shape,
|
||||
foregroundColor: widget.foregroundColor,
|
||||
backgroundColor: widget.backgroundColor,
|
||||
child: widget.builder(context),
|
||||
onPressed: () async {
|
||||
await showModalBottomSheet<void>(
|
||||
context: context, builder: widget.bottomSheetBuilder);
|
||||
},
|
||||
)
|
||||
: Container();
|
||||
}
|
||||
|
||||
void showFloatingActionButton(bool value) {
|
||||
setState(() {
|
||||
_showFab = value;
|
||||
});
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../tools/theme_service.dart';
|
||||
|
||||
class ChatComponent extends ConsumerStatefulWidget {
|
||||
const ChatComponent({super.key});
|
||||
|
||||
@ -63,56 +65,68 @@ class ChatComponentState extends ConsumerState<ChatComponent> {
|
||||
_addMessage(textMessage);
|
||||
}
|
||||
|
||||
void _handleAttachmentPressed() {
|
||||
//
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final chatTheme = scale.toChatTheme();
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
//
|
||||
return Align(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
return DefaultTextStyle(
|
||||
style: textTheme.bodySmall!,
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: scale.primaryScale.appBackground,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsetsDirectional.fromSTEB(16, 0, 16, 0),
|
||||
child: Text("current contact",
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).textTheme.titleMedium),
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: scale.primaryScale.subtleBackground,
|
||||
),
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.fromSTEB(
|
||||
16, 0, 16, 0),
|
||||
child: Text("current contact",
|
||||
textAlign: TextAlign.start,
|
||||
style: textTheme.titleMedium),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(),
|
||||
child: Chat(
|
||||
//theme: _chatTheme,
|
||||
messages: _messages,
|
||||
//onAttachmentPressed: _handleAttachmentPressed,
|
||||
//onMessageTap: _handleMessageTap,
|
||||
//onPreviewDataFetched: _handlePreviewDataFetched,
|
||||
onSendPressed: _handleSendPressed,
|
||||
showUserAvatars: true,
|
||||
showUserNames: true,
|
||||
user: _user,
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(),
|
||||
child: Chat(
|
||||
theme: chatTheme,
|
||||
messages: _messages,
|
||||
//onAttachmentPressed: _handleAttachmentPressed,
|
||||
//onMessageTap: _handleMessageTap,
|
||||
//onPreviewDataFetched: _handlePreviewDataFetched,
|
||||
|
||||
onSendPressed: _handleSendPressed,
|
||||
showUserAvatars: true,
|
||||
showUserNames: true,
|
||||
user: _user,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,74 @@
|
||||
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';
|
||||
|
||||
class ContactInvitationDisplay extends ConsumerWidget {
|
||||
const ContactInvitationDisplay({super.key});
|
||||
//final LocalAccount account;
|
||||
import '../tools/tools.dart';
|
||||
|
||||
class ContactInvitationDisplayDialog extends ConsumerStatefulWidget {
|
||||
const ContactInvitationDisplayDialog({
|
||||
super.key,
|
||||
});
|
||||
|
||||
// EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none;
|
||||
// _encryptionKey = '';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
//final logins = ref.watch(loginsProvider);
|
||||
ContactInvitationDisplayDialogState createState() =>
|
||||
ContactInvitationDisplayDialogState();
|
||||
}
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [const Expanded(child: Text('Contact Invitation'))]));
|
||||
class ContactInvitationDisplayDialogState
|
||||
extends ConsumerState<ContactInvitationDisplayDialog> {
|
||||
final focusNode = FocusNode();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
Future<void>? _generateFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (_generateFuture == null) {
|
||||
_generateFuture = _generate();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _generate() async {
|
||||
// Generate invitation
|
||||
|
||||
setState(() {
|
||||
_generateFuture = null;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
//properties.add(DiagnosticsProperty<LocalAccount>('account', account));
|
||||
void dispose() {
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
final cardsize = MediaQuery.of(context).size.shortestSide - 24;
|
||||
//
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
child: SizedBox(
|
||||
width: cardsize,
|
||||
height: cardsize,
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [Text("Contact Invitation")]))
|
||||
.withModalHUD(context, _generateFuture != null)));
|
||||
}
|
||||
}
|
||||
|
162
lib/components/enter_pin.dart
Normal file
162
lib/components/enter_pin.dart
Normal file
@ -0,0 +1,162 @@
|
||||
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_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:pinput/pinput.dart';
|
||||
|
||||
import '../tools/tools.dart';
|
||||
|
||||
class EnterPinDialog extends ConsumerStatefulWidget {
|
||||
const EnterPinDialog({
|
||||
this.matchPin,
|
||||
this.description,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String? matchPin;
|
||||
final String? description;
|
||||
|
||||
@override
|
||||
EnterPinDialogState createState() => EnterPinDialogState();
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(StringProperty('matchPin', matchPin))
|
||||
..add(StringProperty('description', description));
|
||||
}
|
||||
}
|
||||
|
||||
class EnterPinDialogState extends ConsumerState<EnterPinDialog> {
|
||||
final pinController = TextEditingController();
|
||||
final focusNode = FocusNode();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
pinController.dispose();
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final focusedBorderColor = scale.primaryScale.hoverBorder;
|
||||
final fillColor = scale.primaryScale.elementBackground;
|
||||
final borderColor = scale.primaryScale.border;
|
||||
|
||||
final defaultPinTheme = PinTheme(
|
||||
width: 56,
|
||||
height: 60,
|
||||
textStyle: TextStyle(fontSize: 22, color: scale.primaryScale.text),
|
||||
decoration: BoxDecoration(
|
||||
color: fillColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: borderColor),
|
||||
),
|
||||
);
|
||||
|
||||
/// Optionally you can use form to validate the Pinput
|
||||
return Dialog(
|
||||
backgroundColor: scale.grayScale.subtleBackground,
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.matchPin == null
|
||||
? translate('enter_pin_dialog.enter_pin')
|
||||
: translate('enter_pin_dialog.reenter_pin'),
|
||||
style: theme.textTheme.titleLarge,
|
||||
).paddingAll(16),
|
||||
Directionality(
|
||||
// Specify direction if desired
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Pinput(
|
||||
controller: pinController,
|
||||
focusNode: focusNode,
|
||||
autofocus: true,
|
||||
defaultPinTheme: defaultPinTheme,
|
||||
enableSuggestions: false,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
// validator: (widget.matchPin != null)
|
||||
// ? (value) => value == widget.matchPin
|
||||
// ? null
|
||||
// : translate('enter_pin_dialog.pin_does_not_match')
|
||||
// : null,
|
||||
// onClipboardFound: (value) {
|
||||
// debugPrint('onClipboardFound: $value');
|
||||
// pinController.setText(value);
|
||||
// },
|
||||
hapticFeedbackType: HapticFeedbackType.lightImpact,
|
||||
onCompleted: (pin) {
|
||||
debugPrint('onCompleted: $pin');
|
||||
Navigator.pop(context, pin);
|
||||
},
|
||||
onChanged: (value) {
|
||||
debugPrint('onChanged: $value');
|
||||
},
|
||||
cursor: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 9),
|
||||
width: 22,
|
||||
height: 1,
|
||||
color: focusedBorderColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
focusedPinTheme: defaultPinTheme.copyWith(
|
||||
height: 68,
|
||||
width: 64,
|
||||
decoration: defaultPinTheme.decoration!.copyWith(
|
||||
border: Border.all(color: borderColor),
|
||||
),
|
||||
),
|
||||
errorText: '',
|
||||
errorPinTheme: defaultPinTheme.copyWith(
|
||||
decoration: BoxDecoration(
|
||||
color: scale.errorScale.border,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
).paddingAll(16),
|
||||
),
|
||||
if (widget.description != null)
|
||||
SizedBox(
|
||||
width: 400,
|
||||
child: Text(
|
||||
widget.description!,
|
||||
textAlign: TextAlign.center,
|
||||
).paddingAll(16))
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty<TextEditingController>(
|
||||
'pinController', pinController))
|
||||
..add(DiagnosticsProperty<FocusNode>('focusNode', focusNode))
|
||||
..add(DiagnosticsProperty<GlobalKey<FormState>>('formKey', formKey));
|
||||
}
|
||||
}
|
162
lib/components/send_invite_dialog.dart
Normal file
162
lib/components/send_invite_dialog.dart
Normal file
@ -0,0 +1,162 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.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 '../tools/tools.dart';
|
||||
import 'contact_invitation_display.dart';
|
||||
import 'enter_pin.dart';
|
||||
|
||||
class SendInviteDialog extends ConsumerStatefulWidget {
|
||||
const SendInviteDialog({super.key});
|
||||
|
||||
@override
|
||||
SendInviteDialogState createState() => SendInviteDialogState();
|
||||
}
|
||||
|
||||
class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
|
||||
final messageTextController = TextEditingController(
|
||||
text: translate('send_invite_dialog.connect_with_me'));
|
||||
|
||||
EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none;
|
||||
String _encryptionKey = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> _onNoneEncryptionSelected(bool selected) async {
|
||||
setState(() {
|
||||
if (selected) {
|
||||
_encryptionKeyType = EncryptionKeyType.none;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onPinEncryptionSelected(bool selected) async {
|
||||
final description = translate('send_invite_dialog.pin_description');
|
||||
final pin = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => EnterPinDialog(description: description));
|
||||
if (pin == null) {
|
||||
return;
|
||||
}
|
||||
// ignore: use_build_context_synchronously
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
final matchpin = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => EnterPinDialog(
|
||||
matchPin: pin,
|
||||
description: description,
|
||||
));
|
||||
if (matchpin == null) {
|
||||
return;
|
||||
} else if (pin == matchpin) {
|
||||
setState(() {
|
||||
_encryptionKeyType = EncryptionKeyType.pin;
|
||||
_encryptionKey = pin;
|
||||
});
|
||||
} else {
|
||||
// ignore: use_build_context_synchronously
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
showErrorToast(
|
||||
context, translate('send_invite_dialog.pin_does_not_match'));
|
||||
setState(() {
|
||||
_encryptionKeyType = EncryptionKeyType.none;
|
||||
_encryptionKey = '';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onPasswordEncryptionSelected(bool selected) async {
|
||||
setState(() {
|
||||
if (selected) {
|
||||
_encryptionKeyType = EncryptionKeyType.password;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onGenerateButtonPressed() async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => ContactInvitationDisplayDialog());
|
||||
// if (ret == null) {
|
||||
// return;
|
||||
// }
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
//final scale = theme.extension<ScaleScheme>()!;
|
||||
final textTheme = theme.textTheme;
|
||||
return SizedBox(
|
||||
height: 400,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
translate('send_invite_dialog.message_to_contact'),
|
||||
).paddingAll(8),
|
||||
TextField(
|
||||
controller: messageTextController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: translate('send_invite_dialog.enter_message_hint'),
|
||||
labelText: translate('send_invite_dialog.message')),
|
||||
).paddingAll(8),
|
||||
const SizedBox(height: 10),
|
||||
Text(translate('send_invite_dialog.protect_this_invitation'),
|
||||
style: textTheme.labelLarge)
|
||||
.paddingAll(8),
|
||||
Wrap(spacing: 5, children: [
|
||||
ChoiceChip(
|
||||
label: Text(translate('send_invite_dialog.unlocked')),
|
||||
selected: _encryptionKeyType == EncryptionKeyType.none,
|
||||
onSelected: _onNoneEncryptionSelected,
|
||||
),
|
||||
ChoiceChip(
|
||||
label: Text(translate('send_invite_dialog.numeric_pin')),
|
||||
selected: _encryptionKeyType == EncryptionKeyType.pin,
|
||||
onSelected: _onPinEncryptionSelected,
|
||||
),
|
||||
ChoiceChip(
|
||||
label: Text(translate('send_invite_dialog.password')),
|
||||
selected: _encryptionKeyType == EncryptionKeyType.password,
|
||||
onSelected: _onPasswordEncryptionSelected,
|
||||
)
|
||||
]).paddingAll(8),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 60,
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ElevatedButton(
|
||||
onPressed: _onGenerateButtonPressed,
|
||||
child: Text(
|
||||
translate('send_invite_dialog.generate'),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(translate('send_invite_dialog.note')).paddingAll(8),
|
||||
Text(
|
||||
translate('send_invite_dialog.note_text'),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
).paddingAll(8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -131,8 +131,8 @@ extension IdentityMasterExtension on IdentityMaster {
|
||||
// Create new account to insert into identity
|
||||
await (await pool.create(parent: identityRec.key))
|
||||
.deleteScope((accountRec) async {
|
||||
// Make empty contact request list
|
||||
final contactRequests = await (await DHTShortArray.create())
|
||||
// Make empty contact invitation record list
|
||||
final contactInvitationRecords = await (await DHTShortArray.create())
|
||||
.scope((r) => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make account object
|
||||
@ -140,7 +140,7 @@ extension IdentityMasterExtension on IdentityMaster {
|
||||
..profile = (proto.Profile()
|
||||
..name = name
|
||||
..title = title)
|
||||
..contactRequests = contactRequests.toProto();
|
||||
..contactInvitationRecords = contactInvitationRecords.toProto();
|
||||
|
||||
// Write account key
|
||||
await accountRec.eventualWriteProtobuf(account);
|
||||
|
@ -3,6 +3,7 @@ import 'dart:typed_data';
|
||||
import 'package:change_case/change_case.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../entities/proto.dart' as proto;
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'identity.dart';
|
||||
|
||||
@ -22,7 +23,27 @@ enum EncryptionKeyType {
|
||||
factory EncryptionKeyType.fromJson(dynamic j) =>
|
||||
EncryptionKeyType.values.byName((j as String).toCamelCase());
|
||||
|
||||
factory EncryptionKeyType.fromProto(proto.EncryptionKeyType p) {
|
||||
// ignore: exhaustive_cases
|
||||
switch (p) {
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_NONE:
|
||||
return EncryptionKeyType.none;
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PIN:
|
||||
return EncryptionKeyType.pin;
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PASSWORD:
|
||||
return EncryptionKeyType.password;
|
||||
}
|
||||
throw StateError('unknown EncryptionKeyType enum value');
|
||||
}
|
||||
String toJson() => name.toPascalCase();
|
||||
proto.EncryptionKeyType toProto() => switch (this) {
|
||||
EncryptionKeyType.none =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_NONE,
|
||||
EncryptionKeyType.pin =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PIN,
|
||||
EncryptionKeyType.password =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PASSWORD,
|
||||
};
|
||||
}
|
||||
|
||||
// Local Accounts are stored in a table locally and not backed by a DHT key
|
||||
|
@ -1240,7 +1240,7 @@ class Account extends $pb.GeneratedMessage {
|
||||
..aOB(2, _omitFieldNames ? '' : 'invisible')
|
||||
..a<$core.int>(3, _omitFieldNames ? '' : 'autoAwayTimeoutSec', $pb.PbFieldType.OU3)
|
||||
..aOM<OwnedDHTRecordPointer>(4, _omitFieldNames ? '' : 'contactList', subBuilder: OwnedDHTRecordPointer.create)
|
||||
..aOM<OwnedDHTRecordPointer>(5, _omitFieldNames ? '' : 'contactRequests', subBuilder: OwnedDHTRecordPointer.create)
|
||||
..aOM<OwnedDHTRecordPointer>(5, _omitFieldNames ? '' : 'contactInvitationRecords', subBuilder: OwnedDHTRecordPointer.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@ -1306,15 +1306,15 @@ class Account extends $pb.GeneratedMessage {
|
||||
OwnedDHTRecordPointer ensureContactList() => $_ensure(3);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
OwnedDHTRecordPointer get contactRequests => $_getN(4);
|
||||
OwnedDHTRecordPointer get contactInvitationRecords => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set contactRequests(OwnedDHTRecordPointer v) { setField(5, v); }
|
||||
set contactInvitationRecords(OwnedDHTRecordPointer v) { setField(5, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasContactRequests() => $_has(4);
|
||||
$core.bool hasContactInvitationRecords() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearContactRequests() => clearField(5);
|
||||
void clearContactInvitationRecords() => clearField(5);
|
||||
@$pb.TagNumber(5)
|
||||
OwnedDHTRecordPointer ensureContactRequests() => $_ensure(4);
|
||||
OwnedDHTRecordPointer ensureContactInvitationRecords() => $_ensure(4);
|
||||
}
|
||||
|
||||
class ContactInvitation extends $pb.GeneratedMessage {
|
||||
@ -1432,9 +1432,8 @@ class ContactRequest extends $pb.GeneratedMessage {
|
||||
factory ContactRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactRequest', createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'writerSalt', $pb.PbFieldType.OY)
|
||||
..e<EncryptionKind>(2, _omitFieldNames ? '' : 'encryptionKeyType', $pb.PbFieldType.OE, defaultOrMaker: EncryptionKind.ENCRYPTION_KIND_UNSPECIFIED, valueOf: EncryptionKind.valueOf, enumValues: EncryptionKind.values)
|
||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'private', $pb.PbFieldType.OY)
|
||||
..e<EncryptionKeyType>(1, _omitFieldNames ? '' : 'encryptionKeyType', $pb.PbFieldType.OE, defaultOrMaker: EncryptionKeyType.ENCRYPTION_KEY_TYPE_UNSPECIFIED, valueOf: EncryptionKeyType.valueOf, enumValues: EncryptionKeyType.values)
|
||||
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'private', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@ -1460,31 +1459,22 @@ class ContactRequest extends $pb.GeneratedMessage {
|
||||
static ContactRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get writerSalt => $_getN(0);
|
||||
EncryptionKeyType get encryptionKeyType => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set writerSalt($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||
set encryptionKeyType(EncryptionKeyType v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasWriterSalt() => $_has(0);
|
||||
$core.bool hasEncryptionKeyType() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearWriterSalt() => clearField(1);
|
||||
void clearEncryptionKeyType() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
EncryptionKind get encryptionKeyType => $_getN(1);
|
||||
$core.List<$core.int> get private => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set encryptionKeyType(EncryptionKind v) { setField(2, v); }
|
||||
set private($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEncryptionKeyType() => $_has(1);
|
||||
$core.bool hasPrivate() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEncryptionKeyType() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get private => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set private($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasPrivate() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearPrivate() => clearField(3);
|
||||
void clearPrivate() => clearField(2);
|
||||
}
|
||||
|
||||
class ContactRequestPrivate extends $pb.GeneratedMessage {
|
||||
@ -1697,13 +1687,13 @@ class SignedContactResponse extends $pb.GeneratedMessage {
|
||||
Signature ensureIdentitySignature() => $_ensure(1);
|
||||
}
|
||||
|
||||
class ContactRequestRecord extends $pb.GeneratedMessage {
|
||||
factory ContactRequestRecord() => create();
|
||||
ContactRequestRecord._() : super();
|
||||
factory ContactRequestRecord.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ContactRequestRecord.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
class ContactInvitationRecord extends $pb.GeneratedMessage {
|
||||
factory ContactInvitationRecord() => create();
|
||||
ContactInvitationRecord._() : super();
|
||||
factory ContactInvitationRecord.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ContactInvitationRecord.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactRequestRecord', createEmptyInstance: create)
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactInvitationRecord', createEmptyInstance: create)
|
||||
..aOM<TypedKey>(1, _omitFieldNames ? '' : 'contactRequestRecordKey', subBuilder: TypedKey.create)
|
||||
..aOM<CryptoKey>(2, _omitFieldNames ? '' : 'writerKey', subBuilder: CryptoKey.create)
|
||||
..aOM<CryptoKey>(3, _omitFieldNames ? '' : 'writerSecret', subBuilder: CryptoKey.create)
|
||||
@ -1717,22 +1707,22 @@ class ContactRequestRecord extends $pb.GeneratedMessage {
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
ContactRequestRecord clone() => ContactRequestRecord()..mergeFromMessage(this);
|
||||
ContactInvitationRecord clone() => ContactInvitationRecord()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
ContactRequestRecord copyWith(void Function(ContactRequestRecord) updates) => super.copyWith((message) => updates(message as ContactRequestRecord)) as ContactRequestRecord;
|
||||
ContactInvitationRecord copyWith(void Function(ContactInvitationRecord) updates) => super.copyWith((message) => updates(message as ContactInvitationRecord)) as ContactInvitationRecord;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ContactRequestRecord create() => ContactRequestRecord._();
|
||||
ContactRequestRecord createEmptyInstance() => create();
|
||||
static $pb.PbList<ContactRequestRecord> createRepeated() => $pb.PbList<ContactRequestRecord>();
|
||||
static ContactInvitationRecord create() => ContactInvitationRecord._();
|
||||
ContactInvitationRecord createEmptyInstance() => create();
|
||||
static $pb.PbList<ContactInvitationRecord> createRepeated() => $pb.PbList<ContactInvitationRecord>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ContactRequestRecord getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ContactRequestRecord>(create);
|
||||
static ContactRequestRecord? _defaultInstance;
|
||||
static ContactInvitationRecord getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ContactInvitationRecord>(create);
|
||||
static ContactInvitationRecord? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
TypedKey get contactRequestRecordKey => $_getN(0);
|
||||
|
@ -51,21 +51,23 @@ class Availability extends $pb.ProtobufEnum {
|
||||
const Availability._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class EncryptionKind extends $pb.ProtobufEnum {
|
||||
static const EncryptionKind ENCRYPTION_KIND_UNSPECIFIED = EncryptionKind._(0, _omitEnumNames ? '' : 'ENCRYPTION_KIND_UNSPECIFIED');
|
||||
static const EncryptionKind ENCRYPTION_KIND_PIN = EncryptionKind._(1, _omitEnumNames ? '' : 'ENCRYPTION_KIND_PIN');
|
||||
static const EncryptionKind ENCRYPTION_KIND_PASSWORD = EncryptionKind._(2, _omitEnumNames ? '' : 'ENCRYPTION_KIND_PASSWORD');
|
||||
class EncryptionKeyType extends $pb.ProtobufEnum {
|
||||
static const EncryptionKeyType ENCRYPTION_KEY_TYPE_UNSPECIFIED = EncryptionKeyType._(0, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_UNSPECIFIED');
|
||||
static const EncryptionKeyType ENCRYPTION_KEY_TYPE_NONE = EncryptionKeyType._(1, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_NONE');
|
||||
static const EncryptionKeyType ENCRYPTION_KEY_TYPE_PIN = EncryptionKeyType._(2, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_PIN');
|
||||
static const EncryptionKeyType ENCRYPTION_KEY_TYPE_PASSWORD = EncryptionKeyType._(3, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_PASSWORD');
|
||||
|
||||
static const $core.List<EncryptionKind> values = <EncryptionKind> [
|
||||
ENCRYPTION_KIND_UNSPECIFIED,
|
||||
ENCRYPTION_KIND_PIN,
|
||||
ENCRYPTION_KIND_PASSWORD,
|
||||
static const $core.List<EncryptionKeyType> values = <EncryptionKeyType> [
|
||||
ENCRYPTION_KEY_TYPE_UNSPECIFIED,
|
||||
ENCRYPTION_KEY_TYPE_NONE,
|
||||
ENCRYPTION_KEY_TYPE_PIN,
|
||||
ENCRYPTION_KEY_TYPE_PASSWORD,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, EncryptionKind> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static EncryptionKind? valueOf($core.int value) => _byValue[value];
|
||||
static final $core.Map<$core.int, EncryptionKeyType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static EncryptionKeyType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const EncryptionKind._($core.int v, $core.String n) : super(v, n);
|
||||
const EncryptionKeyType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
|
||||
|
@ -46,20 +46,22 @@ final $typed_data.Uint8List availabilityDescriptor = $convert.base64Decode(
|
||||
'lMSVRZX09GRkxJTkUQARIVChFBVkFJTEFCSUxJVFlfRlJFRRACEhUKEUFWQUlMQUJJTElUWV9C'
|
||||
'VVNZEAMSFQoRQVZBSUxBQklMSVRZX0FXQVkQBA==');
|
||||
|
||||
@$core.Deprecated('Use encryptionKindDescriptor instead')
|
||||
const EncryptionKind$json = {
|
||||
'1': 'EncryptionKind',
|
||||
@$core.Deprecated('Use encryptionKeyTypeDescriptor instead')
|
||||
const EncryptionKeyType$json = {
|
||||
'1': 'EncryptionKeyType',
|
||||
'2': [
|
||||
{'1': 'ENCRYPTION_KIND_UNSPECIFIED', '2': 0},
|
||||
{'1': 'ENCRYPTION_KIND_PIN', '2': 1},
|
||||
{'1': 'ENCRYPTION_KIND_PASSWORD', '2': 2},
|
||||
{'1': 'ENCRYPTION_KEY_TYPE_UNSPECIFIED', '2': 0},
|
||||
{'1': 'ENCRYPTION_KEY_TYPE_NONE', '2': 1},
|
||||
{'1': 'ENCRYPTION_KEY_TYPE_PIN', '2': 2},
|
||||
{'1': 'ENCRYPTION_KEY_TYPE_PASSWORD', '2': 3},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `EncryptionKind`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List encryptionKindDescriptor = $convert.base64Decode(
|
||||
'Cg5FbmNyeXB0aW9uS2luZBIfChtFTkNSWVBUSU9OX0tJTkRfVU5TUEVDSUZJRUQQABIXChNFTk'
|
||||
'NSWVBUSU9OX0tJTkRfUElOEAESHAoYRU5DUllQVElPTl9LSU5EX1BBU1NXT1JEEAI=');
|
||||
/// Descriptor for `EncryptionKeyType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List encryptionKeyTypeDescriptor = $convert.base64Decode(
|
||||
'ChFFbmNyeXB0aW9uS2V5VHlwZRIjCh9FTkNSWVBUSU9OX0tFWV9UWVBFX1VOU1BFQ0lGSUVEEA'
|
||||
'ASHAoYRU5DUllQVElPTl9LRVlfVFlQRV9OT05FEAESGwoXRU5DUllQVElPTl9LRVlfVFlQRV9Q'
|
||||
'SU4QAhIgChxFTkNSWVBUSU9OX0tFWV9UWVBFX1BBU1NXT1JEEAM=');
|
||||
|
||||
@$core.Deprecated('Use cryptoKeyDescriptor instead')
|
||||
const CryptoKey$json = {
|
||||
@ -344,7 +346,7 @@ const Account$json = {
|
||||
{'1': 'invisible', '3': 2, '4': 1, '5': 8, '10': 'invisible'},
|
||||
{'1': 'auto_away_timeout_sec', '3': 3, '4': 1, '5': 13, '10': 'autoAwayTimeoutSec'},
|
||||
{'1': 'contact_list', '3': 4, '4': 1, '5': 11, '6': '.OwnedDHTRecordPointer', '10': 'contactList'},
|
||||
{'1': 'contact_requests', '3': 5, '4': 1, '5': 11, '6': '.OwnedDHTRecordPointer', '10': 'contactRequests'},
|
||||
{'1': 'contact_invitation_records', '3': 5, '4': 1, '5': 11, '6': '.OwnedDHTRecordPointer', '10': 'contactInvitationRecords'},
|
||||
],
|
||||
};
|
||||
|
||||
@ -353,8 +355,8 @@ final $typed_data.Uint8List accountDescriptor = $convert.base64Decode(
|
||||
'CgdBY2NvdW50EiIKB3Byb2ZpbGUYASABKAsyCC5Qcm9maWxlUgdwcm9maWxlEhwKCWludmlzaW'
|
||||
'JsZRgCIAEoCFIJaW52aXNpYmxlEjEKFWF1dG9fYXdheV90aW1lb3V0X3NlYxgDIAEoDVISYXV0'
|
||||
'b0F3YXlUaW1lb3V0U2VjEjkKDGNvbnRhY3RfbGlzdBgEIAEoCzIWLk93bmVkREhUUmVjb3JkUG'
|
||||
'9pbnRlclILY29udGFjdExpc3QSQQoQY29udGFjdF9yZXF1ZXN0cxgFIAEoCzIWLk93bmVkREhU'
|
||||
'UmVjb3JkUG9pbnRlclIPY29udGFjdFJlcXVlc3Rz');
|
||||
'9pbnRlclILY29udGFjdExpc3QSVAoaY29udGFjdF9pbnZpdGF0aW9uX3JlY29yZHMYBSABKAsy'
|
||||
'Fi5Pd25lZERIVFJlY29yZFBvaW50ZXJSGGNvbnRhY3RJbnZpdGF0aW9uUmVjb3Jkcw==');
|
||||
|
||||
@$core.Deprecated('Use contactInvitationDescriptor instead')
|
||||
const ContactInvitation$json = {
|
||||
@ -390,17 +392,15 @@ final $typed_data.Uint8List signedContactInvitationDescriptor = $convert.base64D
|
||||
const ContactRequest$json = {
|
||||
'1': 'ContactRequest',
|
||||
'2': [
|
||||
{'1': 'writer_salt', '3': 1, '4': 1, '5': 12, '10': 'writerSalt'},
|
||||
{'1': 'encryption_key_type', '3': 2, '4': 1, '5': 14, '6': '.EncryptionKind', '10': 'encryptionKeyType'},
|
||||
{'1': 'private', '3': 3, '4': 1, '5': 12, '10': 'private'},
|
||||
{'1': 'encryption_key_type', '3': 1, '4': 1, '5': 14, '6': '.EncryptionKeyType', '10': 'encryptionKeyType'},
|
||||
{'1': 'private', '3': 2, '4': 1, '5': 12, '10': 'private'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ContactRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List contactRequestDescriptor = $convert.base64Decode(
|
||||
'Cg5Db250YWN0UmVxdWVzdBIfCgt3cml0ZXJfc2FsdBgBIAEoDFIKd3JpdGVyU2FsdBI/ChNlbm'
|
||||
'NyeXB0aW9uX2tleV90eXBlGAIgASgOMg8uRW5jcnlwdGlvbktpbmRSEWVuY3J5cHRpb25LZXlU'
|
||||
'eXBlEhgKB3ByaXZhdGUYAyABKAxSB3ByaXZhdGU=');
|
||||
'Cg5Db250YWN0UmVxdWVzdBJCChNlbmNyeXB0aW9uX2tleV90eXBlGAEgASgOMhIuRW5jcnlwdG'
|
||||
'lvbktleVR5cGVSEWVuY3J5cHRpb25LZXlUeXBlEhgKB3ByaXZhdGUYAiABKAxSB3ByaXZhdGU=');
|
||||
|
||||
@$core.Deprecated('Use contactRequestPrivateDescriptor instead')
|
||||
const ContactRequestPrivate$json = {
|
||||
@ -453,9 +453,9 @@ final $typed_data.Uint8List signedContactResponseDescriptor = $convert.base64Dec
|
||||
'FjdFJlc3BvbnNlEjkKEmlkZW50aXR5X3NpZ25hdHVyZRgCIAEoCzIKLlNpZ25hdHVyZVIRaWRl'
|
||||
'bnRpdHlTaWduYXR1cmU=');
|
||||
|
||||
@$core.Deprecated('Use contactRequestRecordDescriptor instead')
|
||||
const ContactRequestRecord$json = {
|
||||
'1': 'ContactRequestRecord',
|
||||
@$core.Deprecated('Use contactInvitationRecordDescriptor instead')
|
||||
const ContactInvitationRecord$json = {
|
||||
'1': 'ContactInvitationRecord',
|
||||
'2': [
|
||||
{'1': 'contact_request_record_key', '3': 1, '4': 1, '5': 11, '6': '.TypedKey', '10': 'contactRequestRecordKey'},
|
||||
{'1': 'writer_key', '3': 2, '4': 1, '5': 11, '6': '.CryptoKey', '10': 'writerKey'},
|
||||
@ -466,12 +466,12 @@ const ContactRequestRecord$json = {
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ContactRequestRecord`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List contactRequestRecordDescriptor = $convert.base64Decode(
|
||||
'ChRDb250YWN0UmVxdWVzdFJlY29yZBJGChpjb250YWN0X3JlcXVlc3RfcmVjb3JkX2tleRgBIA'
|
||||
'EoCzIJLlR5cGVkS2V5Uhdjb250YWN0UmVxdWVzdFJlY29yZEtleRIpCgp3cml0ZXJfa2V5GAIg'
|
||||
'ASgLMgouQ3J5cHRvS2V5Ugl3cml0ZXJLZXkSLwoNd3JpdGVyX3NlY3JldBgDIAEoCzIKLkNyeX'
|
||||
'B0b0tleVIMd3JpdGVyU2VjcmV0EjEKD2NoYXRfcmVjb3JkX2tleRgEIAEoCzIJLlR5cGVkS2V5'
|
||||
'Ug1jaGF0UmVjb3JkS2V5Eh4KCmV4cGlyYXRpb24YBSABKARSCmV4cGlyYXRpb24SHgoKaW52aX'
|
||||
'RhdGlvbhgGIAEoDFIKaW52aXRhdGlvbg==');
|
||||
/// Descriptor for `ContactInvitationRecord`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List contactInvitationRecordDescriptor = $convert.base64Decode(
|
||||
'ChdDb250YWN0SW52aXRhdGlvblJlY29yZBJGChpjb250YWN0X3JlcXVlc3RfcmVjb3JkX2tleR'
|
||||
'gBIAEoCzIJLlR5cGVkS2V5Uhdjb250YWN0UmVxdWVzdFJlY29yZEtleRIpCgp3cml0ZXJfa2V5'
|
||||
'GAIgASgLMgouQ3J5cHRvS2V5Ugl3cml0ZXJLZXkSLwoNd3JpdGVyX3NlY3JldBgDIAEoCzIKLk'
|
||||
'NyeXB0b0tleVIMd3JpdGVyU2VjcmV0EjEKD2NoYXRfcmVjb3JkX2tleRgEIAEoCzIJLlR5cGVk'
|
||||
'S2V5Ug1jaGF0UmVjb3JkS2V5Eh4KCmV4cGlyYXRpb24YBSABKARSCmV4cGlyYXRpb24SHgoKaW'
|
||||
'52aXRhdGlvbhgGIAEoDFIKaW52aXRhdGlvbg==');
|
||||
|
||||
|
@ -258,17 +258,18 @@ message Account {
|
||||
// The contacts DHTList for this account
|
||||
// DHT Private
|
||||
OwnedDHTRecordPointer contact_list = 4;
|
||||
// The contact requests DHTShortArray for this account
|
||||
// The ContactInvitationRecord DHTShortArray for this account
|
||||
// DHT Private
|
||||
OwnedDHTRecordPointer contact_requests = 5;
|
||||
OwnedDHTRecordPointer contact_invitation_records = 5;
|
||||
}
|
||||
|
||||
// EncryptionKind
|
||||
// EncryptionKeyType
|
||||
// Encryption of secret
|
||||
enum EncryptionKind {
|
||||
ENCRYPTION_KIND_UNSPECIFIED = 0;
|
||||
ENCRYPTION_KIND_PIN = 1;
|
||||
ENCRYPTION_KIND_PASSWORD =2;
|
||||
enum EncryptionKeyType {
|
||||
ENCRYPTION_KEY_TYPE_UNSPECIFIED = 0;
|
||||
ENCRYPTION_KEY_TYPE_NONE = 1;
|
||||
ENCRYPTION_KEY_TYPE_PIN = 2;
|
||||
ENCRYPTION_KEY_TYPE_PASSWORD = 3;
|
||||
}
|
||||
|
||||
// Invitation that is shared for VeilidChat contact connections
|
||||
@ -277,7 +278,7 @@ enum EncryptionKind {
|
||||
message ContactInvitation {
|
||||
// Contact request DHT record key
|
||||
TypedKey contact_request_record_key = 1;
|
||||
// Writer secret key bytes possibly encrypted
|
||||
// Writer secret key bytes possibly encrypted with nonce appended
|
||||
bytes writer_secret = 2;
|
||||
}
|
||||
|
||||
@ -290,14 +291,12 @@ message SignedContactInvitation {
|
||||
}
|
||||
|
||||
// Contact request unicastinbox on the DHT
|
||||
// DHTSchema: SMPL 2 owner key, 1 writer key symmetrically encrypted with writer secret
|
||||
// DHTSchema: SMPL 1 owner key, 1 writer key symmetrically encrypted with writer secret
|
||||
message ContactRequest {
|
||||
// The salt for the encryption used on the unicastinbox writer secret
|
||||
bytes writer_salt = 1;
|
||||
// The kind of encryption used on the unicastinbox writer key
|
||||
EncryptionKind encryption_key_type = 2;
|
||||
EncryptionKeyType encryption_key_type = 1;
|
||||
// The private part encoded and symmetrically encrypted with the unicastinbox writer secret
|
||||
bytes private = 3;
|
||||
bytes private = 2;
|
||||
}
|
||||
|
||||
// The private part of a possibly encrypted contact request
|
||||
@ -335,7 +334,7 @@ message SignedContactResponse {
|
||||
}
|
||||
|
||||
// Contact request record kept in Account DHTList to keep track of extant contact invitations
|
||||
message ContactRequestRecord {
|
||||
message ContactInvitationRecord {
|
||||
// Contact request unicastinbox DHT record key
|
||||
TypedKey contact_request_record_key = 1;
|
||||
// Unencrypted writer key for this request
|
||||
@ -345,6 +344,6 @@ message ContactRequestRecord {
|
||||
TypedKey chat_record_key = 4;
|
||||
// Expiration timestamp
|
||||
uint64 expiration = 5;
|
||||
// A copy of the raw invitation bytes post-encryption
|
||||
// A copy of the raw SignedContactResponse invitation bytes post-encryption and signing
|
||||
bytes invitation = 6;
|
||||
}
|
@ -5,6 +5,8 @@ import 'package:split_view/split_view.dart';
|
||||
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
|
||||
|
||||
import '../components/chat_component.dart';
|
||||
import '../providers/local_accounts.dart';
|
||||
import '../providers/logins.dart';
|
||||
import '../providers/window_control.dart';
|
||||
import '../tools/tools.dart';
|
||||
import 'main_pager/main_pager.dart';
|
||||
|
@ -1,12 +1,19 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.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';
|
||||
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
||||
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
|
||||
|
||||
import '../../components/bottom_sheet_action_button.dart';
|
||||
import '../../components/contact_invitation_display.dart';
|
||||
import '../../components/send_invite_dialog.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import 'account_page.dart';
|
||||
import 'chats_page.dart';
|
||||
|
||||
@ -102,22 +109,81 @@ class MainPagerState extends ConsumerState<MainPager>
|
||||
return bottomBarItems;
|
||||
}
|
||||
|
||||
Future<void> _onNewContactInvitation(BuildContext context) async {
|
||||
Scaffold.of(context).showBottomSheet<void>((context) => SizedBox(
|
||||
height: 200, child: Center(child: ContactInvitationDisplay())));
|
||||
Future<void> sendContactInvitationDialog(BuildContext context) async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
// ignore: prefer_expression_function_bodies
|
||||
builder: (context) {
|
||||
return const AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(
|
||||
top: 10,
|
||||
),
|
||||
title: Text(
|
||||
'Send Contact Invite',
|
||||
style: TextStyle(fontSize: 24),
|
||||
),
|
||||
content: SendInviteDialog());
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onNewChat(BuildContext context) async {
|
||||
//
|
||||
Widget _newContactInvitationBottomSheetBuilder(
|
||||
// ignore: prefer_expression_function_bodies
|
||||
BuildContext context) {
|
||||
return KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKeyEvent: (ke) {
|
||||
if (ke.logicalKey == LogicalKeyboardKey.escape) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
child: Column(children: [
|
||||
Text(translate('accounts_menu.invite_contact'),
|
||||
style: Theme.of(context).textTheme.titleMedium)
|
||||
.paddingAll(8),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
await sendContactInvitationDialog(context);
|
||||
},
|
||||
iconSize: 64,
|
||||
icon: const Icon(Icons.output)),
|
||||
Text(translate('accounts_menu.send_invite'))
|
||||
]),
|
||||
Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
iconSize: 64,
|
||||
icon: const Icon(Icons.input)),
|
||||
Text(translate('accounts_menu.receive_invite'))
|
||||
])
|
||||
]).expanded()
|
||||
])));
|
||||
}
|
||||
|
||||
Future<void> _onFloatingActionButtonPressed(BuildContext context) async {
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget _onNewChatBottomSheetBuilder(BuildContext context) {
|
||||
return const SizedBox(height: 200, child: Center(child: Text("test")));
|
||||
}
|
||||
|
||||
Widget _bottomSheetBuilder(BuildContext context) {
|
||||
if (_currentPage == 0) {
|
||||
// New contact invitation
|
||||
return _onNewContactInvitation(context);
|
||||
return _newContactInvitationBottomSheetBuilder(context);
|
||||
} else if (_currentPage == 1) {
|
||||
// New chat
|
||||
return _onNewChat(context);
|
||||
return _onNewChatBottomSheetBuilder(context);
|
||||
} else {
|
||||
// Unknown error
|
||||
return waitingPage(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,6 +217,7 @@ class MainPagerState extends ConsumerState<MainPager>
|
||||
// theme.colorScheme.primary,
|
||||
// theme.colorScheme.primaryContainer,
|
||||
// ]),
|
||||
//borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
option: AnimatedBarOptions(
|
||||
// iconSize: 32,
|
||||
//barAnimation: BarAnimation.fade,
|
||||
@ -172,16 +239,16 @@ class MainPagerState extends ConsumerState<MainPager>
|
||||
},
|
||||
),
|
||||
|
||||
floatingActionButton: FloatingActionButton(
|
||||
floatingActionButton: BottomSheetActionButton(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14))),
|
||||
//foregroundColor: theme.colorScheme.secondary,
|
||||
backgroundColor: theme.colorScheme.secondaryContainer,
|
||||
child: Icon(
|
||||
_fabIconList[_currentPage],
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
onPressed: () async => _onFloatingActionButtonPressed(context)),
|
||||
builder: (context) => Icon(
|
||||
_fabIconList[_currentPage],
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
bottomSheetBuilder: _bottomSheetBuilder),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../entities/local_account.dart';
|
||||
import '../entities/proto.dart' as proto;
|
||||
import '../entities/user_login.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
|
||||
import 'local_accounts.dart';
|
||||
@ -70,3 +72,61 @@ Future<AccountInfo> fetchAccount(FetchAccountRef ref,
|
||||
return AccountInfo(
|
||||
status: AccountInfoStatus.accountReady, active: active, account: account);
|
||||
}
|
||||
|
||||
class ActiveAccountInfo {
|
||||
ActiveAccountInfo({
|
||||
required this.localAccount,
|
||||
required this.userLogin,
|
||||
required this.account,
|
||||
});
|
||||
|
||||
LocalAccount localAccount;
|
||||
UserLogin userLogin;
|
||||
proto.Account account;
|
||||
}
|
||||
|
||||
/// Get the active account info
|
||||
@riverpod
|
||||
Future<ActiveAccountInfo?> fetchActiveAccount(FetchAccountRef 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));
|
||||
if (activeUserLogin == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the user login
|
||||
final userLogin = await ref.watch(
|
||||
fetchLoginProvider(accountMasterRecordKey: activeUserLogin).future);
|
||||
if (userLogin == null) {
|
||||
// Account was locked
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get which local account we want to fetch the profile for
|
||||
final localAccount = await ref.watch(
|
||||
fetchLocalAccountProvider(accountMasterRecordKey: activeUserLogin)
|
||||
.future);
|
||||
if (localAccount == null) {
|
||||
// Local account does not exist
|
||||
return null;
|
||||
}
|
||||
|
||||
// Pull the account DHT key, decode it and return it
|
||||
final pool = await DHTRecordPool.instance();
|
||||
final account = await (await pool.openOwned(
|
||||
userLogin.accountRecordInfo.accountRecord,
|
||||
parent: localAccount.identityMaster.identityRecordKey))
|
||||
.scope((accountRec) => accountRec.getProtobuf(proto.Account.fromBuffer));
|
||||
if (account == null) {
|
||||
// Account could not be read or decrypted from DHT
|
||||
return null;
|
||||
}
|
||||
|
||||
// Got account, decrypted and decoded
|
||||
return ActiveAccountInfo(
|
||||
localAccount: localAccount,
|
||||
userLogin: userLogin,
|
||||
account: account,
|
||||
);
|
||||
}
|
||||
|
@ -128,4 +128,25 @@ class FetchAccountProvider extends AutoDisposeFutureProvider<AccountInfo> {
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
String _$fetchActiveAccountHash() =>
|
||||
r'8c7e571c135deeb5cacf56c61459d71f7447baaf';
|
||||
|
||||
/// Get the active account info
|
||||
///
|
||||
/// Copied from [fetchActiveAccount].
|
||||
@ProviderFor(fetchActiveAccount)
|
||||
final fetchActiveAccountProvider =
|
||||
AutoDisposeFutureProvider<ActiveAccountInfo?>.internal(
|
||||
fetchActiveAccount,
|
||||
name: r'fetchActiveAccountProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchActiveAccountHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef FetchActiveAccountRef
|
||||
= AutoDisposeFutureProviderRef<ActiveAccountInfo?>;
|
||||
// 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
|
||||
|
103
lib/providers/contact.dart
Normal file
103
lib/providers/contact.dart
Normal file
@ -0,0 +1,103 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.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<Uint8List> createContactInvitation(
|
||||
ActiveAccountInfo activeAccountInfo,
|
||||
EncryptionKeyType encryptionKeyType,
|
||||
String encryptionKey,
|
||||
Timestamp expiration) async {
|
||||
final pool = await DHTRecordPool.instance();
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
final identityKey =
|
||||
activeAccountInfo.localAccount.identityMaster.identityPublicKey;
|
||||
final identitySecret = activeAccountInfo.userLogin.identitySecret.value;
|
||||
|
||||
// Generate writer keypair to share with new contact
|
||||
final cs = await pool.veilid.bestCryptoSystem();
|
||||
final writer = await cs.generateKeyPair();
|
||||
|
||||
// Encrypt the writer secret with the encryption key
|
||||
final encryptedSecret = await encryptSecretToBytes(
|
||||
secret: writer.secret,
|
||||
cryptoKind: cs.kind(),
|
||||
encryptionKey: encryptionKey,
|
||||
encryptionKeyType: encryptionKeyType);
|
||||
|
||||
// Create local chat DHT record with the account record key as its parent
|
||||
// Do not set the encryption of this key yet as it will not yet be written
|
||||
// to and it will be eventually encrypted with the DH of the contact's
|
||||
// identity key
|
||||
late final Uint8List signedContactInvitationBytes;
|
||||
await (await pool.create(parent: accountRecordKey))
|
||||
.deleteScope((localChatRecord) async {
|
||||
// Make ContactRequestPrivate and encrypt with the writer secret
|
||||
final crpriv = proto.ContactRequestPrivate()
|
||||
..writerKey = writer.key.toProto()
|
||||
..profile = activeAccountInfo.account.profile
|
||||
..accountMasterRecordKey =
|
||||
activeAccountInfo.userLogin.accountMasterRecordKey.toProto()
|
||||
..chatRecordKey = localChatRecord.key.toProto()
|
||||
..expiration = expiration.toInt64();
|
||||
final crprivbytes = crpriv.writeToBuffer();
|
||||
final encryptedContactRequestPrivate =
|
||||
await cs.encryptNoAuthWithNonce(crprivbytes, writer.secret);
|
||||
|
||||
// Create ContactRequest and embed contactrequestprivate
|
||||
final creq = proto.ContactRequest()
|
||||
..encryptionKeyType = encryptionKeyType.toProto()
|
||||
..private = encryptedContactRequestPrivate;
|
||||
|
||||
// Create DHT unicast inbox for ContactRequest
|
||||
await (await pool.create(
|
||||
parent: accountRecordKey,
|
||||
schema: DHTSchema.smpl(
|
||||
oCnt: 1, members: [DHTSchemaMember(mCnt: 1, mKey: writer.key)]),
|
||||
crypto: const DHTRecordCryptoPublic()))
|
||||
.deleteScope((inboxRecord) async {
|
||||
// Store ContactRequest in owner subkey
|
||||
await inboxRecord.eventualWriteProtobuf(creq);
|
||||
|
||||
// Create ContactInvitation and SignedContactInvitation
|
||||
final cinv = proto.ContactInvitation()
|
||||
..contactRequestRecordKey = inboxRecord.key.toProto()
|
||||
..writerSecret = encryptedSecret;
|
||||
final cinvbytes = cinv.writeToBuffer();
|
||||
final scinv = proto.SignedContactInvitation()
|
||||
..contactInvitation = cinvbytes
|
||||
..identitySignature =
|
||||
(await cs.sign(identityKey, identitySecret, cinvbytes)).toProto();
|
||||
signedContactInvitationBytes = scinv.writeToBuffer();
|
||||
|
||||
// Create ContactInvitationRecord
|
||||
final cinvrec = proto.ContactInvitationRecord()
|
||||
..contactRequestRecordKey = inboxRecord.key.toProto()
|
||||
..writerKey = writer.key.toProto()
|
||||
..writerSecret = writer.secret.toProto()
|
||||
..chatRecordKey = localChatRecord.key.toProto()
|
||||
..expiration = expiration.toInt64()
|
||||
..invitation = signedContactInvitationBytes;
|
||||
|
||||
// Add ContactInvitationRecord to local table
|
||||
await (await DHTShortArray.openOwned(
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||
activeAccountInfo.account.contactInvitationRecords),
|
||||
parent: accountRecordKey))
|
||||
.scope((cirList) async {
|
||||
if (await cirList.tryAddItem(cinvrec.writeToBuffer()) == false) {
|
||||
throw StateError('Failed to add contact invitation record');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return signedContactInvitationBytes;
|
||||
}
|
@ -51,33 +51,6 @@ class LocalAccounts extends _$LocalAccounts
|
||||
state = AsyncValue.data(updated);
|
||||
}
|
||||
|
||||
/// Make encrypted identitySecret
|
||||
Future<Uint8List> _encryptIdentitySecret(
|
||||
{required SecretKey identitySecret,
|
||||
required CryptoKind cryptoKind,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = ''}) async {
|
||||
final veilid = await eventualVeilid.future;
|
||||
|
||||
late final Uint8List identitySecretBytes;
|
||||
switch (encryptionKeyType) {
|
||||
case EncryptionKeyType.none:
|
||||
identitySecretBytes = identitySecret.decode();
|
||||
case EncryptionKeyType.pin:
|
||||
case EncryptionKeyType.password:
|
||||
final cs = await veilid.getCryptoSystem(cryptoKind);
|
||||
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||
final nonce = await cs.randomNonce();
|
||||
final identitySecretSaltBytes = nonce.decode();
|
||||
final sharedSecret =
|
||||
await cs.deriveSharedSecret(ekbytes, identitySecretSaltBytes);
|
||||
identitySecretBytes = (await cs.cryptNoAuth(
|
||||
identitySecret.decode(), nonce, sharedSecret))
|
||||
..addAll(identitySecretSaltBytes);
|
||||
}
|
||||
return identitySecretBytes;
|
||||
}
|
||||
|
||||
/// Creates a new Account associated with master identity
|
||||
/// Adds a logged-out LocalAccount to track its existence on this device
|
||||
Future<LocalAccount> newLocalAccount(
|
||||
@ -97,8 +70,8 @@ class LocalAccounts extends _$LocalAccounts
|
||||
);
|
||||
|
||||
// Encrypt identitySecret with key
|
||||
final identitySecretBytes = await _encryptIdentitySecret(
|
||||
identitySecret: identitySecret,
|
||||
final identitySecretBytes = await encryptSecretToBytes(
|
||||
secret: identitySecret,
|
||||
cryptoKind: identityMaster.identityRecordKey.kind,
|
||||
encryptionKey: encryptionKey,
|
||||
encryptionKeyType: encryptionKeyType);
|
||||
|
@ -112,7 +112,7 @@ class FetchLocalAccountProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$localAccountsHash() => r'a9a1e1765188556858ec982c9e99f780756ade1e';
|
||||
String _$localAccountsHash() => r'80485dab3a2d1024fb5ffe29d9272dc4f3db2dff';
|
||||
|
||||
/// See also [LocalAccounts].
|
||||
@ProviderFor(LocalAccounts)
|
||||
|
@ -121,18 +121,10 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
||||
}
|
||||
final cs = await veilid
|
||||
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
|
||||
final encryptionKeyBytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||
|
||||
final identitySecretKeyBytes =
|
||||
localAccount.identitySecretBytes.sublist(0, SecretKey.decodedLength());
|
||||
final identitySecretSaltBytes =
|
||||
localAccount.identitySecretBytes.sublist(SecretKey.decodedLength());
|
||||
|
||||
final nonce = Nonce.fromBytes(identitySecretSaltBytes);
|
||||
final sharedSecret = await cs.deriveSharedSecret(
|
||||
encryptionKeyBytes, identitySecretSaltBytes);
|
||||
final identitySecret = SecretKey.fromBytes(
|
||||
await cs.cryptNoAuth(identitySecretKeyBytes, nonce, sharedSecret));
|
||||
await cs.decryptNoAuthWithPassword(
|
||||
localAccount.identitySecretBytes, encryptionKey));
|
||||
|
||||
// Validate this secret with the identity public key and log in
|
||||
return _loginCommon(localAccount.identityMaster, identitySecret);
|
||||
|
@ -111,7 +111,7 @@ class FetchLoginProvider extends AutoDisposeFutureProvider<UserLogin?> {
|
||||
}
|
||||
}
|
||||
|
||||
String _$loginsHash() => r'5720eaacf858b2e1d69ebf9d2a981173a30f8592';
|
||||
String _$loginsHash() => r'fdabd035aaa7ae2521ed4b7d984b6ff41576f0ba';
|
||||
|
||||
/// See also [Logins].
|
||||
@ProviderFor(Logins)
|
||||
|
@ -472,50 +472,48 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
|
||||
return radixScheme;
|
||||
}
|
||||
|
||||
ExtendedColorScheme _radixColorScheme(
|
||||
Brightness brightness, RadixThemeColor themeColor) {
|
||||
final radix = _radixScheme(brightness, themeColor);
|
||||
|
||||
return ExtendedColorScheme(
|
||||
scaleScheme: radix.toScale(),
|
||||
brightness: brightness,
|
||||
primary: radix.primaryScale.step9,
|
||||
onPrimary: radix.primaryScale.step12,
|
||||
primaryContainer: radix.primaryScale.step4,
|
||||
onPrimaryContainer: radix.primaryScale.step11,
|
||||
secondary: radix.secondaryScale.step9,
|
||||
onSecondary: radix.secondaryScale.step12,
|
||||
secondaryContainer: radix.secondaryScale.step3,
|
||||
onSecondaryContainer: radix.secondaryScale.step11,
|
||||
tertiary: radix.tertiaryScale.step9,
|
||||
onTertiary: radix.tertiaryScale.step12,
|
||||
tertiaryContainer: radix.tertiaryScale.step3,
|
||||
onTertiaryContainer: radix.tertiaryScale.step11,
|
||||
error: radix.errorScale.step9,
|
||||
onError: radix.errorScale.step12,
|
||||
errorContainer: radix.errorScale.step3,
|
||||
onErrorContainer: radix.errorScale.step11,
|
||||
background: radix.grayScale.step1,
|
||||
onBackground: radix.grayScale.step11,
|
||||
surface: radix.primaryScale.step1,
|
||||
onSurface: radix.primaryScale.step12,
|
||||
surfaceVariant: radix.secondaryScale.step2,
|
||||
onSurfaceVariant: radix.secondaryScale.step11,
|
||||
outline: radix.primaryScale.step7,
|
||||
outlineVariant: radix.primaryScale.step6,
|
||||
shadow: RadixColors.dark.gray.step1,
|
||||
scrim: radix.primaryScale.step9,
|
||||
inverseSurface: radix.primaryScale.step11,
|
||||
onInverseSurface: radix.primaryScale.step2,
|
||||
inversePrimary: radix.primaryScale.step10,
|
||||
surfaceTint: radix.primaryAlphaScale.step4,
|
||||
);
|
||||
}
|
||||
ColorScheme _radixColorScheme(Brightness brightness, RadixScheme radix) =>
|
||||
ColorScheme(
|
||||
brightness: brightness,
|
||||
primary: radix.primaryScale.step9,
|
||||
onPrimary: radix.primaryScale.step12,
|
||||
primaryContainer: radix.primaryScale.step4,
|
||||
onPrimaryContainer: radix.primaryScale.step11,
|
||||
secondary: radix.secondaryScale.step9,
|
||||
onSecondary: radix.secondaryScale.step12,
|
||||
secondaryContainer: radix.secondaryScale.step3,
|
||||
onSecondaryContainer: radix.secondaryScale.step11,
|
||||
tertiary: radix.tertiaryScale.step9,
|
||||
onTertiary: radix.tertiaryScale.step12,
|
||||
tertiaryContainer: radix.tertiaryScale.step3,
|
||||
onTertiaryContainer: radix.tertiaryScale.step11,
|
||||
error: radix.errorScale.step9,
|
||||
onError: radix.errorScale.step12,
|
||||
errorContainer: radix.errorScale.step3,
|
||||
onErrorContainer: radix.errorScale.step11,
|
||||
background: radix.grayScale.step1,
|
||||
onBackground: radix.grayScale.step11,
|
||||
surface: radix.primaryScale.step1,
|
||||
onSurface: radix.primaryScale.step12,
|
||||
surfaceVariant: radix.secondaryScale.step2,
|
||||
onSurfaceVariant: radix.secondaryScale.step11,
|
||||
outline: radix.primaryScale.step7,
|
||||
outlineVariant: radix.primaryScale.step6,
|
||||
shadow: RadixColors.dark.gray.step1,
|
||||
scrim: radix.primaryScale.step9,
|
||||
inverseSurface: radix.primaryScale.step11,
|
||||
onInverseSurface: radix.primaryScale.step2,
|
||||
inversePrimary: radix.primaryScale.step10,
|
||||
surfaceTint: radix.primaryAlphaScale.step4,
|
||||
);
|
||||
|
||||
ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
|
||||
TextTheme? textTheme;
|
||||
final radix = _radixScheme(brightness, themeColor);
|
||||
final colorScheme = _radixColorScheme(brightness, radix);
|
||||
return ThemeData.from(
|
||||
colorScheme: _radixColorScheme(brightness, themeColor),
|
||||
textTheme: textTheme,
|
||||
useMaterial3: true);
|
||||
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true)
|
||||
.copyWith(extensions: <ThemeExtension<dynamic>>[
|
||||
radix.toScale(),
|
||||
]);
|
||||
}
|
||||
|
25
lib/tools/secret_crypto.dart
Normal file
25
lib/tools/secret_crypto.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import '../entities/local_account.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
|
||||
Future<Uint8List> encryptSecretToBytes(
|
||||
{required SecretKey secret,
|
||||
required CryptoKind cryptoKind,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = ''}) async {
|
||||
final veilid = await eventualVeilid.future;
|
||||
|
||||
late final Uint8List identitySecretBytes;
|
||||
switch (encryptionKeyType) {
|
||||
case EncryptionKeyType.none:
|
||||
identitySecretBytes = secret.decode();
|
||||
case EncryptionKeyType.pin:
|
||||
case EncryptionKeyType.password:
|
||||
final cs = await veilid.getCryptoSystem(cryptoKind);
|
||||
|
||||
identitySecretBytes =
|
||||
await cs.encryptNoAuthWithPassword(secret.decode(), encryptionKey);
|
||||
}
|
||||
return identitySecretBytes;
|
||||
}
|
@ -2,59 +2,13 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../entities/preferences.dart';
|
||||
import 'radix_generator.dart';
|
||||
|
||||
@immutable
|
||||
class ExtendedColorScheme extends ColorScheme {
|
||||
const ExtendedColorScheme({
|
||||
required this.scaleScheme,
|
||||
required super.brightness,
|
||||
required super.primary,
|
||||
required super.onPrimary,
|
||||
super.primaryContainer,
|
||||
super.onPrimaryContainer,
|
||||
required super.secondary,
|
||||
required super.onSecondary,
|
||||
super.secondaryContainer,
|
||||
super.onSecondaryContainer,
|
||||
super.tertiary,
|
||||
super.onTertiary,
|
||||
super.tertiaryContainer,
|
||||
super.onTertiaryContainer,
|
||||
required super.error,
|
||||
required super.onError,
|
||||
super.errorContainer,
|
||||
super.onErrorContainer,
|
||||
required super.background,
|
||||
required super.onBackground,
|
||||
required super.surface,
|
||||
required super.onSurface,
|
||||
super.surfaceVariant,
|
||||
super.onSurfaceVariant,
|
||||
super.outline,
|
||||
super.outlineVariant,
|
||||
super.shadow,
|
||||
super.scrim,
|
||||
super.inverseSurface,
|
||||
super.onInverseSurface,
|
||||
super.inversePrimary,
|
||||
super.surfaceTint,
|
||||
});
|
||||
|
||||
final ScaleScheme scaleScheme;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<ScaleScheme>('scales', scaleScheme));
|
||||
}
|
||||
}
|
||||
|
||||
class ScaleColor {
|
||||
ScaleColor({
|
||||
required this.appBackground,
|
||||
@ -83,9 +37,69 @@ class ScaleColor {
|
||||
Color hoverBackground;
|
||||
Color subtleText;
|
||||
Color text;
|
||||
|
||||
ScaleColor copyWith(
|
||||
{Color? appBackground,
|
||||
Color? subtleBackground,
|
||||
Color? elementBackground,
|
||||
Color? hoverElementBackground,
|
||||
Color? activedElementBackground,
|
||||
Color? subtleBorder,
|
||||
Color? border,
|
||||
Color? hoverBorder,
|
||||
Color? background,
|
||||
Color? hoverBackground,
|
||||
Color? subtleText,
|
||||
Color? text}) =>
|
||||
ScaleColor(
|
||||
appBackground: appBackground ?? this.appBackground,
|
||||
subtleBackground: subtleBackground ?? this.subtleBackground,
|
||||
elementBackground: elementBackground ?? this.elementBackground,
|
||||
hoverElementBackground:
|
||||
hoverElementBackground ?? this.hoverElementBackground,
|
||||
activedElementBackground:
|
||||
activedElementBackground ?? this.activedElementBackground,
|
||||
subtleBorder: subtleBorder ?? this.subtleBorder,
|
||||
border: border ?? this.border,
|
||||
hoverBorder: hoverBorder ?? this.hoverBorder,
|
||||
background: background ?? this.background,
|
||||
hoverBackground: hoverBackground ?? this.hoverBackground,
|
||||
subtleText: subtleText ?? this.subtleText,
|
||||
text: text ?? this.text,
|
||||
);
|
||||
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static ScaleColor lerp(ScaleColor a, ScaleColor b, double t) => ScaleColor(
|
||||
appBackground: Color.lerp(a.appBackground, b.appBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
subtleBackground:
|
||||
Color.lerp(a.subtleBackground, b.subtleBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
elementBackground:
|
||||
Color.lerp(a.elementBackground, b.elementBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
hoverElementBackground:
|
||||
Color.lerp(a.hoverElementBackground, b.hoverElementBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
activedElementBackground: Color.lerp(
|
||||
a.activedElementBackground, b.activedElementBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
subtleBorder: Color.lerp(a.subtleBorder, b.subtleBorder, t) ??
|
||||
const Color(0x00000000),
|
||||
border: Color.lerp(a.border, b.border, t) ?? const Color(0x00000000),
|
||||
hoverBorder: Color.lerp(a.hoverBorder, b.hoverBorder, t) ??
|
||||
const Color(0x00000000),
|
||||
background: Color.lerp(a.background, b.background, t) ??
|
||||
const Color(0x00000000),
|
||||
hoverBackground: Color.lerp(a.hoverBackground, b.hoverBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
subtleText: Color.lerp(a.subtleText, b.subtleText, t) ??
|
||||
const Color(0x00000000),
|
||||
text: Color.lerp(a.text, b.text, t) ?? const Color(0x00000000),
|
||||
);
|
||||
}
|
||||
|
||||
class ScaleScheme {
|
||||
class ScaleScheme extends ThemeExtension<ScaleScheme> {
|
||||
ScaleScheme(
|
||||
{required this.primaryScale,
|
||||
required this.primaryAlphaScale,
|
||||
@ -94,15 +108,66 @@ class ScaleScheme {
|
||||
required this.grayScale,
|
||||
required this.errorScale});
|
||||
|
||||
ScaleColor primaryScale;
|
||||
ScaleColor primaryAlphaScale;
|
||||
ScaleColor secondaryScale;
|
||||
ScaleColor tertiaryScale;
|
||||
ScaleColor grayScale;
|
||||
ScaleColor errorScale;
|
||||
final ScaleColor primaryScale;
|
||||
final ScaleColor primaryAlphaScale;
|
||||
final ScaleColor secondaryScale;
|
||||
final ScaleColor tertiaryScale;
|
||||
final ScaleColor grayScale;
|
||||
final ScaleColor errorScale;
|
||||
|
||||
static ScaleScheme of(BuildContext context) =>
|
||||
(Theme.of(context).colorScheme as ExtendedColorScheme).scaleScheme;
|
||||
@override
|
||||
ScaleScheme copyWith(
|
||||
{ScaleColor? primaryScale,
|
||||
ScaleColor? primaryAlphaScale,
|
||||
ScaleColor? secondaryScale,
|
||||
ScaleColor? tertiaryScale,
|
||||
ScaleColor? grayScale,
|
||||
ScaleColor? errorScale}) =>
|
||||
ScaleScheme(
|
||||
primaryScale: primaryScale ?? this.primaryScale,
|
||||
primaryAlphaScale: primaryAlphaScale ?? this.primaryAlphaScale,
|
||||
secondaryScale: secondaryScale ?? this.secondaryScale,
|
||||
tertiaryScale: tertiaryScale ?? this.tertiaryScale,
|
||||
grayScale: grayScale ?? this.grayScale,
|
||||
errorScale: errorScale ?? this.errorScale,
|
||||
);
|
||||
|
||||
@override
|
||||
ScaleScheme lerp(ScaleScheme? other, double t) {
|
||||
if (other is! ScaleScheme) {
|
||||
return this;
|
||||
}
|
||||
return ScaleScheme(
|
||||
primaryScale: ScaleColor.lerp(primaryScale, other.primaryScale, t),
|
||||
primaryAlphaScale:
|
||||
ScaleColor.lerp(primaryAlphaScale, other.primaryAlphaScale, t),
|
||||
secondaryScale: ScaleColor.lerp(secondaryScale, other.secondaryScale, t),
|
||||
tertiaryScale: ScaleColor.lerp(tertiaryScale, other.tertiaryScale, t),
|
||||
grayScale: ScaleColor.lerp(grayScale, other.grayScale, t),
|
||||
errorScale: ScaleColor.lerp(errorScale, other.errorScale, t),
|
||||
);
|
||||
}
|
||||
|
||||
ChatTheme toChatTheme() => DefaultChatTheme(
|
||||
primaryColor: primaryScale.background,
|
||||
secondaryColor: secondaryScale.background,
|
||||
backgroundColor: grayScale.subtleBackground,
|
||||
inputBackgroundColor: primaryScale.appBackground,
|
||||
inputBorderRadius: BorderRadius.zero,
|
||||
inputTextDecoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: primaryScale.subtleBorder),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16))),
|
||||
),
|
||||
inputContainerDecoration: BoxDecoration(color: grayScale.appBackground),
|
||||
inputPadding: EdgeInsets.all(5),
|
||||
inputTextStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1,
|
||||
),
|
||||
attachmentButtonIcon: Icon(Icons.attach_file),
|
||||
);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -5,5 +5,6 @@ export 'phono_byte.dart';
|
||||
export 'protobuf_tools.dart';
|
||||
export 'radix_generator.dart';
|
||||
export 'responsive.dart';
|
||||
export 'secret_crypto.dart';
|
||||
export 'theme_service.dart';
|
||||
export 'widget_helpers.dart';
|
||||
|
@ -1,8 +1,11 @@
|
||||
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:motion_toast/motion_toast.dart';
|
||||
import 'package:quickalert/quickalert.dart';
|
||||
|
||||
import 'theme_service.dart';
|
||||
|
||||
extension BorderExt on Widget {
|
||||
DecoratedBox debugBorder() => DecoratedBox(
|
||||
decoration: BoxDecoration(border: Border.all(color: Colors.redAccent)),
|
||||
@ -10,19 +13,27 @@ extension BorderExt on Widget {
|
||||
}
|
||||
|
||||
extension ModalProgressExt on Widget {
|
||||
BlurryModalProgressHUD withModalHUD(BuildContext context, bool isLoading) =>
|
||||
BlurryModalProgressHUD(
|
||||
inAsyncCall: isLoading,
|
||||
blurEffectIntensity: 4,
|
||||
progressIndicator: buildProgressIndicator(context),
|
||||
color: Theme.of(context).shadowColor,
|
||||
child: this);
|
||||
BlurryModalProgressHUD withModalHUD(BuildContext context, bool isLoading) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
return BlurryModalProgressHUD(
|
||||
inAsyncCall: isLoading,
|
||||
blurEffectIntensity: 4,
|
||||
progressIndicator: buildProgressIndicator(context),
|
||||
color: scale.tertiaryScale.appBackground.withAlpha(64),
|
||||
child: this);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildProgressIndicator(BuildContext context) => SpinKitFoldingCube(
|
||||
color: Theme.of(context).highlightColor,
|
||||
size: 90,
|
||||
);
|
||||
Widget buildProgressIndicator(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
return SpinKitFoldingCube(
|
||||
color: scale.tertiaryScale.background,
|
||||
size: 80,
|
||||
);
|
||||
}
|
||||
|
||||
Widget waitingPage(BuildContext context) => ColoredBox(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
@ -40,3 +51,10 @@ Future<void> showErrorModal(
|
||||
//textColor: Colors.white,
|
||||
);
|
||||
}
|
||||
|
||||
void showErrorToast(BuildContext context, String message) {
|
||||
MotionToast.error(
|
||||
title: Text("Error"),
|
||||
description: Text(message),
|
||||
).show(context);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class DHTRecord {
|
||||
_recordDescriptor = recordDescriptor,
|
||||
_defaultSubkey = defaultSubkey,
|
||||
_writer = writer,
|
||||
_open = false,
|
||||
_open = true,
|
||||
_valid = true,
|
||||
_subkeySeqCache = {};
|
||||
final VeilidRoutingContext _routingContext;
|
||||
|
@ -36,28 +36,12 @@ class DHTRecordCryptoPrivate implements DHTRecordCrypto {
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) async {
|
||||
// generate nonce
|
||||
final nonce = await _cryptoSystem.randomNonce();
|
||||
// crypt and append nonce
|
||||
final b = BytesBuilder()
|
||||
..add(await _cryptoSystem.cryptNoAuth(data, nonce, _secretKey))
|
||||
..add(nonce.decode());
|
||||
return b.toBytes();
|
||||
}
|
||||
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) =>
|
||||
_cryptoSystem.encryptNoAuthWithNonce(data, _secretKey);
|
||||
|
||||
@override
|
||||
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) async {
|
||||
// split off nonce from end
|
||||
if (data.length <= Nonce.decodedLength()) {
|
||||
throw const FormatException('not enough data to decrypt');
|
||||
}
|
||||
final nonce =
|
||||
Nonce.fromBytes(data.sublist(data.length - Nonce.decodedLength()));
|
||||
final encryptedData = data.sublist(0, data.length - Nonce.decodedLength());
|
||||
// decrypt
|
||||
return await _cryptoSystem.cryptNoAuth(encryptedData, nonce, _secretKey);
|
||||
}
|
||||
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) =>
|
||||
_cryptoSystem.decryptNoAuthWithNonce(data, _secretKey);
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
|
@ -10,8 +10,10 @@ part 'dht_record_pool.g.dart';
|
||||
@freezed
|
||||
class DHTRecordPoolAllocations with _$DHTRecordPoolAllocations {
|
||||
const factory DHTRecordPoolAllocations({
|
||||
required IMap<TypedKey, ISet<TypedKey>> childrenByParent,
|
||||
required IMap<TypedKey, TypedKey> parentByChild,
|
||||
required IMap<String, ISet<TypedKey>>
|
||||
childrenByParent, // String key due to IMap<> json unsupported in key
|
||||
required IMap<String, TypedKey>
|
||||
parentByChild, // String key due to IMap<> json unsupported in key
|
||||
}) = _DHTRecordPoolAllocations;
|
||||
|
||||
factory DHTRecordPoolAllocations.fromJson(dynamic json) =>
|
||||
@ -100,13 +102,14 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||
final nextDep = currentDeps.removeLast();
|
||||
|
||||
// Remove this child from its parent
|
||||
_removeDependency(nextDep);
|
||||
await _removeDependency(nextDep);
|
||||
|
||||
// Ensure all records are closed before delete
|
||||
assert(!_opened.containsKey(nextDep), 'should not delete opened record');
|
||||
|
||||
allDeps.add(nextDep);
|
||||
final childDeps = _state.childrenByParent[nextDep]?.toList() ?? [];
|
||||
final childDeps =
|
||||
_state.childrenByParent[nextDep.toJson()]?.toList() ?? [];
|
||||
currentDeps.addAll(childDeps);
|
||||
}
|
||||
|
||||
@ -118,41 +121,45 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||
await Future.wait(allFutures);
|
||||
}
|
||||
|
||||
void _addDependency(TypedKey parent, TypedKey child) {
|
||||
Future<void> _addDependency(TypedKey parent, TypedKey child) async {
|
||||
final childrenOfParent =
|
||||
_state.childrenByParent[parent] ?? ISet<TypedKey>();
|
||||
_state.childrenByParent[parent.toJson()] ?? ISet<TypedKey>();
|
||||
if (childrenOfParent.contains(child)) {
|
||||
throw StateError('Dependency added twice: $parent -> $child');
|
||||
// Dependency already added (consecutive opens, etc)
|
||||
return;
|
||||
}
|
||||
if (_state.parentByChild.containsKey(child)) {
|
||||
if (_state.parentByChild.containsKey(child.toJson())) {
|
||||
throw StateError('Child has two parents: $child <- $parent');
|
||||
}
|
||||
if (_state.childrenByParent.containsKey(child)) {
|
||||
if (_state.childrenByParent.containsKey(child.toJson())) {
|
||||
// dependencies should be opened after their parents
|
||||
throw StateError('Child is not a leaf: $child');
|
||||
}
|
||||
|
||||
_state = _state.copyWith(
|
||||
childrenByParent:
|
||||
_state.childrenByParent.add(parent, childrenOfParent.add(child)),
|
||||
parentByChild: _state.parentByChild.add(child, parent));
|
||||
_state = await store(_state.copyWith(
|
||||
childrenByParent: _state.childrenByParent
|
||||
.add(parent.toJson(), childrenOfParent.add(child)),
|
||||
parentByChild: _state.parentByChild.add(child.toJson(), parent)));
|
||||
}
|
||||
|
||||
void _removeDependency(TypedKey child) {
|
||||
final parent = _state.parentByChild[child];
|
||||
Future<void> _removeDependency(TypedKey child) async {
|
||||
final parent = _state.parentByChild[child.toJson()];
|
||||
if (parent == null) {
|
||||
return;
|
||||
}
|
||||
final children = _state.childrenByParent[parent]!.remove(child);
|
||||
final children = _state.childrenByParent[parent.toJson()]!.remove(child);
|
||||
late final DHTRecordPoolAllocations newState;
|
||||
if (children.isEmpty) {
|
||||
_state = _state.copyWith(
|
||||
childrenByParent: _state.childrenByParent.remove(parent),
|
||||
parentByChild: _state.parentByChild.remove(child));
|
||||
newState = _state.copyWith(
|
||||
childrenByParent: _state.childrenByParent.remove(parent.toJson()),
|
||||
parentByChild: _state.parentByChild.remove(child.toJson()));
|
||||
} else {
|
||||
_state = _state.copyWith(
|
||||
childrenByParent: _state.childrenByParent.add(parent, children),
|
||||
parentByChild: _state.parentByChild.remove(child));
|
||||
newState = _state.copyWith(
|
||||
childrenByParent:
|
||||
_state.childrenByParent.add(parent.toJson(), children),
|
||||
parentByChild: _state.parentByChild.remove(child.toJson()));
|
||||
}
|
||||
_state = await store(newState);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
@ -177,7 +184,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||
recordDescriptor.ownerTypedKeyPair()!));
|
||||
|
||||
if (parent != null) {
|
||||
_addDependency(parent, rec.key);
|
||||
await _addDependency(parent, rec.key);
|
||||
}
|
||||
_recordOpened(rec);
|
||||
|
||||
@ -192,7 +199,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||
DHTRecordCrypto? crypto}) async {
|
||||
// If we are opening a key that already exists
|
||||
// make sure we are using the same parent if one was specified
|
||||
final existingParent = _state.parentByChild[recordKey];
|
||||
final existingParent = _state.parentByChild[recordKey.toJson()];
|
||||
assert(existingParent == parent, 'wrong parent for opened key');
|
||||
|
||||
// Open from the veilid api
|
||||
@ -206,7 +213,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||
|
||||
// Register the dependency if specified
|
||||
if (parent != null) {
|
||||
_addDependency(parent, rec.key);
|
||||
await _addDependency(parent, rec.key);
|
||||
}
|
||||
_recordOpened(rec);
|
||||
|
||||
@ -224,7 +231,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||
}) async {
|
||||
// If we are opening a key that already exists
|
||||
// make sure we are using the same parent if one was specified
|
||||
final existingParent = _state.parentByChild[recordKey];
|
||||
final existingParent = _state.parentByChild[recordKey.toJson()];
|
||||
assert(existingParent == parent, 'wrong parent for opened key');
|
||||
|
||||
// Open from the veilid api
|
||||
@ -241,7 +248,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||
|
||||
// Register the dependency if specified
|
||||
if (parent != null) {
|
||||
_addDependency(parent, rec.key);
|
||||
await _addDependency(parent, rec.key);
|
||||
}
|
||||
_recordOpened(rec);
|
||||
|
||||
|
@ -21,10 +21,10 @@ DHTRecordPoolAllocations _$DHTRecordPoolAllocationsFromJson(
|
||||
|
||||
/// @nodoc
|
||||
mixin _$DHTRecordPoolAllocations {
|
||||
IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
||||
get childrenByParent => throw _privateConstructorUsedError;
|
||||
IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
||||
get parentByChild => throw _privateConstructorUsedError;
|
||||
IMap<String, ISet<Typed<FixedEncodedString43>>> get childrenByParent =>
|
||||
throw _privateConstructorUsedError; // String key due to IMap<> json unsupported in key
|
||||
IMap<String, Typed<FixedEncodedString43>> get parentByChild =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
@ -39,10 +39,8 @@ abstract class $DHTRecordPoolAllocationsCopyWith<$Res> {
|
||||
_$DHTRecordPoolAllocationsCopyWithImpl<$Res, DHTRecordPoolAllocations>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
||||
childrenByParent,
|
||||
IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
||||
parentByChild});
|
||||
{IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent,
|
||||
IMap<String, Typed<FixedEncodedString43>> parentByChild});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -66,12 +64,11 @@ class _$DHTRecordPoolAllocationsCopyWithImpl<$Res,
|
||||
childrenByParent: null == childrenByParent
|
||||
? _value.childrenByParent
|
||||
: childrenByParent // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<Typed<FixedEncodedString43>,
|
||||
ISet<Typed<FixedEncodedString43>>>,
|
||||
as IMap<String, ISet<Typed<FixedEncodedString43>>>,
|
||||
parentByChild: null == parentByChild
|
||||
? _value.parentByChild
|
||||
: parentByChild // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>,
|
||||
as IMap<String, Typed<FixedEncodedString43>>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@ -86,10 +83,8 @@ abstract class _$$_DHTRecordPoolAllocationsCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
||||
childrenByParent,
|
||||
IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
||||
parentByChild});
|
||||
{IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent,
|
||||
IMap<String, Typed<FixedEncodedString43>> parentByChild});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -111,12 +106,11 @@ class __$$_DHTRecordPoolAllocationsCopyWithImpl<$Res>
|
||||
childrenByParent: null == childrenByParent
|
||||
? _value.childrenByParent
|
||||
: childrenByParent // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<Typed<FixedEncodedString43>,
|
||||
ISet<Typed<FixedEncodedString43>>>,
|
||||
as IMap<String, ISet<Typed<FixedEncodedString43>>>,
|
||||
parentByChild: null == parentByChild
|
||||
? _value.parentByChild
|
||||
: parentByChild // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>,
|
||||
as IMap<String, Typed<FixedEncodedString43>>,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -131,11 +125,10 @@ class _$_DHTRecordPoolAllocations implements _DHTRecordPoolAllocations {
|
||||
_$$_DHTRecordPoolAllocationsFromJson(json);
|
||||
|
||||
@override
|
||||
final IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
||||
childrenByParent;
|
||||
final IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent;
|
||||
// String key due to IMap<> json unsupported in key
|
||||
@override
|
||||
final IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
||||
parentByChild;
|
||||
final IMap<String, Typed<FixedEncodedString43>> parentByChild;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@ -174,22 +167,18 @@ class _$_DHTRecordPoolAllocations implements _DHTRecordPoolAllocations {
|
||||
|
||||
abstract class _DHTRecordPoolAllocations implements DHTRecordPoolAllocations {
|
||||
const factory _DHTRecordPoolAllocations(
|
||||
{required final IMap<Typed<FixedEncodedString43>,
|
||||
ISet<Typed<FixedEncodedString43>>>
|
||||
{required final IMap<String, ISet<Typed<FixedEncodedString43>>>
|
||||
childrenByParent,
|
||||
required final IMap<Typed<FixedEncodedString43>,
|
||||
Typed<FixedEncodedString43>>
|
||||
required final IMap<String, Typed<FixedEncodedString43>>
|
||||
parentByChild}) = _$_DHTRecordPoolAllocations;
|
||||
|
||||
factory _DHTRecordPoolAllocations.fromJson(Map<String, dynamic> json) =
|
||||
_$_DHTRecordPoolAllocations.fromJson;
|
||||
|
||||
@override
|
||||
IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
||||
get childrenByParent;
|
||||
@override
|
||||
IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
||||
get parentByChild;
|
||||
IMap<String, ISet<Typed<FixedEncodedString43>>> get childrenByParent;
|
||||
@override // String key due to IMap<> json unsupported in key
|
||||
IMap<String, Typed<FixedEncodedString43>> get parentByChild;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$_DHTRecordPoolAllocationsCopyWith<_$_DHTRecordPoolAllocations>
|
||||
|
@ -9,16 +9,15 @@ part of 'dht_record_pool.dart';
|
||||
_$_DHTRecordPoolAllocations _$$_DHTRecordPoolAllocationsFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$_DHTRecordPoolAllocations(
|
||||
childrenByParent: IMap<Typed<FixedEncodedString43>,
|
||||
ISet<Typed<FixedEncodedString43>>>.fromJson(
|
||||
json['children_by_parent'] as Map<String, dynamic>,
|
||||
(value) => Typed<FixedEncodedString43>.fromJson(value),
|
||||
(value) => ISet<Typed<FixedEncodedString43>>.fromJson(
|
||||
value, (value) => Typed<FixedEncodedString43>.fromJson(value))),
|
||||
parentByChild: IMap<Typed<FixedEncodedString43>,
|
||||
Typed<FixedEncodedString43>>.fromJson(
|
||||
childrenByParent:
|
||||
IMap<String, ISet<Typed<FixedEncodedString43>>>.fromJson(
|
||||
json['children_by_parent'] as Map<String, dynamic>,
|
||||
(value) => value as String,
|
||||
(value) => ISet<Typed<FixedEncodedString43>>.fromJson(value,
|
||||
(value) => Typed<FixedEncodedString43>.fromJson(value))),
|
||||
parentByChild: IMap<String, Typed<FixedEncodedString43>>.fromJson(
|
||||
json['parent_by_child'] as Map<String, dynamic>,
|
||||
(value) => Typed<FixedEncodedString43>.fromJson(value),
|
||||
(value) => value as String,
|
||||
(value) => Typed<FixedEncodedString43>.fromJson(value)),
|
||||
);
|
||||
|
||||
@ -26,13 +25,13 @@ Map<String, dynamic> _$$_DHTRecordPoolAllocationsToJson(
|
||||
_$_DHTRecordPoolAllocations instance) =>
|
||||
<String, dynamic>{
|
||||
'children_by_parent': instance.childrenByParent.toJson(
|
||||
(value) => value.toJson(),
|
||||
(value) => value,
|
||||
(value) => value.toJson(
|
||||
(value) => value.toJson(),
|
||||
),
|
||||
),
|
||||
'parent_by_child': instance.parentByChild.toJson(
|
||||
(value) => value.toJson(),
|
||||
(value) => value,
|
||||
(value) => value.toJson(),
|
||||
),
|
||||
};
|
||||
|
@ -26,50 +26,52 @@ class IdentityMasterWithSecrets {
|
||||
return (await pool.create(crypto: const DHTRecordCryptoPublic()))
|
||||
.deleteScope((masterRec) async {
|
||||
// Identity record is private
|
||||
final identityRec = await pool.create(parent: masterRec.key);
|
||||
// Make IdentityMaster
|
||||
final masterRecordKey = masterRec.key;
|
||||
final masterOwner = masterRec.ownerKeyPair!;
|
||||
final masterSigBuf = BytesBuilder()
|
||||
..add(masterRecordKey.decode())
|
||||
..add(masterOwner.key.decode());
|
||||
return (await pool.create(parent: masterRec.key))
|
||||
.scope((identityRec) async {
|
||||
// Make IdentityMaster
|
||||
final masterRecordKey = masterRec.key;
|
||||
final masterOwner = masterRec.ownerKeyPair!;
|
||||
final masterSigBuf = BytesBuilder()
|
||||
..add(masterRecordKey.decode())
|
||||
..add(masterOwner.key.decode());
|
||||
|
||||
final identityRecordKey = identityRec.key;
|
||||
final identityOwner = identityRec.ownerKeyPair!;
|
||||
final identitySigBuf = BytesBuilder()
|
||||
..add(identityRecordKey.decode())
|
||||
..add(identityOwner.key.decode());
|
||||
final identityRecordKey = identityRec.key;
|
||||
final identityOwner = identityRec.ownerKeyPair!;
|
||||
final identitySigBuf = BytesBuilder()
|
||||
..add(identityRecordKey.decode())
|
||||
..add(identityOwner.key.decode());
|
||||
|
||||
assert(masterRecordKey.kind == identityRecordKey.kind,
|
||||
'new master and identity should have same cryptosystem');
|
||||
final crypto = await pool.veilid.getCryptoSystem(masterRecordKey.kind);
|
||||
assert(masterRecordKey.kind == identityRecordKey.kind,
|
||||
'new master and identity should have same cryptosystem');
|
||||
final crypto = await pool.veilid.getCryptoSystem(masterRecordKey.kind);
|
||||
|
||||
final identitySignature =
|
||||
await crypto.signWithKeyPair(masterOwner, identitySigBuf.toBytes());
|
||||
final masterSignature =
|
||||
await crypto.signWithKeyPair(identityOwner, masterSigBuf.toBytes());
|
||||
final identitySignature =
|
||||
await crypto.signWithKeyPair(masterOwner, identitySigBuf.toBytes());
|
||||
final masterSignature =
|
||||
await crypto.signWithKeyPair(identityOwner, masterSigBuf.toBytes());
|
||||
|
||||
final identityMaster = IdentityMaster(
|
||||
identityRecordKey: identityRecordKey,
|
||||
identityPublicKey: identityOwner.key,
|
||||
masterRecordKey: masterRecordKey,
|
||||
masterPublicKey: masterOwner.key,
|
||||
identitySignature: identitySignature,
|
||||
masterSignature: masterSignature);
|
||||
final identityMaster = IdentityMaster(
|
||||
identityRecordKey: identityRecordKey,
|
||||
identityPublicKey: identityOwner.key,
|
||||
masterRecordKey: masterRecordKey,
|
||||
masterPublicKey: masterOwner.key,
|
||||
identitySignature: identitySignature,
|
||||
masterSignature: masterSignature);
|
||||
|
||||
// Write identity master to master dht key
|
||||
await masterRec.eventualWriteJson(identityMaster);
|
||||
// Write identity master to master dht key
|
||||
await masterRec.eventualWriteJson(identityMaster);
|
||||
|
||||
// Make empty identity
|
||||
const identity = Identity(accountRecords: IMapConst({}));
|
||||
// Make empty identity
|
||||
const identity = Identity(accountRecords: IMapConst({}));
|
||||
|
||||
// Write empty identity to identity dht key
|
||||
await identityRec.eventualWriteJson(identity);
|
||||
// Write empty identity to identity dht key
|
||||
await identityRec.eventualWriteJson(identity);
|
||||
|
||||
return IdentityMasterWithSecrets._(
|
||||
identityMaster: identityMaster,
|
||||
masterSecret: masterOwner.secret,
|
||||
identitySecret: identityOwner.secret);
|
||||
return IdentityMasterWithSecrets._(
|
||||
identityMaster: identityMaster,
|
||||
masterSecret: masterOwner.secret,
|
||||
identitySecret: identityOwner.secret);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -47,9 +47,10 @@ abstract mixin class AsyncTableDBBacked<T> {
|
||||
}
|
||||
|
||||
/// Store things to storage
|
||||
Future<void> store(T obj) async {
|
||||
Future<T> store(T obj) async {
|
||||
await tableScope(tableName(), (tdb) async {
|
||||
await tdb.storeStringJson(0, tableKeyName(), valueToJson(obj));
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <smart_auth/smart_auth_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
#include <veilid/veilid_plugin.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
@ -15,6 +16,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
|
||||
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
|
||||
g_autoptr(FlPluginRegistrar) smart_auth_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "SmartAuthPlugin");
|
||||
smart_auth_plugin_register_with_registrar(smart_auth_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
screen_retriever
|
||||
smart_auth
|
||||
url_launcher_linux
|
||||
veilid
|
||||
window_manager
|
||||
|
@ -9,6 +9,7 @@ import path_provider_foundation
|
||||
import screen_retriever
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
import smart_auth
|
||||
import sqflite
|
||||
import url_launcher_macos
|
||||
import veilid
|
||||
@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SmartAuthPlugin.register(with: registry.registrar(forPlugin: "SmartAuthPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
VeilidPlugin.register(with: registry.registrar(forPlugin: "VeilidPlugin"))
|
||||
|
@ -8,9 +8,13 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- screen_retriever (0.0.1):
|
||||
- FlutterMacOS
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- smart_auth (0.0.1):
|
||||
- FlutterMacOS
|
||||
- sqflite (0.0.2):
|
||||
- FlutterMacOS
|
||||
- FMDB (>= 2.7.5)
|
||||
@ -25,7 +29,9 @@ DEPENDENCIES:
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
|
||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- smart_auth (from `Flutter/ephemeral/.symlinks/plugins/smart_auth/macos`)
|
||||
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- veilid (from `Flutter/ephemeral/.symlinks/plugins/veilid/macos`)
|
||||
@ -42,8 +48,12 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
screen_retriever:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
|
||||
share_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
smart_auth:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/smart_auth/macos
|
||||
sqflite:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
|
||||
url_launcher_macos:
|
||||
@ -58,7 +68,9 @@ SPEC CHECKSUMS:
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
smart_auth: b38e3ab4bfe089eacb1e233aca1a2340f96c28e9
|
||||
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
|
||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||
veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0
|
||||
|
36
pubspec.lock
36
pubspec.lock
@ -757,6 +757,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
motion_toast:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: motion_toast
|
||||
sha256: f27cfcd39c6a0c433670fb20e4add55c42f925a5382b25f58e917c054d47a624
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.8"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -853,6 +861,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.0"
|
||||
pinput:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pinput
|
||||
sha256: "543da5bfdefd9e06914a12100f8c9156f84cef3efc14bca507c49e966c5b813b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1090,6 +1106,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
smart_auth:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: smart_auth
|
||||
sha256: a25229b38c02f733d0a4e98d941b42bed91a976cb589e934895e60ccfa674cf6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1250,6 +1274,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
universal_platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_platform
|
||||
sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0+1"
|
||||
url_launcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1360,7 +1392,7 @@ packages:
|
||||
path: "../veilid/veilid-flutter"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.1.6"
|
||||
version: "0.1.7"
|
||||
visibility_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1427,4 +1459,4 @@ packages:
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.0.5 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
flutter: ">=3.10.6"
|
||||
|
@ -40,8 +40,10 @@ dependencies:
|
||||
intl: ^0.18.0
|
||||
json_annotation: ^4.8.1
|
||||
loggy: ^2.0.3
|
||||
motion_toast: ^2.7.8
|
||||
path: ^1.8.2
|
||||
path_provider: ^2.0.11
|
||||
pinput: ^2.3.0
|
||||
protobuf: ^3.0.0
|
||||
quickalert: ^1.0.1
|
||||
radix_colors: ^1.0.4
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <smart_auth/smart_auth_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <veilid/veilid_plugin.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
@ -17,6 +18,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
SmartAuthPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SmartAuthPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
VeilidPluginRegisterWithRegistrar(
|
||||
|
@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
screen_retriever
|
||||
share_plus
|
||||
smart_auth
|
||||
url_launcher_windows
|
||||
veilid
|
||||
window_manager
|
||||
|
Loading…
Reference in New Issue
Block a user