mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-06-01 04:24:31 -04:00
contact invitation algorithm
This commit is contained in:
parent
c35056f687
commit
f52094c105
43 changed files with 1319 additions and 451 deletions
|
@ -43,12 +43,37 @@
|
||||||
"account_page": {
|
"account_page": {
|
||||||
"missing_account_title": "Missing Account",
|
"missing_account_title": "Missing Account",
|
||||||
"missing_account_text": "Account is missing, removing from list",
|
"missing_account_text": "Account is missing, removing from list",
|
||||||
"invalid_account_title": "Missing Account",
|
"invalid_account_title": "Invalid Account",
|
||||||
"invalid_account_text": "Account is missing, removing from list"
|
"invalid_account_text": "Account is invalid, removing from list"
|
||||||
},
|
},
|
||||||
"empty_contact_list": {
|
"empty_contact_list": {
|
||||||
"invite_people": "Invite people to VeilidChat"
|
"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": {
|
"themes": {
|
||||||
"vapor": "Vapor"
|
"vapor": "Vapor"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,13 @@
|
||||||
2. Encrypt secret with requested encryption type
|
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)
|
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
|
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
|
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
|
8. Create SignedContactInvitation embedding ContactInvitation
|
||||||
9. Render SignedContactInvitation to shareable encoding (qr code, text blob, etc)
|
9. Create ContactInvitationRecord and add to local table in Account
|
||||||
10. Share SignedContactInvitation out of band to desired contact, along with password somehow if used
|
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
|
## Receiving an invitation
|
||||||
1. Receive SignedContactInvitation from out of band, and the password somehow if used
|
1. Receive SignedContactInvitation from out of band, and the password somehow if used
|
||||||
|
|
|
@ -6,6 +6,8 @@ PODS:
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- share_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
@ -14,15 +16,19 @@ PODS:
|
||||||
- FMDB (>= 2.7.5)
|
- FMDB (>= 2.7.5)
|
||||||
- system_info_plus (0.0.1):
|
- system_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
- veilid (0.0.1):
|
- veilid (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- 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`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
- system_info_plus (from `.symlinks/plugins/system_info_plus/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`)
|
- veilid (from `.symlinks/plugins/veilid/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
|
@ -34,12 +40,16 @@ EXTERNAL SOURCES:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
|
share_plus:
|
||||||
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
system_info_plus:
|
system_info_plus:
|
||||||
:path: ".symlinks/plugins/system_info_plus/ios"
|
:path: ".symlinks/plugins/system_info_plus/ios"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
veilid:
|
veilid:
|
||||||
:path: ".symlinks/plugins/veilid/ios"
|
:path: ".symlinks/plugins/veilid/ios"
|
||||||
|
|
||||||
|
@ -47,9 +57,11 @@ SPEC CHECKSUMS:
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||||
|
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||||
system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa
|
system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa
|
||||||
|
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||||
veilid: f5c2e662f91907b30cf95762619526ac3e4512fd
|
veilid: f5c2e662f91907b30cf95762619526ac3e4512fd
|
||||||
|
|
||||||
PODFILE CHECKSUM: fcab1959fbc0528061dce4ed4f921740dc46f1e5
|
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:intl/date_symbol_data_local.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
import '../tools/theme_service.dart';
|
||||||
|
|
||||||
class ChatComponent extends ConsumerStatefulWidget {
|
class ChatComponent extends ConsumerStatefulWidget {
|
||||||
const ChatComponent({super.key});
|
const ChatComponent({super.key});
|
||||||
|
|
||||||
|
@ -63,56 +65,68 @@ class ChatComponentState extends ConsumerState<ChatComponent> {
|
||||||
_addMessage(textMessage);
|
_addMessage(textMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleAttachmentPressed() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget build(BuildContext context) {
|
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(
|
return DefaultTextStyle(
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
style: textTheme.bodySmall!,
|
||||||
child: Container(
|
child: Align(
|
||||||
decoration: BoxDecoration(
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
child: Container(
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: Stack(
|
color: scale.primaryScale.appBackground,
|
||||||
children: [
|
),
|
||||||
Column(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Column(
|
||||||
height: 48,
|
children: [
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
color: Theme.of(context).primaryColor,
|
height: 48,
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: Align(
|
color: scale.primaryScale.subtleBackground,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
),
|
||||||
child: Padding(
|
child: Align(
|
||||||
padding:
|
alignment: AlignmentDirectional.centerStart,
|
||||||
const EdgeInsetsDirectional.fromSTEB(16, 0, 16, 0),
|
child: Padding(
|
||||||
child: Text("current contact",
|
padding: const EdgeInsetsDirectional.fromSTEB(
|
||||||
textAlign: TextAlign.start,
|
16, 0, 16, 0),
|
||||||
style: Theme.of(context).textTheme.titleMedium),
|
child: Text("current contact",
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
style: textTheme.titleMedium),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
),
|
child: Container(
|
||||||
Expanded(
|
decoration: const BoxDecoration(),
|
||||||
child: Container(
|
child: Chat(
|
||||||
decoration: const BoxDecoration(),
|
theme: chatTheme,
|
||||||
child: Chat(
|
messages: _messages,
|
||||||
//theme: _chatTheme,
|
//onAttachmentPressed: _handleAttachmentPressed,
|
||||||
messages: _messages,
|
//onMessageTap: _handleMessageTap,
|
||||||
//onAttachmentPressed: _handleAttachmentPressed,
|
//onPreviewDataFetched: _handlePreviewDataFetched,
|
||||||
//onMessageTap: _handleMessageTap,
|
|
||||||
//onPreviewDataFetched: _handlePreviewDataFetched,
|
onSendPressed: _handleSendPressed,
|
||||||
onSendPressed: _handleSendPressed,
|
showUserAvatars: true,
|
||||||
showUserAvatars: true,
|
showUserNames: true,
|
||||||
showUserNames: true,
|
user: _user,
|
||||||
user: _user,
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
)));
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,74 @@
|
||||||
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.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_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
|
||||||
class ContactInvitationDisplay extends ConsumerWidget {
|
import '../tools/tools.dart';
|
||||||
const ContactInvitationDisplay({super.key});
|
|
||||||
//final LocalAccount account;
|
class ContactInvitationDisplayDialog extends ConsumerStatefulWidget {
|
||||||
|
const ContactInvitationDisplayDialog({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
// EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none;
|
||||||
|
// _encryptionKey = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ContactInvitationDisplayDialogState createState() =>
|
||||||
//final logins = ref.watch(loginsProvider);
|
ContactInvitationDisplayDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
return ConstrainedBox(
|
class ContactInvitationDisplayDialogState
|
||||||
constraints: const BoxConstraints(maxHeight: 300),
|
extends ConsumerState<ContactInvitationDisplayDialog> {
|
||||||
child: Column(
|
final focusNode = FocusNode();
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
final formKey = GlobalKey<FormState>();
|
||||||
children: [const Expanded(child: Text('Contact Invitation'))]));
|
Future<void>? _generateFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (_generateFuture == null) {
|
||||||
|
_generateFuture = _generate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _generate() async {
|
||||||
|
// Generate invitation
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_generateFuture = null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void dispose() {
|
||||||
super.debugFillProperties(properties);
|
focusNode.dispose();
|
||||||
//properties.add(DiagnosticsProperty<LocalAccount>('account', account));
|
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
|
// Create new account to insert into identity
|
||||||
await (await pool.create(parent: identityRec.key))
|
await (await pool.create(parent: identityRec.key))
|
||||||
.deleteScope((accountRec) async {
|
.deleteScope((accountRec) async {
|
||||||
// Make empty contact request list
|
// Make empty contact invitation record list
|
||||||
final contactRequests = await (await DHTShortArray.create())
|
final contactInvitationRecords = await (await DHTShortArray.create())
|
||||||
.scope((r) => r.record.ownedDHTRecordPointer);
|
.scope((r) => r.record.ownedDHTRecordPointer);
|
||||||
|
|
||||||
// Make account object
|
// Make account object
|
||||||
|
@ -140,7 +140,7 @@ extension IdentityMasterExtension on IdentityMaster {
|
||||||
..profile = (proto.Profile()
|
..profile = (proto.Profile()
|
||||||
..name = name
|
..name = name
|
||||||
..title = title)
|
..title = title)
|
||||||
..contactRequests = contactRequests.toProto();
|
..contactInvitationRecords = contactInvitationRecords.toProto();
|
||||||
|
|
||||||
// Write account key
|
// Write account key
|
||||||
await accountRec.eventualWriteProtobuf(account);
|
await accountRec.eventualWriteProtobuf(account);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:typed_data';
|
||||||
import 'package:change_case/change_case.dart';
|
import 'package:change_case/change_case.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import '../entities/proto.dart' as proto;
|
||||||
import '../veilid_support/veilid_support.dart';
|
import '../veilid_support/veilid_support.dart';
|
||||||
import 'identity.dart';
|
import 'identity.dart';
|
||||||
|
|
||||||
|
@ -22,7 +23,27 @@ enum EncryptionKeyType {
|
||||||
factory EncryptionKeyType.fromJson(dynamic j) =>
|
factory EncryptionKeyType.fromJson(dynamic j) =>
|
||||||
EncryptionKeyType.values.byName((j as String).toCamelCase());
|
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();
|
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
|
// 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')
|
..aOB(2, _omitFieldNames ? '' : 'invisible')
|
||||||
..a<$core.int>(3, _omitFieldNames ? '' : 'autoAwayTimeoutSec', $pb.PbFieldType.OU3)
|
..a<$core.int>(3, _omitFieldNames ? '' : 'autoAwayTimeoutSec', $pb.PbFieldType.OU3)
|
||||||
..aOM<OwnedDHTRecordPointer>(4, _omitFieldNames ? '' : 'contactList', subBuilder: OwnedDHTRecordPointer.create)
|
..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
|
..hasRequiredFields = false
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -1306,15 +1306,15 @@ class Account extends $pb.GeneratedMessage {
|
||||||
OwnedDHTRecordPointer ensureContactList() => $_ensure(3);
|
OwnedDHTRecordPointer ensureContactList() => $_ensure(3);
|
||||||
|
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(5)
|
||||||
OwnedDHTRecordPointer get contactRequests => $_getN(4);
|
OwnedDHTRecordPointer get contactInvitationRecords => $_getN(4);
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(5)
|
||||||
set contactRequests(OwnedDHTRecordPointer v) { setField(5, v); }
|
set contactInvitationRecords(OwnedDHTRecordPointer v) { setField(5, v); }
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(5)
|
||||||
$core.bool hasContactRequests() => $_has(4);
|
$core.bool hasContactInvitationRecords() => $_has(4);
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(5)
|
||||||
void clearContactRequests() => clearField(5);
|
void clearContactInvitationRecords() => clearField(5);
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(5)
|
||||||
OwnedDHTRecordPointer ensureContactRequests() => $_ensure(4);
|
OwnedDHTRecordPointer ensureContactInvitationRecords() => $_ensure(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactInvitation extends $pb.GeneratedMessage {
|
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);
|
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)
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactRequest', createEmptyInstance: create)
|
||||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'writerSalt', $pb.PbFieldType.OY)
|
..e<EncryptionKeyType>(1, _omitFieldNames ? '' : 'encryptionKeyType', $pb.PbFieldType.OE, defaultOrMaker: EncryptionKeyType.ENCRYPTION_KEY_TYPE_UNSPECIFIED, valueOf: EncryptionKeyType.valueOf, enumValues: EncryptionKeyType.values)
|
||||||
..e<EncryptionKind>(2, _omitFieldNames ? '' : 'encryptionKeyType', $pb.PbFieldType.OE, defaultOrMaker: EncryptionKind.ENCRYPTION_KIND_UNSPECIFIED, valueOf: EncryptionKind.valueOf, enumValues: EncryptionKind.values)
|
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'private', $pb.PbFieldType.OY)
|
||||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'private', $pb.PbFieldType.OY)
|
|
||||||
..hasRequiredFields = false
|
..hasRequiredFields = false
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -1460,31 +1459,22 @@ class ContactRequest extends $pb.GeneratedMessage {
|
||||||
static ContactRequest? _defaultInstance;
|
static ContactRequest? _defaultInstance;
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$core.List<$core.int> get writerSalt => $_getN(0);
|
EncryptionKeyType get encryptionKeyType => $_getN(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
set writerSalt($core.List<$core.int> v) { $_setBytes(0, v); }
|
set encryptionKeyType(EncryptionKeyType v) { setField(1, v); }
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$core.bool hasWriterSalt() => $_has(0);
|
$core.bool hasEncryptionKeyType() => $_has(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
void clearWriterSalt() => clearField(1);
|
void clearEncryptionKeyType() => clearField(1);
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
EncryptionKind get encryptionKeyType => $_getN(1);
|
$core.List<$core.int> get private => $_getN(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set encryptionKeyType(EncryptionKind v) { setField(2, v); }
|
set private($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasEncryptionKeyType() => $_has(1);
|
$core.bool hasPrivate() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearEncryptionKeyType() => clearField(2);
|
void clearPrivate() => 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactRequestPrivate extends $pb.GeneratedMessage {
|
class ContactRequestPrivate extends $pb.GeneratedMessage {
|
||||||
|
@ -1697,13 +1687,13 @@ class SignedContactResponse extends $pb.GeneratedMessage {
|
||||||
Signature ensureIdentitySignature() => $_ensure(1);
|
Signature ensureIdentitySignature() => $_ensure(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactRequestRecord extends $pb.GeneratedMessage {
|
class ContactInvitationRecord extends $pb.GeneratedMessage {
|
||||||
factory ContactRequestRecord() => create();
|
factory ContactInvitationRecord() => create();
|
||||||
ContactRequestRecord._() : super();
|
ContactInvitationRecord._() : super();
|
||||||
factory ContactRequestRecord.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
factory ContactInvitationRecord.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);
|
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<TypedKey>(1, _omitFieldNames ? '' : 'contactRequestRecordKey', subBuilder: TypedKey.create)
|
||||||
..aOM<CryptoKey>(2, _omitFieldNames ? '' : 'writerKey', subBuilder: CryptoKey.create)
|
..aOM<CryptoKey>(2, _omitFieldNames ? '' : 'writerKey', subBuilder: CryptoKey.create)
|
||||||
..aOM<CryptoKey>(3, _omitFieldNames ? '' : 'writerSecret', 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. '
|
'Using this can add significant overhead to your binary. '
|
||||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||||
'Will be removed in next major version')
|
'Will be removed in next major version')
|
||||||
ContactRequestRecord clone() => ContactRequestRecord()..mergeFromMessage(this);
|
ContactInvitationRecord clone() => ContactInvitationRecord()..mergeFromMessage(this);
|
||||||
@$core.Deprecated(
|
@$core.Deprecated(
|
||||||
'Using this can add significant overhead to your binary. '
|
'Using this can add significant overhead to your binary. '
|
||||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||||
'Will be removed in next major version')
|
'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;
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
@$core.pragma('dart2js:noInline')
|
||||||
static ContactRequestRecord create() => ContactRequestRecord._();
|
static ContactInvitationRecord create() => ContactInvitationRecord._();
|
||||||
ContactRequestRecord createEmptyInstance() => create();
|
ContactInvitationRecord createEmptyInstance() => create();
|
||||||
static $pb.PbList<ContactRequestRecord> createRepeated() => $pb.PbList<ContactRequestRecord>();
|
static $pb.PbList<ContactInvitationRecord> createRepeated() => $pb.PbList<ContactInvitationRecord>();
|
||||||
@$core.pragma('dart2js:noInline')
|
@$core.pragma('dart2js:noInline')
|
||||||
static ContactRequestRecord getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ContactRequestRecord>(create);
|
static ContactInvitationRecord getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ContactInvitationRecord>(create);
|
||||||
static ContactRequestRecord? _defaultInstance;
|
static ContactInvitationRecord? _defaultInstance;
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
TypedKey get contactRequestRecordKey => $_getN(0);
|
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);
|
const Availability._($core.int v, $core.String n) : super(v, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
class EncryptionKind extends $pb.ProtobufEnum {
|
class EncryptionKeyType extends $pb.ProtobufEnum {
|
||||||
static const EncryptionKind ENCRYPTION_KIND_UNSPECIFIED = EncryptionKind._(0, _omitEnumNames ? '' : 'ENCRYPTION_KIND_UNSPECIFIED');
|
static const EncryptionKeyType ENCRYPTION_KEY_TYPE_UNSPECIFIED = EncryptionKeyType._(0, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_UNSPECIFIED');
|
||||||
static const EncryptionKind ENCRYPTION_KIND_PIN = EncryptionKind._(1, _omitEnumNames ? '' : 'ENCRYPTION_KIND_PIN');
|
static const EncryptionKeyType ENCRYPTION_KEY_TYPE_NONE = EncryptionKeyType._(1, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_NONE');
|
||||||
static const EncryptionKind ENCRYPTION_KIND_PASSWORD = EncryptionKind._(2, _omitEnumNames ? '' : 'ENCRYPTION_KIND_PASSWORD');
|
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> [
|
static const $core.List<EncryptionKeyType> values = <EncryptionKeyType> [
|
||||||
ENCRYPTION_KIND_UNSPECIFIED,
|
ENCRYPTION_KEY_TYPE_UNSPECIFIED,
|
||||||
ENCRYPTION_KIND_PIN,
|
ENCRYPTION_KEY_TYPE_NONE,
|
||||||
ENCRYPTION_KIND_PASSWORD,
|
ENCRYPTION_KEY_TYPE_PIN,
|
||||||
|
ENCRYPTION_KEY_TYPE_PASSWORD,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final $core.Map<$core.int, EncryptionKind> _byValue = $pb.ProtobufEnum.initByValue(values);
|
static final $core.Map<$core.int, EncryptionKeyType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||||
static EncryptionKind? valueOf($core.int value) => _byValue[value];
|
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'
|
'lMSVRZX09GRkxJTkUQARIVChFBVkFJTEFCSUxJVFlfRlJFRRACEhUKEUFWQUlMQUJJTElUWV9C'
|
||||||
'VVNZEAMSFQoRQVZBSUxBQklMSVRZX0FXQVkQBA==');
|
'VVNZEAMSFQoRQVZBSUxBQklMSVRZX0FXQVkQBA==');
|
||||||
|
|
||||||
@$core.Deprecated('Use encryptionKindDescriptor instead')
|
@$core.Deprecated('Use encryptionKeyTypeDescriptor instead')
|
||||||
const EncryptionKind$json = {
|
const EncryptionKeyType$json = {
|
||||||
'1': 'EncryptionKind',
|
'1': 'EncryptionKeyType',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'ENCRYPTION_KIND_UNSPECIFIED', '2': 0},
|
{'1': 'ENCRYPTION_KEY_TYPE_UNSPECIFIED', '2': 0},
|
||||||
{'1': 'ENCRYPTION_KIND_PIN', '2': 1},
|
{'1': 'ENCRYPTION_KEY_TYPE_NONE', '2': 1},
|
||||||
{'1': 'ENCRYPTION_KIND_PASSWORD', '2': 2},
|
{'1': 'ENCRYPTION_KEY_TYPE_PIN', '2': 2},
|
||||||
|
{'1': 'ENCRYPTION_KEY_TYPE_PASSWORD', '2': 3},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `EncryptionKind`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
/// Descriptor for `EncryptionKeyType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
final $typed_data.Uint8List encryptionKindDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List encryptionKeyTypeDescriptor = $convert.base64Decode(
|
||||||
'Cg5FbmNyeXB0aW9uS2luZBIfChtFTkNSWVBUSU9OX0tJTkRfVU5TUEVDSUZJRUQQABIXChNFTk'
|
'ChFFbmNyeXB0aW9uS2V5VHlwZRIjCh9FTkNSWVBUSU9OX0tFWV9UWVBFX1VOU1BFQ0lGSUVEEA'
|
||||||
'NSWVBUSU9OX0tJTkRfUElOEAESHAoYRU5DUllQVElPTl9LSU5EX1BBU1NXT1JEEAI=');
|
'ASHAoYRU5DUllQVElPTl9LRVlfVFlQRV9OT05FEAESGwoXRU5DUllQVElPTl9LRVlfVFlQRV9Q'
|
||||||
|
'SU4QAhIgChxFTkNSWVBUSU9OX0tFWV9UWVBFX1BBU1NXT1JEEAM=');
|
||||||
|
|
||||||
@$core.Deprecated('Use cryptoKeyDescriptor instead')
|
@$core.Deprecated('Use cryptoKeyDescriptor instead')
|
||||||
const CryptoKey$json = {
|
const CryptoKey$json = {
|
||||||
|
@ -344,7 +346,7 @@ const Account$json = {
|
||||||
{'1': 'invisible', '3': 2, '4': 1, '5': 8, '10': 'invisible'},
|
{'1': 'invisible', '3': 2, '4': 1, '5': 8, '10': 'invisible'},
|
||||||
{'1': 'auto_away_timeout_sec', '3': 3, '4': 1, '5': 13, '10': 'autoAwayTimeoutSec'},
|
{'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_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'
|
'CgdBY2NvdW50EiIKB3Byb2ZpbGUYASABKAsyCC5Qcm9maWxlUgdwcm9maWxlEhwKCWludmlzaW'
|
||||||
'JsZRgCIAEoCFIJaW52aXNpYmxlEjEKFWF1dG9fYXdheV90aW1lb3V0X3NlYxgDIAEoDVISYXV0'
|
'JsZRgCIAEoCFIJaW52aXNpYmxlEjEKFWF1dG9fYXdheV90aW1lb3V0X3NlYxgDIAEoDVISYXV0'
|
||||||
'b0F3YXlUaW1lb3V0U2VjEjkKDGNvbnRhY3RfbGlzdBgEIAEoCzIWLk93bmVkREhUUmVjb3JkUG'
|
'b0F3YXlUaW1lb3V0U2VjEjkKDGNvbnRhY3RfbGlzdBgEIAEoCzIWLk93bmVkREhUUmVjb3JkUG'
|
||||||
'9pbnRlclILY29udGFjdExpc3QSQQoQY29udGFjdF9yZXF1ZXN0cxgFIAEoCzIWLk93bmVkREhU'
|
'9pbnRlclILY29udGFjdExpc3QSVAoaY29udGFjdF9pbnZpdGF0aW9uX3JlY29yZHMYBSABKAsy'
|
||||||
'UmVjb3JkUG9pbnRlclIPY29udGFjdFJlcXVlc3Rz');
|
'Fi5Pd25lZERIVFJlY29yZFBvaW50ZXJSGGNvbnRhY3RJbnZpdGF0aW9uUmVjb3Jkcw==');
|
||||||
|
|
||||||
@$core.Deprecated('Use contactInvitationDescriptor instead')
|
@$core.Deprecated('Use contactInvitationDescriptor instead')
|
||||||
const ContactInvitation$json = {
|
const ContactInvitation$json = {
|
||||||
|
@ -390,17 +392,15 @@ final $typed_data.Uint8List signedContactInvitationDescriptor = $convert.base64D
|
||||||
const ContactRequest$json = {
|
const ContactRequest$json = {
|
||||||
'1': 'ContactRequest',
|
'1': 'ContactRequest',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'writer_salt', '3': 1, '4': 1, '5': 12, '10': 'writerSalt'},
|
{'1': 'encryption_key_type', '3': 1, '4': 1, '5': 14, '6': '.EncryptionKeyType', '10': 'encryptionKeyType'},
|
||||||
{'1': 'encryption_key_type', '3': 2, '4': 1, '5': 14, '6': '.EncryptionKind', '10': 'encryptionKeyType'},
|
{'1': 'private', '3': 2, '4': 1, '5': 12, '10': 'private'},
|
||||||
{'1': 'private', '3': 3, '4': 1, '5': 12, '10': 'private'},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `ContactRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `ContactRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List contactRequestDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List contactRequestDescriptor = $convert.base64Decode(
|
||||||
'Cg5Db250YWN0UmVxdWVzdBIfCgt3cml0ZXJfc2FsdBgBIAEoDFIKd3JpdGVyU2FsdBI/ChNlbm'
|
'Cg5Db250YWN0UmVxdWVzdBJCChNlbmNyeXB0aW9uX2tleV90eXBlGAEgASgOMhIuRW5jcnlwdG'
|
||||||
'NyeXB0aW9uX2tleV90eXBlGAIgASgOMg8uRW5jcnlwdGlvbktpbmRSEWVuY3J5cHRpb25LZXlU'
|
'lvbktleVR5cGVSEWVuY3J5cHRpb25LZXlUeXBlEhgKB3ByaXZhdGUYAiABKAxSB3ByaXZhdGU=');
|
||||||
'eXBlEhgKB3ByaXZhdGUYAyABKAxSB3ByaXZhdGU=');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use contactRequestPrivateDescriptor instead')
|
@$core.Deprecated('Use contactRequestPrivateDescriptor instead')
|
||||||
const ContactRequestPrivate$json = {
|
const ContactRequestPrivate$json = {
|
||||||
|
@ -453,9 +453,9 @@ final $typed_data.Uint8List signedContactResponseDescriptor = $convert.base64Dec
|
||||||
'FjdFJlc3BvbnNlEjkKEmlkZW50aXR5X3NpZ25hdHVyZRgCIAEoCzIKLlNpZ25hdHVyZVIRaWRl'
|
'FjdFJlc3BvbnNlEjkKEmlkZW50aXR5X3NpZ25hdHVyZRgCIAEoCzIKLlNpZ25hdHVyZVIRaWRl'
|
||||||
'bnRpdHlTaWduYXR1cmU=');
|
'bnRpdHlTaWduYXR1cmU=');
|
||||||
|
|
||||||
@$core.Deprecated('Use contactRequestRecordDescriptor instead')
|
@$core.Deprecated('Use contactInvitationRecordDescriptor instead')
|
||||||
const ContactRequestRecord$json = {
|
const ContactInvitationRecord$json = {
|
||||||
'1': 'ContactRequestRecord',
|
'1': 'ContactInvitationRecord',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'contact_request_record_key', '3': 1, '4': 1, '5': 11, '6': '.TypedKey', '10': 'contactRequestRecordKey'},
|
{'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'},
|
{'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`.
|
/// Descriptor for `ContactInvitationRecord`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List contactRequestRecordDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List contactInvitationRecordDescriptor = $convert.base64Decode(
|
||||||
'ChRDb250YWN0UmVxdWVzdFJlY29yZBJGChpjb250YWN0X3JlcXVlc3RfcmVjb3JkX2tleRgBIA'
|
'ChdDb250YWN0SW52aXRhdGlvblJlY29yZBJGChpjb250YWN0X3JlcXVlc3RfcmVjb3JkX2tleR'
|
||||||
'EoCzIJLlR5cGVkS2V5Uhdjb250YWN0UmVxdWVzdFJlY29yZEtleRIpCgp3cml0ZXJfa2V5GAIg'
|
'gBIAEoCzIJLlR5cGVkS2V5Uhdjb250YWN0UmVxdWVzdFJlY29yZEtleRIpCgp3cml0ZXJfa2V5'
|
||||||
'ASgLMgouQ3J5cHRvS2V5Ugl3cml0ZXJLZXkSLwoNd3JpdGVyX3NlY3JldBgDIAEoCzIKLkNyeX'
|
'GAIgASgLMgouQ3J5cHRvS2V5Ugl3cml0ZXJLZXkSLwoNd3JpdGVyX3NlY3JldBgDIAEoCzIKLk'
|
||||||
'B0b0tleVIMd3JpdGVyU2VjcmV0EjEKD2NoYXRfcmVjb3JkX2tleRgEIAEoCzIJLlR5cGVkS2V5'
|
'NyeXB0b0tleVIMd3JpdGVyU2VjcmV0EjEKD2NoYXRfcmVjb3JkX2tleRgEIAEoCzIJLlR5cGVk'
|
||||||
'Ug1jaGF0UmVjb3JkS2V5Eh4KCmV4cGlyYXRpb24YBSABKARSCmV4cGlyYXRpb24SHgoKaW52aX'
|
'S2V5Ug1jaGF0UmVjb3JkS2V5Eh4KCmV4cGlyYXRpb24YBSABKARSCmV4cGlyYXRpb24SHgoKaW'
|
||||||
'RhdGlvbhgGIAEoDFIKaW52aXRhdGlvbg==');
|
'52aXRhdGlvbhgGIAEoDFIKaW52aXRhdGlvbg==');
|
||||||
|
|
||||||
|
|
|
@ -258,17 +258,18 @@ message Account {
|
||||||
// The contacts DHTList for this account
|
// The contacts DHTList for this account
|
||||||
// DHT Private
|
// DHT Private
|
||||||
OwnedDHTRecordPointer contact_list = 4;
|
OwnedDHTRecordPointer contact_list = 4;
|
||||||
// The contact requests DHTShortArray for this account
|
// The ContactInvitationRecord DHTShortArray for this account
|
||||||
// DHT Private
|
// DHT Private
|
||||||
OwnedDHTRecordPointer contact_requests = 5;
|
OwnedDHTRecordPointer contact_invitation_records = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptionKind
|
// EncryptionKeyType
|
||||||
// Encryption of secret
|
// Encryption of secret
|
||||||
enum EncryptionKind {
|
enum EncryptionKeyType {
|
||||||
ENCRYPTION_KIND_UNSPECIFIED = 0;
|
ENCRYPTION_KEY_TYPE_UNSPECIFIED = 0;
|
||||||
ENCRYPTION_KIND_PIN = 1;
|
ENCRYPTION_KEY_TYPE_NONE = 1;
|
||||||
ENCRYPTION_KIND_PASSWORD =2;
|
ENCRYPTION_KEY_TYPE_PIN = 2;
|
||||||
|
ENCRYPTION_KEY_TYPE_PASSWORD = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invitation that is shared for VeilidChat contact connections
|
// Invitation that is shared for VeilidChat contact connections
|
||||||
|
@ -277,7 +278,7 @@ enum EncryptionKind {
|
||||||
message ContactInvitation {
|
message ContactInvitation {
|
||||||
// Contact request DHT record key
|
// Contact request DHT record key
|
||||||
TypedKey contact_request_record_key = 1;
|
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;
|
bytes writer_secret = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,14 +291,12 @@ message SignedContactInvitation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contact request unicastinbox on the DHT
|
// 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 {
|
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
|
// 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
|
// 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
|
// 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
|
// Contact request record kept in Account DHTList to keep track of extant contact invitations
|
||||||
message ContactRequestRecord {
|
message ContactInvitationRecord {
|
||||||
// Contact request unicastinbox DHT record key
|
// Contact request unicastinbox DHT record key
|
||||||
TypedKey contact_request_record_key = 1;
|
TypedKey contact_request_record_key = 1;
|
||||||
// Unencrypted writer key for this request
|
// Unencrypted writer key for this request
|
||||||
|
@ -345,6 +344,6 @@ message ContactRequestRecord {
|
||||||
TypedKey chat_record_key = 4;
|
TypedKey chat_record_key = 4;
|
||||||
// Expiration timestamp
|
// Expiration timestamp
|
||||||
uint64 expiration = 5;
|
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;
|
bytes invitation = 6;
|
||||||
}
|
}
|
|
@ -5,6 +5,8 @@ import 'package:split_view/split_view.dart';
|
||||||
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
|
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
|
||||||
|
|
||||||
import '../components/chat_component.dart';
|
import '../components/chat_component.dart';
|
||||||
|
import '../providers/local_accounts.dart';
|
||||||
|
import '../providers/logins.dart';
|
||||||
import '../providers/window_control.dart';
|
import '../providers/window_control.dart';
|
||||||
import '../tools/tools.dart';
|
import '../tools/tools.dart';
|
||||||
import 'main_pager/main_pager.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/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
||||||
import 'package:stylish_bottom_bar/stylish_bottom_bar.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/contact_invitation_display.dart';
|
||||||
|
import '../../components/send_invite_dialog.dart';
|
||||||
|
import '../../tools/tools.dart';
|
||||||
import 'account_page.dart';
|
import 'account_page.dart';
|
||||||
import 'chats_page.dart';
|
import 'chats_page.dart';
|
||||||
|
|
||||||
|
@ -102,22 +109,81 @@ class MainPagerState extends ConsumerState<MainPager>
|
||||||
return bottomBarItems;
|
return bottomBarItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onNewContactInvitation(BuildContext context) async {
|
Future<void> sendContactInvitationDialog(BuildContext context) async {
|
||||||
Scaffold.of(context).showBottomSheet<void>((context) => SizedBox(
|
await showDialog<void>(
|
||||||
height: 200, child: Center(child: ContactInvitationDisplay())));
|
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) {
|
if (_currentPage == 0) {
|
||||||
// New contact invitation
|
// New contact invitation
|
||||||
return _onNewContactInvitation(context);
|
return _newContactInvitationBottomSheetBuilder(context);
|
||||||
} else if (_currentPage == 1) {
|
} else if (_currentPage == 1) {
|
||||||
// New chat
|
// 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.primary,
|
||||||
// theme.colorScheme.primaryContainer,
|
// theme.colorScheme.primaryContainer,
|
||||||
// ]),
|
// ]),
|
||||||
|
//borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
option: AnimatedBarOptions(
|
option: AnimatedBarOptions(
|
||||||
// iconSize: 32,
|
// iconSize: 32,
|
||||||
//barAnimation: BarAnimation.fade,
|
//barAnimation: BarAnimation.fade,
|
||||||
|
@ -172,16 +239,16 @@ class MainPagerState extends ConsumerState<MainPager>
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: BottomSheetActionButton(
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(14))),
|
borderRadius: BorderRadius.all(Radius.circular(14))),
|
||||||
//foregroundColor: theme.colorScheme.secondary,
|
//foregroundColor: theme.colorScheme.secondary,
|
||||||
backgroundColor: theme.colorScheme.secondaryContainer,
|
backgroundColor: theme.colorScheme.secondaryContainer,
|
||||||
child: Icon(
|
builder: (context) => Icon(
|
||||||
_fabIconList[_currentPage],
|
_fabIconList[_currentPage],
|
||||||
color: theme.colorScheme.onSecondaryContainer,
|
color: theme.colorScheme.onSecondaryContainer,
|
||||||
),
|
),
|
||||||
onPressed: () async => _onFloatingActionButtonPressed(context)),
|
bottomSheetBuilder: _bottomSheetBuilder),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
import '../entities/local_account.dart';
|
||||||
import '../entities/proto.dart' as proto;
|
import '../entities/proto.dart' as proto;
|
||||||
|
import '../entities/user_login.dart';
|
||||||
import '../veilid_support/veilid_support.dart';
|
import '../veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import 'local_accounts.dart';
|
import 'local_accounts.dart';
|
||||||
|
@ -70,3 +72,61 @@ Future<AccountInfo> fetchAccount(FetchAccountRef ref,
|
||||||
return AccountInfo(
|
return AccountInfo(
|
||||||
status: AccountInfoStatus.accountReady, active: active, account: account);
|
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);
|
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
|
// 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);
|
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
|
/// Creates a new Account associated with master identity
|
||||||
/// Adds a logged-out LocalAccount to track its existence on this device
|
/// Adds a logged-out LocalAccount to track its existence on this device
|
||||||
Future<LocalAccount> newLocalAccount(
|
Future<LocalAccount> newLocalAccount(
|
||||||
|
@ -97,8 +70,8 @@ class LocalAccounts extends _$LocalAccounts
|
||||||
);
|
);
|
||||||
|
|
||||||
// Encrypt identitySecret with key
|
// Encrypt identitySecret with key
|
||||||
final identitySecretBytes = await _encryptIdentitySecret(
|
final identitySecretBytes = await encryptSecretToBytes(
|
||||||
identitySecret: identitySecret,
|
secret: identitySecret,
|
||||||
cryptoKind: identityMaster.identityRecordKey.kind,
|
cryptoKind: identityMaster.identityRecordKey.kind,
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
encryptionKeyType: encryptionKeyType);
|
encryptionKeyType: encryptionKeyType);
|
||||||
|
|
|
@ -112,7 +112,7 @@ class FetchLocalAccountProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$localAccountsHash() => r'a9a1e1765188556858ec982c9e99f780756ade1e';
|
String _$localAccountsHash() => r'80485dab3a2d1024fb5ffe29d9272dc4f3db2dff';
|
||||||
|
|
||||||
/// See also [LocalAccounts].
|
/// See also [LocalAccounts].
|
||||||
@ProviderFor(LocalAccounts)
|
@ProviderFor(LocalAccounts)
|
||||||
|
|
|
@ -121,18 +121,10 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
||||||
}
|
}
|
||||||
final cs = await veilid
|
final cs = await veilid
|
||||||
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
|
.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(
|
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
|
// Validate this secret with the identity public key and log in
|
||||||
return _loginCommon(localAccount.identityMaster, identitySecret);
|
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].
|
/// See also [Logins].
|
||||||
@ProviderFor(Logins)
|
@ProviderFor(Logins)
|
||||||
|
|
|
@ -472,50 +472,48 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
|
||||||
return radixScheme;
|
return radixScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtendedColorScheme _radixColorScheme(
|
ColorScheme _radixColorScheme(Brightness brightness, RadixScheme radix) =>
|
||||||
Brightness brightness, RadixThemeColor themeColor) {
|
ColorScheme(
|
||||||
final radix = _radixScheme(brightness, themeColor);
|
brightness: brightness,
|
||||||
|
primary: radix.primaryScale.step9,
|
||||||
return ExtendedColorScheme(
|
onPrimary: radix.primaryScale.step12,
|
||||||
scaleScheme: radix.toScale(),
|
primaryContainer: radix.primaryScale.step4,
|
||||||
brightness: brightness,
|
onPrimaryContainer: radix.primaryScale.step11,
|
||||||
primary: radix.primaryScale.step9,
|
secondary: radix.secondaryScale.step9,
|
||||||
onPrimary: radix.primaryScale.step12,
|
onSecondary: radix.secondaryScale.step12,
|
||||||
primaryContainer: radix.primaryScale.step4,
|
secondaryContainer: radix.secondaryScale.step3,
|
||||||
onPrimaryContainer: radix.primaryScale.step11,
|
onSecondaryContainer: radix.secondaryScale.step11,
|
||||||
secondary: radix.secondaryScale.step9,
|
tertiary: radix.tertiaryScale.step9,
|
||||||
onSecondary: radix.secondaryScale.step12,
|
onTertiary: radix.tertiaryScale.step12,
|
||||||
secondaryContainer: radix.secondaryScale.step3,
|
tertiaryContainer: radix.tertiaryScale.step3,
|
||||||
onSecondaryContainer: radix.secondaryScale.step11,
|
onTertiaryContainer: radix.tertiaryScale.step11,
|
||||||
tertiary: radix.tertiaryScale.step9,
|
error: radix.errorScale.step9,
|
||||||
onTertiary: radix.tertiaryScale.step12,
|
onError: radix.errorScale.step12,
|
||||||
tertiaryContainer: radix.tertiaryScale.step3,
|
errorContainer: radix.errorScale.step3,
|
||||||
onTertiaryContainer: radix.tertiaryScale.step11,
|
onErrorContainer: radix.errorScale.step11,
|
||||||
error: radix.errorScale.step9,
|
background: radix.grayScale.step1,
|
||||||
onError: radix.errorScale.step12,
|
onBackground: radix.grayScale.step11,
|
||||||
errorContainer: radix.errorScale.step3,
|
surface: radix.primaryScale.step1,
|
||||||
onErrorContainer: radix.errorScale.step11,
|
onSurface: radix.primaryScale.step12,
|
||||||
background: radix.grayScale.step1,
|
surfaceVariant: radix.secondaryScale.step2,
|
||||||
onBackground: radix.grayScale.step11,
|
onSurfaceVariant: radix.secondaryScale.step11,
|
||||||
surface: radix.primaryScale.step1,
|
outline: radix.primaryScale.step7,
|
||||||
onSurface: radix.primaryScale.step12,
|
outlineVariant: radix.primaryScale.step6,
|
||||||
surfaceVariant: radix.secondaryScale.step2,
|
shadow: RadixColors.dark.gray.step1,
|
||||||
onSurfaceVariant: radix.secondaryScale.step11,
|
scrim: radix.primaryScale.step9,
|
||||||
outline: radix.primaryScale.step7,
|
inverseSurface: radix.primaryScale.step11,
|
||||||
outlineVariant: radix.primaryScale.step6,
|
onInverseSurface: radix.primaryScale.step2,
|
||||||
shadow: RadixColors.dark.gray.step1,
|
inversePrimary: radix.primaryScale.step10,
|
||||||
scrim: radix.primaryScale.step9,
|
surfaceTint: radix.primaryAlphaScale.step4,
|
||||||
inverseSurface: radix.primaryScale.step11,
|
);
|
||||||
onInverseSurface: radix.primaryScale.step2,
|
|
||||||
inversePrimary: radix.primaryScale.step10,
|
|
||||||
surfaceTint: radix.primaryAlphaScale.step4,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
|
ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
|
||||||
TextTheme? textTheme;
|
TextTheme? textTheme;
|
||||||
|
final radix = _radixScheme(brightness, themeColor);
|
||||||
|
final colorScheme = _radixColorScheme(brightness, radix);
|
||||||
return ThemeData.from(
|
return ThemeData.from(
|
||||||
colorScheme: _radixColorScheme(brightness, themeColor),
|
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true)
|
||||||
textTheme: textTheme,
|
.copyWith(extensions: <ThemeExtension<dynamic>>[
|
||||||
useMaterial3: true);
|
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 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../entities/preferences.dart';
|
import '../entities/preferences.dart';
|
||||||
import 'radix_generator.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 {
|
class ScaleColor {
|
||||||
ScaleColor({
|
ScaleColor({
|
||||||
required this.appBackground,
|
required this.appBackground,
|
||||||
|
@ -83,9 +37,69 @@ class ScaleColor {
|
||||||
Color hoverBackground;
|
Color hoverBackground;
|
||||||
Color subtleText;
|
Color subtleText;
|
||||||
Color text;
|
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(
|
ScaleScheme(
|
||||||
{required this.primaryScale,
|
{required this.primaryScale,
|
||||||
required this.primaryAlphaScale,
|
required this.primaryAlphaScale,
|
||||||
|
@ -94,15 +108,66 @@ class ScaleScheme {
|
||||||
required this.grayScale,
|
required this.grayScale,
|
||||||
required this.errorScale});
|
required this.errorScale});
|
||||||
|
|
||||||
ScaleColor primaryScale;
|
final ScaleColor primaryScale;
|
||||||
ScaleColor primaryAlphaScale;
|
final ScaleColor primaryAlphaScale;
|
||||||
ScaleColor secondaryScale;
|
final ScaleColor secondaryScale;
|
||||||
ScaleColor tertiaryScale;
|
final ScaleColor tertiaryScale;
|
||||||
ScaleColor grayScale;
|
final ScaleColor grayScale;
|
||||||
ScaleColor errorScale;
|
final ScaleColor errorScale;
|
||||||
|
|
||||||
static ScaleScheme of(BuildContext context) =>
|
@override
|
||||||
(Theme.of(context).colorScheme as ExtendedColorScheme).scaleScheme;
|
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 'protobuf_tools.dart';
|
||||||
export 'radix_generator.dart';
|
export 'radix_generator.dart';
|
||||||
export 'responsive.dart';
|
export 'responsive.dart';
|
||||||
|
export 'secret_crypto.dart';
|
||||||
export 'theme_service.dart';
|
export 'theme_service.dart';
|
||||||
export 'widget_helpers.dart';
|
export 'widget_helpers.dart';
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
|
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||||
|
import 'package:motion_toast/motion_toast.dart';
|
||||||
import 'package:quickalert/quickalert.dart';
|
import 'package:quickalert/quickalert.dart';
|
||||||
|
|
||||||
|
import 'theme_service.dart';
|
||||||
|
|
||||||
extension BorderExt on Widget {
|
extension BorderExt on Widget {
|
||||||
DecoratedBox debugBorder() => DecoratedBox(
|
DecoratedBox debugBorder() => DecoratedBox(
|
||||||
decoration: BoxDecoration(border: Border.all(color: Colors.redAccent)),
|
decoration: BoxDecoration(border: Border.all(color: Colors.redAccent)),
|
||||||
|
@ -10,19 +13,27 @@ extension BorderExt on Widget {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ModalProgressExt on Widget {
|
extension ModalProgressExt on Widget {
|
||||||
BlurryModalProgressHUD withModalHUD(BuildContext context, bool isLoading) =>
|
BlurryModalProgressHUD withModalHUD(BuildContext context, bool isLoading) {
|
||||||
BlurryModalProgressHUD(
|
final theme = Theme.of(context);
|
||||||
inAsyncCall: isLoading,
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
blurEffectIntensity: 4,
|
|
||||||
progressIndicator: buildProgressIndicator(context),
|
return BlurryModalProgressHUD(
|
||||||
color: Theme.of(context).shadowColor,
|
inAsyncCall: isLoading,
|
||||||
child: this);
|
blurEffectIntensity: 4,
|
||||||
|
progressIndicator: buildProgressIndicator(context),
|
||||||
|
color: scale.tertiaryScale.appBackground.withAlpha(64),
|
||||||
|
child: this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildProgressIndicator(BuildContext context) => SpinKitFoldingCube(
|
Widget buildProgressIndicator(BuildContext context) {
|
||||||
color: Theme.of(context).highlightColor,
|
final theme = Theme.of(context);
|
||||||
size: 90,
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
);
|
return SpinKitFoldingCube(
|
||||||
|
color: scale.tertiaryScale.background,
|
||||||
|
size: 80,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget waitingPage(BuildContext context) => ColoredBox(
|
Widget waitingPage(BuildContext context) => ColoredBox(
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
@ -40,3 +51,10 @@ Future<void> showErrorModal(
|
||||||
//textColor: Colors.white,
|
//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,
|
_recordDescriptor = recordDescriptor,
|
||||||
_defaultSubkey = defaultSubkey,
|
_defaultSubkey = defaultSubkey,
|
||||||
_writer = writer,
|
_writer = writer,
|
||||||
_open = false,
|
_open = true,
|
||||||
_valid = true,
|
_valid = true,
|
||||||
_subkeySeqCache = {};
|
_subkeySeqCache = {};
|
||||||
final VeilidRoutingContext _routingContext;
|
final VeilidRoutingContext _routingContext;
|
||||||
|
|
|
@ -36,28 +36,12 @@ class DHTRecordCryptoPrivate implements DHTRecordCrypto {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) async {
|
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) =>
|
||||||
// generate nonce
|
_cryptoSystem.encryptNoAuthWithNonce(data, _secretKey);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) async {
|
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) =>
|
||||||
// split off nonce from end
|
_cryptoSystem.decryptNoAuthWithNonce(data, _secretKey);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////
|
////////////////////////////////////
|
||||||
|
|
|
@ -10,8 +10,10 @@ part 'dht_record_pool.g.dart';
|
||||||
@freezed
|
@freezed
|
||||||
class DHTRecordPoolAllocations with _$DHTRecordPoolAllocations {
|
class DHTRecordPoolAllocations with _$DHTRecordPoolAllocations {
|
||||||
const factory DHTRecordPoolAllocations({
|
const factory DHTRecordPoolAllocations({
|
||||||
required IMap<TypedKey, ISet<TypedKey>> childrenByParent,
|
required IMap<String, ISet<TypedKey>>
|
||||||
required IMap<TypedKey, TypedKey> parentByChild,
|
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;
|
}) = _DHTRecordPoolAllocations;
|
||||||
|
|
||||||
factory DHTRecordPoolAllocations.fromJson(dynamic json) =>
|
factory DHTRecordPoolAllocations.fromJson(dynamic json) =>
|
||||||
|
@ -100,13 +102,14 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||||
final nextDep = currentDeps.removeLast();
|
final nextDep = currentDeps.removeLast();
|
||||||
|
|
||||||
// Remove this child from its parent
|
// Remove this child from its parent
|
||||||
_removeDependency(nextDep);
|
await _removeDependency(nextDep);
|
||||||
|
|
||||||
// Ensure all records are closed before delete
|
// Ensure all records are closed before delete
|
||||||
assert(!_opened.containsKey(nextDep), 'should not delete opened record');
|
assert(!_opened.containsKey(nextDep), 'should not delete opened record');
|
||||||
|
|
||||||
allDeps.add(nextDep);
|
allDeps.add(nextDep);
|
||||||
final childDeps = _state.childrenByParent[nextDep]?.toList() ?? [];
|
final childDeps =
|
||||||
|
_state.childrenByParent[nextDep.toJson()]?.toList() ?? [];
|
||||||
currentDeps.addAll(childDeps);
|
currentDeps.addAll(childDeps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,41 +121,45 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||||
await Future.wait(allFutures);
|
await Future.wait(allFutures);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addDependency(TypedKey parent, TypedKey child) {
|
Future<void> _addDependency(TypedKey parent, TypedKey child) async {
|
||||||
final childrenOfParent =
|
final childrenOfParent =
|
||||||
_state.childrenByParent[parent] ?? ISet<TypedKey>();
|
_state.childrenByParent[parent.toJson()] ?? ISet<TypedKey>();
|
||||||
if (childrenOfParent.contains(child)) {
|
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');
|
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
|
// dependencies should be opened after their parents
|
||||||
throw StateError('Child is not a leaf: $child');
|
throw StateError('Child is not a leaf: $child');
|
||||||
}
|
}
|
||||||
|
|
||||||
_state = _state.copyWith(
|
_state = await store(_state.copyWith(
|
||||||
childrenByParent:
|
childrenByParent: _state.childrenByParent
|
||||||
_state.childrenByParent.add(parent, childrenOfParent.add(child)),
|
.add(parent.toJson(), childrenOfParent.add(child)),
|
||||||
parentByChild: _state.parentByChild.add(child, parent));
|
parentByChild: _state.parentByChild.add(child.toJson(), parent)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeDependency(TypedKey child) {
|
Future<void> _removeDependency(TypedKey child) async {
|
||||||
final parent = _state.parentByChild[child];
|
final parent = _state.parentByChild[child.toJson()];
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final children = _state.childrenByParent[parent]!.remove(child);
|
final children = _state.childrenByParent[parent.toJson()]!.remove(child);
|
||||||
|
late final DHTRecordPoolAllocations newState;
|
||||||
if (children.isEmpty) {
|
if (children.isEmpty) {
|
||||||
_state = _state.copyWith(
|
newState = _state.copyWith(
|
||||||
childrenByParent: _state.childrenByParent.remove(parent),
|
childrenByParent: _state.childrenByParent.remove(parent.toJson()),
|
||||||
parentByChild: _state.parentByChild.remove(child));
|
parentByChild: _state.parentByChild.remove(child.toJson()));
|
||||||
} else {
|
} else {
|
||||||
_state = _state.copyWith(
|
newState = _state.copyWith(
|
||||||
childrenByParent: _state.childrenByParent.add(parent, children),
|
childrenByParent:
|
||||||
parentByChild: _state.parentByChild.remove(child));
|
_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()!));
|
recordDescriptor.ownerTypedKeyPair()!));
|
||||||
|
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
_addDependency(parent, rec.key);
|
await _addDependency(parent, rec.key);
|
||||||
}
|
}
|
||||||
_recordOpened(rec);
|
_recordOpened(rec);
|
||||||
|
|
||||||
|
@ -192,7 +199,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||||
DHTRecordCrypto? crypto}) async {
|
DHTRecordCrypto? crypto}) async {
|
||||||
// If we are opening a key that already exists
|
// If we are opening a key that already exists
|
||||||
// make sure we are using the same parent if one was specified
|
// 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');
|
assert(existingParent == parent, 'wrong parent for opened key');
|
||||||
|
|
||||||
// Open from the veilid api
|
// Open from the veilid api
|
||||||
|
@ -206,7 +213,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||||
|
|
||||||
// Register the dependency if specified
|
// Register the dependency if specified
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
_addDependency(parent, rec.key);
|
await _addDependency(parent, rec.key);
|
||||||
}
|
}
|
||||||
_recordOpened(rec);
|
_recordOpened(rec);
|
||||||
|
|
||||||
|
@ -224,7 +231,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||||
}) async {
|
}) async {
|
||||||
// If we are opening a key that already exists
|
// If we are opening a key that already exists
|
||||||
// make sure we are using the same parent if one was specified
|
// 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');
|
assert(existingParent == parent, 'wrong parent for opened key');
|
||||||
|
|
||||||
// Open from the veilid api
|
// Open from the veilid api
|
||||||
|
@ -241,7 +248,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
|
||||||
|
|
||||||
// Register the dependency if specified
|
// Register the dependency if specified
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
_addDependency(parent, rec.key);
|
await _addDependency(parent, rec.key);
|
||||||
}
|
}
|
||||||
_recordOpened(rec);
|
_recordOpened(rec);
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,10 @@ DHTRecordPoolAllocations _$DHTRecordPoolAllocationsFromJson(
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$DHTRecordPoolAllocations {
|
mixin _$DHTRecordPoolAllocations {
|
||||||
IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
IMap<String, ISet<Typed<FixedEncodedString43>>> get childrenByParent =>
|
||||||
get childrenByParent => throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError; // String key due to IMap<> json unsupported in key
|
||||||
IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
IMap<String, Typed<FixedEncodedString43>> get parentByChild =>
|
||||||
get parentByChild => throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
|
@ -39,10 +39,8 @@ abstract class $DHTRecordPoolAllocationsCopyWith<$Res> {
|
||||||
_$DHTRecordPoolAllocationsCopyWithImpl<$Res, DHTRecordPoolAllocations>;
|
_$DHTRecordPoolAllocationsCopyWithImpl<$Res, DHTRecordPoolAllocations>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
{IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent,
|
||||||
childrenByParent,
|
IMap<String, Typed<FixedEncodedString43>> parentByChild});
|
||||||
IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
|
||||||
parentByChild});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
@ -66,12 +64,11 @@ class _$DHTRecordPoolAllocationsCopyWithImpl<$Res,
|
||||||
childrenByParent: null == childrenByParent
|
childrenByParent: null == childrenByParent
|
||||||
? _value.childrenByParent
|
? _value.childrenByParent
|
||||||
: childrenByParent // ignore: cast_nullable_to_non_nullable
|
: childrenByParent // ignore: cast_nullable_to_non_nullable
|
||||||
as IMap<Typed<FixedEncodedString43>,
|
as IMap<String, ISet<Typed<FixedEncodedString43>>>,
|
||||||
ISet<Typed<FixedEncodedString43>>>,
|
|
||||||
parentByChild: null == parentByChild
|
parentByChild: null == parentByChild
|
||||||
? _value.parentByChild
|
? _value.parentByChild
|
||||||
: parentByChild // ignore: cast_nullable_to_non_nullable
|
: parentByChild // ignore: cast_nullable_to_non_nullable
|
||||||
as IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>,
|
as IMap<String, Typed<FixedEncodedString43>>,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,10 +83,8 @@ abstract class _$$_DHTRecordPoolAllocationsCopyWith<$Res>
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
{IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent,
|
||||||
childrenByParent,
|
IMap<String, Typed<FixedEncodedString43>> parentByChild});
|
||||||
IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
|
||||||
parentByChild});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
@ -111,12 +106,11 @@ class __$$_DHTRecordPoolAllocationsCopyWithImpl<$Res>
|
||||||
childrenByParent: null == childrenByParent
|
childrenByParent: null == childrenByParent
|
||||||
? _value.childrenByParent
|
? _value.childrenByParent
|
||||||
: childrenByParent // ignore: cast_nullable_to_non_nullable
|
: childrenByParent // ignore: cast_nullable_to_non_nullable
|
||||||
as IMap<Typed<FixedEncodedString43>,
|
as IMap<String, ISet<Typed<FixedEncodedString43>>>,
|
||||||
ISet<Typed<FixedEncodedString43>>>,
|
|
||||||
parentByChild: null == parentByChild
|
parentByChild: null == parentByChild
|
||||||
? _value.parentByChild
|
? _value.parentByChild
|
||||||
: parentByChild // ignore: cast_nullable_to_non_nullable
|
: 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);
|
_$$_DHTRecordPoolAllocationsFromJson(json);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
final IMap<String, ISet<Typed<FixedEncodedString43>>> childrenByParent;
|
||||||
childrenByParent;
|
// String key due to IMap<> json unsupported in key
|
||||||
@override
|
@override
|
||||||
final IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
final IMap<String, Typed<FixedEncodedString43>> parentByChild;
|
||||||
parentByChild;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -174,22 +167,18 @@ class _$_DHTRecordPoolAllocations implements _DHTRecordPoolAllocations {
|
||||||
|
|
||||||
abstract class _DHTRecordPoolAllocations implements DHTRecordPoolAllocations {
|
abstract class _DHTRecordPoolAllocations implements DHTRecordPoolAllocations {
|
||||||
const factory _DHTRecordPoolAllocations(
|
const factory _DHTRecordPoolAllocations(
|
||||||
{required final IMap<Typed<FixedEncodedString43>,
|
{required final IMap<String, ISet<Typed<FixedEncodedString43>>>
|
||||||
ISet<Typed<FixedEncodedString43>>>
|
|
||||||
childrenByParent,
|
childrenByParent,
|
||||||
required final IMap<Typed<FixedEncodedString43>,
|
required final IMap<String, Typed<FixedEncodedString43>>
|
||||||
Typed<FixedEncodedString43>>
|
|
||||||
parentByChild}) = _$_DHTRecordPoolAllocations;
|
parentByChild}) = _$_DHTRecordPoolAllocations;
|
||||||
|
|
||||||
factory _DHTRecordPoolAllocations.fromJson(Map<String, dynamic> json) =
|
factory _DHTRecordPoolAllocations.fromJson(Map<String, dynamic> json) =
|
||||||
_$_DHTRecordPoolAllocations.fromJson;
|
_$_DHTRecordPoolAllocations.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
IMap<Typed<FixedEncodedString43>, ISet<Typed<FixedEncodedString43>>>
|
IMap<String, ISet<Typed<FixedEncodedString43>>> get childrenByParent;
|
||||||
get childrenByParent;
|
@override // String key due to IMap<> json unsupported in key
|
||||||
@override
|
IMap<String, Typed<FixedEncodedString43>> get parentByChild;
|
||||||
IMap<Typed<FixedEncodedString43>, Typed<FixedEncodedString43>>
|
|
||||||
get parentByChild;
|
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_DHTRecordPoolAllocationsCopyWith<_$_DHTRecordPoolAllocations>
|
_$$_DHTRecordPoolAllocationsCopyWith<_$_DHTRecordPoolAllocations>
|
||||||
|
|
|
@ -9,16 +9,15 @@ part of 'dht_record_pool.dart';
|
||||||
_$_DHTRecordPoolAllocations _$$_DHTRecordPoolAllocationsFromJson(
|
_$_DHTRecordPoolAllocations _$$_DHTRecordPoolAllocationsFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
_$_DHTRecordPoolAllocations(
|
_$_DHTRecordPoolAllocations(
|
||||||
childrenByParent: IMap<Typed<FixedEncodedString43>,
|
childrenByParent:
|
||||||
ISet<Typed<FixedEncodedString43>>>.fromJson(
|
IMap<String, ISet<Typed<FixedEncodedString43>>>.fromJson(
|
||||||
json['children_by_parent'] as Map<String, dynamic>,
|
json['children_by_parent'] as Map<String, dynamic>,
|
||||||
(value) => Typed<FixedEncodedString43>.fromJson(value),
|
(value) => value as String,
|
||||||
(value) => ISet<Typed<FixedEncodedString43>>.fromJson(
|
(value) => ISet<Typed<FixedEncodedString43>>.fromJson(value,
|
||||||
value, (value) => Typed<FixedEncodedString43>.fromJson(value))),
|
(value) => Typed<FixedEncodedString43>.fromJson(value))),
|
||||||
parentByChild: IMap<Typed<FixedEncodedString43>,
|
parentByChild: IMap<String, Typed<FixedEncodedString43>>.fromJson(
|
||||||
Typed<FixedEncodedString43>>.fromJson(
|
|
||||||
json['parent_by_child'] as Map<String, dynamic>,
|
json['parent_by_child'] as Map<String, dynamic>,
|
||||||
(value) => Typed<FixedEncodedString43>.fromJson(value),
|
(value) => value as String,
|
||||||
(value) => Typed<FixedEncodedString43>.fromJson(value)),
|
(value) => Typed<FixedEncodedString43>.fromJson(value)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -26,13 +25,13 @@ Map<String, dynamic> _$$_DHTRecordPoolAllocationsToJson(
|
||||||
_$_DHTRecordPoolAllocations instance) =>
|
_$_DHTRecordPoolAllocations instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'children_by_parent': instance.childrenByParent.toJson(
|
'children_by_parent': instance.childrenByParent.toJson(
|
||||||
(value) => value.toJson(),
|
(value) => value,
|
||||||
(value) => value.toJson(
|
(value) => value.toJson(
|
||||||
(value) => value.toJson(),
|
(value) => value.toJson(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'parent_by_child': instance.parentByChild.toJson(
|
'parent_by_child': instance.parentByChild.toJson(
|
||||||
(value) => value.toJson(),
|
(value) => value,
|
||||||
(value) => value.toJson(),
|
(value) => value.toJson(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,50 +26,52 @@ class IdentityMasterWithSecrets {
|
||||||
return (await pool.create(crypto: const DHTRecordCryptoPublic()))
|
return (await pool.create(crypto: const DHTRecordCryptoPublic()))
|
||||||
.deleteScope((masterRec) async {
|
.deleteScope((masterRec) async {
|
||||||
// Identity record is private
|
// Identity record is private
|
||||||
final identityRec = await pool.create(parent: masterRec.key);
|
return (await pool.create(parent: masterRec.key))
|
||||||
// Make IdentityMaster
|
.scope((identityRec) async {
|
||||||
final masterRecordKey = masterRec.key;
|
// Make IdentityMaster
|
||||||
final masterOwner = masterRec.ownerKeyPair!;
|
final masterRecordKey = masterRec.key;
|
||||||
final masterSigBuf = BytesBuilder()
|
final masterOwner = masterRec.ownerKeyPair!;
|
||||||
..add(masterRecordKey.decode())
|
final masterSigBuf = BytesBuilder()
|
||||||
..add(masterOwner.key.decode());
|
..add(masterRecordKey.decode())
|
||||||
|
..add(masterOwner.key.decode());
|
||||||
|
|
||||||
final identityRecordKey = identityRec.key;
|
final identityRecordKey = identityRec.key;
|
||||||
final identityOwner = identityRec.ownerKeyPair!;
|
final identityOwner = identityRec.ownerKeyPair!;
|
||||||
final identitySigBuf = BytesBuilder()
|
final identitySigBuf = BytesBuilder()
|
||||||
..add(identityRecordKey.decode())
|
..add(identityRecordKey.decode())
|
||||||
..add(identityOwner.key.decode());
|
..add(identityOwner.key.decode());
|
||||||
|
|
||||||
assert(masterRecordKey.kind == identityRecordKey.kind,
|
assert(masterRecordKey.kind == identityRecordKey.kind,
|
||||||
'new master and identity should have same cryptosystem');
|
'new master and identity should have same cryptosystem');
|
||||||
final crypto = await pool.veilid.getCryptoSystem(masterRecordKey.kind);
|
final crypto = await pool.veilid.getCryptoSystem(masterRecordKey.kind);
|
||||||
|
|
||||||
final identitySignature =
|
final identitySignature =
|
||||||
await crypto.signWithKeyPair(masterOwner, identitySigBuf.toBytes());
|
await crypto.signWithKeyPair(masterOwner, identitySigBuf.toBytes());
|
||||||
final masterSignature =
|
final masterSignature =
|
||||||
await crypto.signWithKeyPair(identityOwner, masterSigBuf.toBytes());
|
await crypto.signWithKeyPair(identityOwner, masterSigBuf.toBytes());
|
||||||
|
|
||||||
final identityMaster = IdentityMaster(
|
final identityMaster = IdentityMaster(
|
||||||
identityRecordKey: identityRecordKey,
|
identityRecordKey: identityRecordKey,
|
||||||
identityPublicKey: identityOwner.key,
|
identityPublicKey: identityOwner.key,
|
||||||
masterRecordKey: masterRecordKey,
|
masterRecordKey: masterRecordKey,
|
||||||
masterPublicKey: masterOwner.key,
|
masterPublicKey: masterOwner.key,
|
||||||
identitySignature: identitySignature,
|
identitySignature: identitySignature,
|
||||||
masterSignature: masterSignature);
|
masterSignature: masterSignature);
|
||||||
|
|
||||||
// Write identity master to master dht key
|
// Write identity master to master dht key
|
||||||
await masterRec.eventualWriteJson(identityMaster);
|
await masterRec.eventualWriteJson(identityMaster);
|
||||||
|
|
||||||
// Make empty identity
|
// Make empty identity
|
||||||
const identity = Identity(accountRecords: IMapConst({}));
|
const identity = Identity(accountRecords: IMapConst({}));
|
||||||
|
|
||||||
// Write empty identity to identity dht key
|
// Write empty identity to identity dht key
|
||||||
await identityRec.eventualWriteJson(identity);
|
await identityRec.eventualWriteJson(identity);
|
||||||
|
|
||||||
return IdentityMasterWithSecrets._(
|
return IdentityMasterWithSecrets._(
|
||||||
identityMaster: identityMaster,
|
identityMaster: identityMaster,
|
||||||
masterSecret: masterOwner.secret,
|
masterSecret: masterOwner.secret,
|
||||||
identitySecret: identityOwner.secret);
|
identitySecret: identityOwner.secret);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,10 @@ abstract mixin class AsyncTableDBBacked<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store things to storage
|
/// Store things to storage
|
||||||
Future<void> store(T obj) async {
|
Future<T> store(T obj) async {
|
||||||
await tableScope(tableName(), (tdb) async {
|
await tableScope(tableName(), (tdb) async {
|
||||||
await tdb.storeStringJson(0, tableKeyName(), valueToJson(obj));
|
await tdb.storeStringJson(0, tableKeyName(), valueToJson(obj));
|
||||||
});
|
});
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <screen_retriever/screen_retriever_plugin.h>
|
#include <screen_retriever/screen_retriever_plugin.h>
|
||||||
|
#include <smart_auth/smart_auth_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
#include <veilid/veilid_plugin.h>
|
#include <veilid/veilid_plugin.h>
|
||||||
#include <window_manager/window_manager_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 =
|
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
|
||||||
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
|
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 =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
screen_retriever
|
screen_retriever
|
||||||
|
smart_auth
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
veilid
|
veilid
|
||||||
window_manager
|
window_manager
|
||||||
|
|
|
@ -9,6 +9,7 @@ import path_provider_foundation
|
||||||
import screen_retriever
|
import screen_retriever
|
||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
import smart_auth
|
||||||
import sqflite
|
import sqflite
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import veilid
|
import veilid
|
||||||
|
@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
SmartAuthPlugin.register(with: registry.registrar(forPlugin: "SmartAuthPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
VeilidPlugin.register(with: registry.registrar(forPlugin: "VeilidPlugin"))
|
VeilidPlugin.register(with: registry.registrar(forPlugin: "VeilidPlugin"))
|
||||||
|
|
|
@ -8,9 +8,13 @@ PODS:
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- screen_retriever (0.0.1):
|
- screen_retriever (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- share_plus (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- smart_auth (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
- sqflite (0.0.2):
|
- sqflite (0.0.2):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FMDB (>= 2.7.5)
|
- FMDB (>= 2.7.5)
|
||||||
|
@ -25,7 +29,9 @@ DEPENDENCIES:
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
|
- 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`)
|
- 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`)
|
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
|
||||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
- veilid (from `Flutter/ephemeral/.symlinks/plugins/veilid/macos`)
|
- veilid (from `Flutter/ephemeral/.symlinks/plugins/veilid/macos`)
|
||||||
|
@ -42,8 +48,12 @@ EXTERNAL SOURCES:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||||
screen_retriever:
|
screen_retriever:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
|
||||||
|
share_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||||
|
smart_auth:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/smart_auth/macos
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
|
@ -58,7 +68,9 @@ SPEC CHECKSUMS:
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||||
|
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
|
||||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||||
|
smart_auth: b38e3ab4bfe089eacb1e233aca1a2340f96c28e9
|
||||||
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
|
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
|
||||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||||
veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0
|
veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0
|
||||||
|
|
36
pubspec.lock
36
pubspec.lock
|
@ -757,6 +757,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
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:
|
octo_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -853,6 +861,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.14.0"
|
version: "0.14.0"
|
||||||
|
pinput:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: pinput
|
||||||
|
sha256: "543da5bfdefd9e06914a12100f8c9156f84cef3efc14bca507c49e966c5b813b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1090,6 +1106,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
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:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1250,6 +1274,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
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:
|
url_launcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1360,7 +1392,7 @@ packages:
|
||||||
path: "../veilid/veilid-flutter"
|
path: "../veilid/veilid-flutter"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.1.6"
|
version: "0.1.7"
|
||||||
visibility_detector:
|
visibility_detector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1427,4 +1459,4 @@ packages:
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.5 <4.0.0"
|
dart: ">=3.0.5 <4.0.0"
|
||||||
flutter: ">=3.10.0"
|
flutter: ">=3.10.6"
|
||||||
|
|
|
@ -40,8 +40,10 @@ dependencies:
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
json_annotation: ^4.8.1
|
json_annotation: ^4.8.1
|
||||||
loggy: ^2.0.3
|
loggy: ^2.0.3
|
||||||
|
motion_toast: ^2.7.8
|
||||||
path: ^1.8.2
|
path: ^1.8.2
|
||||||
path_provider: ^2.0.11
|
path_provider: ^2.0.11
|
||||||
|
pinput: ^2.3.0
|
||||||
protobuf: ^3.0.0
|
protobuf: ^3.0.0
|
||||||
quickalert: ^1.0.1
|
quickalert: ^1.0.1
|
||||||
radix_colors: ^1.0.4
|
radix_colors: ^1.0.4
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <screen_retriever/screen_retriever_plugin.h>
|
#include <screen_retriever/screen_retriever_plugin.h>
|
||||||
#include <share_plus/share_plus_windows_plugin_c_api.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 <url_launcher_windows/url_launcher_windows.h>
|
||||||
#include <veilid/veilid_plugin.h>
|
#include <veilid/veilid_plugin.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
|
@ -17,6 +18,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
||||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||||
|
SmartAuthPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("SmartAuthPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
VeilidPluginRegisterWithRegistrar(
|
VeilidPluginRegisterWithRegistrar(
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
screen_retriever
|
screen_retriever
|
||||||
share_plus
|
share_plus
|
||||||
|
smart_auth
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
veilid
|
veilid
|
||||||
window_manager
|
window_manager
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue