mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-08-07 21:52:26 -04:00
layout work
This commit is contained in:
parent
accd79c82d
commit
95e5306eb3
11 changed files with 657 additions and 216 deletions
|
@ -33,17 +33,23 @@ class ContactInvitationListWidgetState
|
|||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
constraints: const BoxConstraints(minHeight: 64, maxHeight: 200),
|
||||
decoration: ShapeDecoration(
|
||||
color: scale.primaryScale.subtleBorder,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
)),
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: ShapeDecoration(
|
||||
color: scale.grayScale.appBackground,
|
||||
color: scale.primaryScale.subtleBackground,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
)),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(
|
||||
color: scale.primaryScale.subtleBorder, width: 4))),
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount: widget.contactInvitationRecordList.length,
|
||||
|
@ -69,7 +75,7 @@ class ContactInvitationListWidgetState
|
|||
return index;
|
||||
},
|
||||
shrinkWrap: true,
|
||||
)).paddingLTRB(8, 0, 8, 8).flexible()
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -30,21 +30,28 @@ class ContactListWidget extends ConsumerWidget {
|
|||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: ShapeDecoration(
|
||||
color: scale.primaryScale.subtleBorder,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
)),
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 64,
|
||||
),
|
||||
child: Column(children: [
|
||||
Text(
|
||||
'Contacts',
|
||||
style: textTheme.bodyLarge,
|
||||
).paddingAll(8),
|
||||
translate('contact_list.title'),
|
||||
style: textTheme.titleMedium!
|
||||
.copyWith(color: scale.primaryScale.subtleText),
|
||||
).paddingLTRB(4, 4, 4, 0),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: ShapeDecoration(
|
||||
color: scale.grayScale.appBackground,
|
||||
color: scale.primaryScale.subtleBackground,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
)),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(
|
||||
color: scale.primaryScale.subtleBorder, width: 4))),
|
||||
child: (contactList.isEmpty)
|
||||
? const EmptyContactListWidget().toCenter()
|
||||
: SearchableList<proto.Contact>(
|
||||
|
@ -76,6 +83,6 @@ class ContactListWidget extends ConsumerWidget {
|
|||
),
|
||||
).expanded()
|
||||
]),
|
||||
).paddingLTRB(8, 0, 8, 65);
|
||||
).paddingLTRB(8, 0, 8, 8);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,13 +19,13 @@ class EmptyContactListWidget extends ConsumerWidget {
|
|||
children: [
|
||||
Icon(
|
||||
Icons.person_add_sharp,
|
||||
color: scale.primaryScale.border,
|
||||
color: scale.primaryScale.subtleBorder,
|
||||
size: 48,
|
||||
),
|
||||
Text(
|
||||
translate('contact_list.invite_people'),
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: scale.primaryScale.border,
|
||||
color: scale.primaryScale.subtleBorder,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -21,6 +21,41 @@ class PasteInviteDialog extends ConsumerStatefulWidget {
|
|||
|
||||
@override
|
||||
PasteInviteDialogState createState() => PasteInviteDialogState();
|
||||
|
||||
static Future<void> show(BuildContext context) async {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final textTheme = theme.textTheme;
|
||||
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
// ignore: prefer_expression_function_bodies
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
side: BorderSide(width: 4, color: scale.primaryScale.border),
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
backgroundColor: scale.primaryScale.border,
|
||||
title: Text(
|
||||
translate('paste_invite_dialog.title'),
|
||||
style: textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
titlePadding: EdgeInsets.fromLTRB(4, 4, 4, 0),
|
||||
content: DecoratedBox(
|
||||
decoration: ShapeDecoration(
|
||||
color: scale.primaryScale.subtleBackground,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(
|
||||
width: 4, color: scale.primaryScale.border),
|
||||
)),
|
||||
child: const PasteInviteDialog().paddingAll(4)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
||||
|
@ -240,16 +275,16 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
|||
return SizedBox(height: 400, child: waitingPage(context));
|
||||
}
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 400),
|
||||
constraints: const BoxConstraints(maxHeight: 400, maxWidth: 400),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
translate('paste_invite_dialog.paste_invite_here'),
|
||||
).paddingAll(8),
|
||||
).paddingLTRB(0, 0, 0, 8),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: TextField(
|
||||
|
@ -267,12 +302,13 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
|||
'---- END VEILIDCHAT CONTACT INVITE -----\n',
|
||||
//labelText: translate('paste_invite_dialog.paste')
|
||||
),
|
||||
).paddingAll(8)),
|
||||
)).paddingLTRB(0, 0, 0, 8),
|
||||
if (_validatingPaste)
|
||||
Column(children: [
|
||||
Text(translate('paste_invite_dialog.validating')),
|
||||
Text(translate('paste_invite_dialog.validating'))
|
||||
.paddingLTRB(0, 0, 0, 8),
|
||||
buildProgressIndicator(context),
|
||||
]),
|
||||
]).paddingAll(16).toCenter(),
|
||||
if (_validInvitation == null &&
|
||||
!_validatingPaste &&
|
||||
_pasteTextController.text.isNotEmpty)
|
||||
|
@ -282,10 +318,15 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
|||
]).paddingAll(16).toCenter(),
|
||||
if (_validInvitation != null && !_validatingPaste)
|
||||
Column(children: [
|
||||
ProfileWidget(
|
||||
name: _validInvitation!.contactRequestPrivate.profile.name,
|
||||
title:
|
||||
_validInvitation!.contactRequestPrivate.profile.title),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 64),
|
||||
width: double.infinity,
|
||||
child: ProfileWidget(
|
||||
name: _validInvitation!
|
||||
.contactRequestPrivate.profile.name,
|
||||
title: _validInvitation!
|
||||
.contactRequestPrivate.profile.title))
|
||||
.paddingLTRB(0, 0, 0, 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
|
|
|
@ -24,26 +24,23 @@ class ProfileWidget extends ConsumerWidget {
|
|||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final textTheme = theme.textTheme;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: ShapeDecoration(
|
||||
color: scale.primaryScale.subtleBackground,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(color: scale.primaryScale.border))),
|
||||
child: Row(children: [
|
||||
Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text(name, style: textTheme.headlineSmall).paddingAll(8),
|
||||
if (title != null && title!.isNotEmpty)
|
||||
Text(title!, style: textTheme.bodyMedium).paddingLTRB(8, 0, 8, 8),
|
||||
]).expanded(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
tooltip: translate('app_bar.settings_tooltip'),
|
||||
onPressed: () async {
|
||||
context.go('/home/settings');
|
||||
})
|
||||
])).paddingAll(8);
|
||||
return DecoratedBox(
|
||||
decoration: ShapeDecoration(
|
||||
color: scale.primaryScale.subtleBorder,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(
|
||||
width: 0, color: scale.primaryScale.subtleBorder))),
|
||||
child: Column(children: [
|
||||
Text(
|
||||
name,
|
||||
style: textTheme.headlineSmall,
|
||||
textAlign: TextAlign.left,
|
||||
).paddingAll(4),
|
||||
if (title != null && title!.isNotEmpty)
|
||||
Text(title!, style: textTheme.bodyMedium).paddingLTRB(4, 0, 4, 4),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
315
lib/components/scan_invite_dialog.dart
Normal file
315
lib/components/scan_invite_dialog.dart
Normal file
|
@ -0,0 +1,315 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:quickalert/quickalert.dart';
|
||||
|
||||
import '../entities/local_account.dart';
|
||||
import '../providers/account.dart';
|
||||
import '../providers/contact.dart';
|
||||
import '../providers/contact_invite.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'enter_pin.dart';
|
||||
import 'profile_widget.dart';
|
||||
|
||||
class ScanInviteDialog extends ConsumerStatefulWidget {
|
||||
const ScanInviteDialog({super.key});
|
||||
|
||||
@override
|
||||
ScanInviteDialogState createState() => ScanInviteDialogState();
|
||||
}
|
||||
|
||||
class ScanInviteDialogState extends ConsumerState<ScanInviteDialog> {
|
||||
final _pasteTextController = TextEditingController();
|
||||
|
||||
EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none;
|
||||
String _encryptionKey = '';
|
||||
Timestamp? _expiration;
|
||||
ValidContactInvitation? _validInvitation;
|
||||
bool _validatingPaste = false;
|
||||
bool _isAccepting = false;
|
||||
|
||||
@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('receive_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('receive_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> _onAccept() async {
|
||||
final navigator = Navigator.of(context);
|
||||
|
||||
setState(() {
|
||||
_isAccepting = true;
|
||||
});
|
||||
final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future);
|
||||
if (activeAccountInfo == null) {
|
||||
setState(() {
|
||||
_isAccepting = false;
|
||||
});
|
||||
navigator.pop();
|
||||
return;
|
||||
}
|
||||
final validInvitation = _validInvitation;
|
||||
if (validInvitation != null) {
|
||||
final acceptedContact =
|
||||
await acceptContactInvitation(activeAccountInfo, validInvitation);
|
||||
if (acceptedContact != null) {
|
||||
await createContact(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
profile: acceptedContact.profile,
|
||||
remoteIdentity: acceptedContact.remoteIdentity,
|
||||
remoteConversationRecordKey:
|
||||
acceptedContact.remoteConversationRecordKey,
|
||||
localConversationRecordKey:
|
||||
acceptedContact.localConversationRecordKey,
|
||||
);
|
||||
ref
|
||||
..invalidate(fetchContactInvitationRecordsProvider)
|
||||
..invalidate(fetchContactListProvider);
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
showErrorToast(context, 'paste_invite_dialog.failed_to_accept');
|
||||
}
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
_isAccepting = false;
|
||||
});
|
||||
navigator.pop();
|
||||
}
|
||||
|
||||
Future<void> _onReject() async {
|
||||
final navigator = Navigator.of(context);
|
||||
|
||||
setState(() {
|
||||
_isAccepting = true;
|
||||
});
|
||||
final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future);
|
||||
if (activeAccountInfo == null) {
|
||||
setState(() {
|
||||
_isAccepting = false;
|
||||
});
|
||||
navigator.pop();
|
||||
return;
|
||||
}
|
||||
final validInvitation = _validInvitation;
|
||||
if (validInvitation != null) {
|
||||
if (await rejectContactInvitation(activeAccountInfo, validInvitation)) {
|
||||
// do nothing right now
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
showErrorToast(context, 'paste_invite_dialog.failed_to_reject');
|
||||
}
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
_isAccepting = false;
|
||||
});
|
||||
navigator.pop();
|
||||
}
|
||||
|
||||
Future<void> _onPasteChanged(String text) async {
|
||||
try {
|
||||
final lines = text.split('\n');
|
||||
if (lines.isEmpty) {
|
||||
setState(() {
|
||||
_validatingPaste = false;
|
||||
_validInvitation = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var firstline =
|
||||
lines.indexWhere((element) => element.contains('BEGIN VEILIDCHAT'));
|
||||
firstline += 1;
|
||||
|
||||
var lastline =
|
||||
lines.indexWhere((element) => element.contains('END VEILIDCHAT'));
|
||||
if (lastline == -1) {
|
||||
lastline = lines.length;
|
||||
}
|
||||
if (lastline <= firstline) {
|
||||
setState(() {
|
||||
_validatingPaste = false;
|
||||
_validInvitation = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
final inviteDataBase64 = lines.sublist(firstline, lastline).join();
|
||||
final inviteData = base64UrlNoPadDecode(inviteDataBase64);
|
||||
|
||||
setState(() {
|
||||
_validatingPaste = true;
|
||||
_validInvitation = null;
|
||||
});
|
||||
final validatedContactInvitation = await validateContactInvitation(
|
||||
inviteData, (encryptionKeyType, encryptedSecret) async {
|
||||
switch (encryptionKeyType) {
|
||||
case EncryptionKeyType.none:
|
||||
return SecretKey.fromBytes(encryptedSecret);
|
||||
case EncryptionKeyType.pin:
|
||||
//xxx
|
||||
return SecretKey.fromBytes(encryptedSecret);
|
||||
case EncryptionKeyType.password:
|
||||
//xxx
|
||||
return SecretKey.fromBytes(encryptedSecret);
|
||||
}
|
||||
});
|
||||
// Verify expiration
|
||||
// xxx
|
||||
|
||||
setState(() {
|
||||
_validatingPaste = false;
|
||||
_validInvitation = validatedContactInvitation;
|
||||
});
|
||||
} on Exception catch (_) {
|
||||
setState(() {
|
||||
_validatingPaste = false;
|
||||
_validInvitation = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
//final scale = theme.extension<ScaleScheme>()!;
|
||||
final textTheme = theme.textTheme;
|
||||
//final height = MediaQuery.of(context).size.height;
|
||||
|
||||
if (_isAccepting) {
|
||||
return SizedBox(height: 400, child: waitingPage(context));
|
||||
}
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 400),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
translate('paste_invite_dialog.paste_invite_here'),
|
||||
).paddingAll(8),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: TextField(
|
||||
enabled: !_validatingPaste,
|
||||
onChanged: _onPasteChanged,
|
||||
style: textTheme.labelSmall!
|
||||
.copyWith(fontFamily: 'Victor Mono', fontSize: 11),
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
controller: _pasteTextController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: '--- BEGIN VEILIDCHAT CONTACT INVITE ----\n'
|
||||
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n'
|
||||
'---- END VEILIDCHAT CONTACT INVITE -----\n',
|
||||
//labelText: translate('paste_invite_dialog.paste')
|
||||
),
|
||||
).paddingAll(8)),
|
||||
if (_validatingPaste)
|
||||
Column(children: [
|
||||
Text(translate('paste_invite_dialog.validating')),
|
||||
buildProgressIndicator(context),
|
||||
]),
|
||||
if (_validInvitation == null &&
|
||||
!_validatingPaste &&
|
||||
_pasteTextController.text.isNotEmpty)
|
||||
Column(children: [
|
||||
Text(translate('paste_invite_dialog.invalid_invitation')),
|
||||
const Icon(Icons.error)
|
||||
]).paddingAll(16).toCenter(),
|
||||
if (_validInvitation != null && !_validatingPaste)
|
||||
Column(children: [
|
||||
ProfileWidget(
|
||||
name: _validInvitation!.contactRequestPrivate.profile.name,
|
||||
title:
|
||||
_validInvitation!.contactRequestPrivate.profile.title),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.check_circle),
|
||||
label: Text(translate('button.accept')),
|
||||
onPressed: _onAccept,
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.cancel),
|
||||
label: Text(translate('button.reject')),
|
||||
onPressed: _onReject,
|
||||
)
|
||||
],
|
||||
),
|
||||
])
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
}
|
||||
}
|
|
@ -20,6 +20,26 @@ class SendInviteDialog extends ConsumerStatefulWidget {
|
|||
|
||||
@override
|
||||
SendInviteDialogState createState() => SendInviteDialogState();
|
||||
|
||||
static Future<void> show(BuildContext context) async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
// ignore: prefer_expression_function_bodies
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
contentPadding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
),
|
||||
title: Text(
|
||||
translate('send_invite_dialog.title'),
|
||||
style: const TextStyle(fontSize: 24),
|
||||
),
|
||||
content: const SendInviteDialog());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
|
||||
|
@ -161,7 +181,7 @@ class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
|
|||
onSelected: _onNoneEncryptionSelected,
|
||||
),
|
||||
ChoiceChip(
|
||||
label: Text(translate('send_invite_dialog.numeric_pin')),
|
||||
label: Text(translate('send_invite_dialog.pin')),
|
||||
selected: _encryptionKeyType == EncryptionKeyType.pin,
|
||||
onSelected: _onPinEncryptionSelected,
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue