mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-02-23 00:09:55 -05:00
layout work
This commit is contained in:
parent
accd79c82d
commit
95e5306eb3
@ -47,11 +47,13 @@
|
|||||||
"title": "Language Selection"
|
"title": "Language Selection"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"account_page": {
|
"home": {
|
||||||
"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": "Invalid Account",
|
"invalid_account_title": "Invalid Account",
|
||||||
"invalid_account_text": "Account is invalid, removing from list",
|
"invalid_account_text": "Account is invalid, removing from list"
|
||||||
|
},
|
||||||
|
"account_page": {
|
||||||
"contact_invitations": "Contact Invitations"
|
"contact_invitations": "Contact Invitations"
|
||||||
},
|
},
|
||||||
"accounts_menu": {
|
"accounts_menu": {
|
||||||
@ -61,13 +63,14 @@
|
|||||||
"paste_invite": "Paste Invite"
|
"paste_invite": "Paste Invite"
|
||||||
},
|
},
|
||||||
"send_invite_dialog": {
|
"send_invite_dialog": {
|
||||||
|
"title": "Send Contact Invite",
|
||||||
"connect_with_me": "Connect with me on VeilidChat!",
|
"connect_with_me": "Connect with me on VeilidChat!",
|
||||||
"enter_message_hint": "enter message for contact (optional)",
|
"enter_message_hint": "enter message for contact (optional)",
|
||||||
"message_to_contact": "Message to send with invitation (not encrypted)",
|
"message_to_contact": "Message to send with invitation (not encrypted)",
|
||||||
"generate": "Generate Invite",
|
"generate": "Generate Invite",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"unlocked": "Unlocked",
|
"unlocked": "Unlocked",
|
||||||
"numeric_pin": "Numeric PIN",
|
"pin": "PIN",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"protect_this_invitation": "Protect this invitation:",
|
"protect_this_invitation": "Protect this invitation:",
|
||||||
"note": "Note:",
|
"note": "Note:",
|
||||||
@ -81,6 +84,7 @@
|
|||||||
"invitation_copied": "Invitation Copied"
|
"invitation_copied": "Invitation Copied"
|
||||||
},
|
},
|
||||||
"paste_invite_dialog": {
|
"paste_invite_dialog": {
|
||||||
|
"title": "Paste Contact Invite",
|
||||||
"paste_invite_here": "Paste your contact invite here:",
|
"paste_invite_here": "Paste your contact invite here:",
|
||||||
"paste": "Paste",
|
"paste": "Paste",
|
||||||
"message_from_contact": "Message from contact",
|
"message_from_contact": "Message from contact",
|
||||||
@ -94,6 +98,7 @@
|
|||||||
"reenter_pin": "Re-Enter PIN To Confirm"
|
"reenter_pin": "Re-Enter PIN To Confirm"
|
||||||
},
|
},
|
||||||
"contact_list": {
|
"contact_list": {
|
||||||
|
"title": "Contact List",
|
||||||
"invite_people": "Invite people to VeilidChat",
|
"invite_people": "Invite people to VeilidChat",
|
||||||
"search": "Search contacts",
|
"search": "Search contacts",
|
||||||
"invitation": "Invitation"
|
"invitation": "Invitation"
|
||||||
|
@ -33,17 +33,23 @@ class ContactInvitationListWidgetState
|
|||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
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(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: ShapeDecoration(
|
decoration: ShapeDecoration(
|
||||||
color: scale.grayScale.appBackground,
|
color: scale.primaryScale.subtleBackground,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
)),
|
side: BorderSide(
|
||||||
|
color: scale.primaryScale.subtleBorder, width: 4))),
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
itemCount: widget.contactInvitationRecordList.length,
|
itemCount: widget.contactInvitationRecordList.length,
|
||||||
@ -69,7 +75,7 @@ class ContactInvitationListWidgetState
|
|||||||
return index;
|
return index;
|
||||||
},
|
},
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
)).paddingLTRB(8, 0, 8, 8).flexible()
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -30,21 +30,28 @@ class ContactListWidget extends ConsumerWidget {
|
|||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
decoration: ShapeDecoration(
|
||||||
|
color: scale.primaryScale.subtleBorder,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
)),
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
minHeight: 64,
|
minHeight: 64,
|
||||||
),
|
),
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
Text(
|
Text(
|
||||||
'Contacts',
|
translate('contact_list.title'),
|
||||||
style: textTheme.bodyLarge,
|
style: textTheme.titleMedium!
|
||||||
).paddingAll(8),
|
.copyWith(color: scale.primaryScale.subtleText),
|
||||||
|
).paddingLTRB(4, 4, 4, 0),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: ShapeDecoration(
|
decoration: ShapeDecoration(
|
||||||
color: scale.grayScale.appBackground,
|
color: scale.primaryScale.subtleBackground,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
)),
|
side: BorderSide(
|
||||||
|
color: scale.primaryScale.subtleBorder, width: 4))),
|
||||||
child: (contactList.isEmpty)
|
child: (contactList.isEmpty)
|
||||||
? const EmptyContactListWidget().toCenter()
|
? const EmptyContactListWidget().toCenter()
|
||||||
: SearchableList<proto.Contact>(
|
: SearchableList<proto.Contact>(
|
||||||
@ -76,6 +83,6 @@ class ContactListWidget extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
).expanded()
|
).expanded()
|
||||||
]),
|
]),
|
||||||
).paddingLTRB(8, 0, 8, 65);
|
).paddingLTRB(8, 0, 8, 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,13 @@ class EmptyContactListWidget extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.person_add_sharp,
|
Icons.person_add_sharp,
|
||||||
color: scale.primaryScale.border,
|
color: scale.primaryScale.subtleBorder,
|
||||||
size: 48,
|
size: 48,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
translate('contact_list.invite_people'),
|
translate('contact_list.invite_people'),
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
color: scale.primaryScale.border,
|
color: scale.primaryScale.subtleBorder,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -21,6 +21,41 @@ class PasteInviteDialog extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
PasteInviteDialogState createState() => PasteInviteDialogState();
|
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> {
|
class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
||||||
@ -240,16 +275,16 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
|||||||
return SizedBox(height: 400, child: waitingPage(context));
|
return SizedBox(height: 400, child: waitingPage(context));
|
||||||
}
|
}
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxHeight: 400),
|
constraints: const BoxConstraints(maxHeight: 400, maxWidth: 400),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
translate('paste_invite_dialog.paste_invite_here'),
|
translate('paste_invite_dialog.paste_invite_here'),
|
||||||
).paddingAll(8),
|
).paddingLTRB(0, 0, 0, 8),
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(maxHeight: 200),
|
constraints: const BoxConstraints(maxHeight: 200),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
@ -267,12 +302,13 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
|||||||
'---- END VEILIDCHAT CONTACT INVITE -----\n',
|
'---- END VEILIDCHAT CONTACT INVITE -----\n',
|
||||||
//labelText: translate('paste_invite_dialog.paste')
|
//labelText: translate('paste_invite_dialog.paste')
|
||||||
),
|
),
|
||||||
).paddingAll(8)),
|
)).paddingLTRB(0, 0, 0, 8),
|
||||||
if (_validatingPaste)
|
if (_validatingPaste)
|
||||||
Column(children: [
|
Column(children: [
|
||||||
Text(translate('paste_invite_dialog.validating')),
|
Text(translate('paste_invite_dialog.validating'))
|
||||||
|
.paddingLTRB(0, 0, 0, 8),
|
||||||
buildProgressIndicator(context),
|
buildProgressIndicator(context),
|
||||||
]),
|
]).paddingAll(16).toCenter(),
|
||||||
if (_validInvitation == null &&
|
if (_validInvitation == null &&
|
||||||
!_validatingPaste &&
|
!_validatingPaste &&
|
||||||
_pasteTextController.text.isNotEmpty)
|
_pasteTextController.text.isNotEmpty)
|
||||||
@ -282,10 +318,15 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
|||||||
]).paddingAll(16).toCenter(),
|
]).paddingAll(16).toCenter(),
|
||||||
if (_validInvitation != null && !_validatingPaste)
|
if (_validInvitation != null && !_validatingPaste)
|
||||||
Column(children: [
|
Column(children: [
|
||||||
ProfileWidget(
|
Container(
|
||||||
name: _validInvitation!.contactRequestPrivate.profile.name,
|
constraints: const BoxConstraints(maxHeight: 64),
|
||||||
title:
|
width: double.infinity,
|
||||||
_validInvitation!.contactRequestPrivate.profile.title),
|
child: ProfileWidget(
|
||||||
|
name: _validInvitation!
|
||||||
|
.contactRequestPrivate.profile.name,
|
||||||
|
title: _validInvitation!
|
||||||
|
.contactRequestPrivate.profile.title))
|
||||||
|
.paddingLTRB(0, 0, 0, 8),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
|
@ -24,26 +24,23 @@ class ProfileWidget extends ConsumerWidget {
|
|||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
|
|
||||||
return Container(
|
return DecoratedBox(
|
||||||
width: double.infinity,
|
|
||||||
decoration: ShapeDecoration(
|
decoration: ShapeDecoration(
|
||||||
color: scale.primaryScale.subtleBackground,
|
color: scale.primaryScale.subtleBorder,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
side: BorderSide(color: scale.primaryScale.border))),
|
side: BorderSide(
|
||||||
child: Row(children: [
|
width: 0, color: scale.primaryScale.subtleBorder))),
|
||||||
Column(mainAxisSize: MainAxisSize.min, children: [
|
child: Column(children: [
|
||||||
Text(name, style: textTheme.headlineSmall).paddingAll(8),
|
Text(
|
||||||
|
name,
|
||||||
|
style: textTheme.headlineSmall,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
).paddingAll(4),
|
||||||
if (title != null && title!.isNotEmpty)
|
if (title != null && title!.isNotEmpty)
|
||||||
Text(title!, style: textTheme.bodyMedium).paddingLTRB(8, 0, 8, 8),
|
Text(title!, style: textTheme.bodyMedium).paddingLTRB(4, 0, 4, 4),
|
||||||
]).expanded(),
|
]),
|
||||||
IconButton(
|
);
|
||||||
icon: const Icon(Icons.settings),
|
|
||||||
tooltip: translate('app_bar.settings_tooltip'),
|
|
||||||
onPressed: () async {
|
|
||||||
context.go('/home/settings');
|
|
||||||
})
|
|
||||||
])).paddingAll(8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
SendInviteDialogState createState() => SendInviteDialogState();
|
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> {
|
class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
|
||||||
@ -161,7 +181,7 @@ class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
|
|||||||
onSelected: _onNoneEncryptionSelected,
|
onSelected: _onNoneEncryptionSelected,
|
||||||
),
|
),
|
||||||
ChoiceChip(
|
ChoiceChip(
|
||||||
label: Text(translate('send_invite_dialog.numeric_pin')),
|
label: Text(translate('send_invite_dialog.pin')),
|
||||||
selected: _encryptionKeyType == EncryptionKeyType.pin,
|
selected: _encryptionKeyType == EncryptionKeyType.pin,
|
||||||
onSelected: _onPinEncryptionSelected,
|
onSelected: _onPinEncryptionSelected,
|
||||||
),
|
),
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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:go_router/go_router.dart';
|
||||||
|
|
||||||
import '../../entities/proto.dart' as proto;
|
import '../../entities/proto.dart' as proto;
|
||||||
import '../components/chat_component.dart';
|
import '../components/chat_component.dart';
|
||||||
import '../components/empty_chat_widget.dart';
|
import '../components/empty_chat_widget.dart';
|
||||||
|
import '../components/profile_widget.dart';
|
||||||
|
import '../entities/local_account.dart';
|
||||||
import '../providers/account.dart';
|
import '../providers/account.dart';
|
||||||
import '../providers/chat.dart';
|
import '../providers/chat.dart';
|
||||||
import '../providers/contact.dart';
|
import '../providers/contact.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 '../veilid_support/veilid_support.dart';
|
||||||
import 'main_pager/main_pager.dart';
|
import 'main_pager/main_pager.dart';
|
||||||
|
|
||||||
class HomePage extends ConsumerStatefulWidget {
|
class HomePage extends ConsumerStatefulWidget {
|
||||||
@ -72,17 +81,126 @@ class HomePageState extends ConsumerState<HomePage>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore: prefer_expression_function_bodies
|
||||||
|
Widget buildAccountList() {
|
||||||
|
return Column(children: [
|
||||||
|
Center(child: Text("Small Profile")),
|
||||||
|
Center(child: Text("Contact invitations")),
|
||||||
|
Center(child: Text("Contacts"))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildUnlockAccount(
|
||||||
|
BuildContext context,
|
||||||
|
IList<LocalAccount> localAccounts,
|
||||||
|
// ignore: prefer_expression_function_bodies
|
||||||
|
) {
|
||||||
|
return Center(child: Text("unlock account"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We have an active, unlocked, user login
|
||||||
|
Widget buildReadyAccount(
|
||||||
|
BuildContext context,
|
||||||
|
IList<LocalAccount> localAccounts,
|
||||||
|
TypedKey activeUserLogin,
|
||||||
|
proto.Account account) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
|
return Column(children: <Widget>[
|
||||||
|
Row(children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.settings),
|
||||||
|
color: scale.secondaryScale.text,
|
||||||
|
constraints: const BoxConstraints.expand(height: 64, width: 64),
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
scale.secondaryScale.subtleBorder),
|
||||||
|
shape: MaterialStateProperty.all(const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(16))))),
|
||||||
|
tooltip: translate('app_bar.settings_tooltip'),
|
||||||
|
onPressed: () async {
|
||||||
|
context.go('/home/settings');
|
||||||
|
}).paddingLTRB(0, 0, 8, 0),
|
||||||
|
ProfileWidget(name: account.profile.name, title: account.profile.title)
|
||||||
|
.expanded(),
|
||||||
|
]).paddingAll(8),
|
||||||
|
MainPager(
|
||||||
|
localAccounts: localAccounts,
|
||||||
|
activeUserLogin: activeUserLogin,
|
||||||
|
account: account)
|
||||||
|
.expanded()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildUserPanel() {
|
||||||
|
final localAccountsV = ref.watch(localAccountsProvider);
|
||||||
|
final loginsV = ref.watch(loginsProvider);
|
||||||
|
|
||||||
|
if (!localAccountsV.hasValue || !loginsV.hasValue) {
|
||||||
|
return waitingPage(context);
|
||||||
|
}
|
||||||
|
final localAccounts = localAccountsV.requireValue;
|
||||||
|
final logins = loginsV.requireValue;
|
||||||
|
|
||||||
|
final activeUserLogin = logins.activeUserLogin;
|
||||||
|
if (activeUserLogin == null) {
|
||||||
|
// If no logged in user is active, show the list of account
|
||||||
|
return buildAccountList();
|
||||||
|
}
|
||||||
|
final accountV = ref
|
||||||
|
.watch(fetchAccountProvider(accountMasterRecordKey: activeUserLogin));
|
||||||
|
if (!accountV.hasValue) {
|
||||||
|
return waitingPage(context);
|
||||||
|
}
|
||||||
|
final account = accountV.requireValue;
|
||||||
|
switch (account.status) {
|
||||||
|
case AccountInfoStatus.noAccount:
|
||||||
|
Future.delayed(0.ms, () async {
|
||||||
|
await showErrorModal(context, translate('home.missing_account_title'),
|
||||||
|
translate('home.missing_account_text'));
|
||||||
|
// Delete account
|
||||||
|
await ref
|
||||||
|
.read(localAccountsProvider.notifier)
|
||||||
|
.deleteLocalAccount(activeUserLogin);
|
||||||
|
// Switch to no active user login
|
||||||
|
await ref.read(loginsProvider.notifier).switchToAccount(null);
|
||||||
|
});
|
||||||
|
return waitingPage(context);
|
||||||
|
case AccountInfoStatus.accountInvalid:
|
||||||
|
Future.delayed(0.ms, () async {
|
||||||
|
await showErrorModal(context, translate('home.invalid_account_title'),
|
||||||
|
translate('home.invalid_account_text'));
|
||||||
|
// Delete account
|
||||||
|
await ref
|
||||||
|
.read(localAccountsProvider.notifier)
|
||||||
|
.deleteLocalAccount(activeUserLogin);
|
||||||
|
// Switch to no active user login
|
||||||
|
await ref.read(loginsProvider.notifier).switchToAccount(null);
|
||||||
|
});
|
||||||
|
return waitingPage(context);
|
||||||
|
case AccountInfoStatus.accountLocked:
|
||||||
|
// Show unlock widget
|
||||||
|
return buildUnlockAccount(context, localAccounts);
|
||||||
|
case AccountInfoStatus.accountReady:
|
||||||
|
return buildReadyAccount(
|
||||||
|
context,
|
||||||
|
localAccounts,
|
||||||
|
activeUserLogin,
|
||||||
|
account.account!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget buildPhone(BuildContext context) {
|
Widget buildPhone(BuildContext context) {
|
||||||
return const Material(
|
return Material(color: Colors.transparent, child: buildUserPanel());
|
||||||
color: Colors.transparent, elevation: 4, child: MainPager());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget buildTabletLeftPane(BuildContext context) {
|
Widget buildTabletLeftPane(BuildContext context) {
|
||||||
//
|
//
|
||||||
return const Material(
|
return Material(color: Colors.transparent, child: buildUserPanel());
|
||||||
color: Colors.transparent, elevation: 4, child: MainPager());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
@ -122,15 +240,21 @@ class HomePageState extends ConsumerState<HomePage>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ref.watch(windowControlProvider);
|
ref.watch(windowControlProvider);
|
||||||
|
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
|
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration:
|
||||||
|
BoxDecoration(color: scale.primaryScale.elementBackground),
|
||||||
child: responsiveVisibility(
|
child: responsiveVisibility(
|
||||||
context: context,
|
context: context,
|
||||||
phone: false,
|
phone: false,
|
||||||
)
|
)
|
||||||
? buildTablet(context)
|
? buildTablet(context)
|
||||||
: buildPhone(context),
|
: buildPhone(context),
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,29 @@
|
|||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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 '../../components/contact_invitation_list_widget.dart';
|
import '../../components/contact_invitation_list_widget.dart';
|
||||||
import '../../components/contact_list_widget.dart';
|
import '../../components/contact_list_widget.dart';
|
||||||
import '../../components/profile_widget.dart';
|
|
||||||
import '../../entities/local_account.dart';
|
import '../../entities/local_account.dart';
|
||||||
import '../../entities/proto.dart' as proto;
|
import '../../entities/proto.dart' as proto;
|
||||||
import '../../providers/account.dart';
|
|
||||||
import '../../providers/contact.dart';
|
import '../../providers/contact.dart';
|
||||||
import '../../providers/contact_invite.dart';
|
import '../../providers/contact_invite.dart';
|
||||||
import '../../providers/local_accounts.dart';
|
import '../../tools/theme_service.dart';
|
||||||
import '../../providers/logins.dart';
|
|
||||||
import '../../tools/tools.dart';
|
|
||||||
import '../../veilid_support/veilid_support.dart';
|
import '../../veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
class AccountPage extends ConsumerStatefulWidget {
|
class AccountPage extends ConsumerStatefulWidget {
|
||||||
const AccountPage({super.key});
|
const AccountPage({
|
||||||
|
required this.localAccounts,
|
||||||
|
required this.activeUserLogin,
|
||||||
|
required this.account,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IList<LocalAccount> localAccounts;
|
||||||
|
final TypedKey activeUserLogin;
|
||||||
|
final proto.Account account;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AccountPageState createState() => AccountPageState();
|
AccountPageState createState() => AccountPageState();
|
||||||
@ -27,12 +31,10 @@ class AccountPage extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class AccountPageState extends ConsumerState<AccountPage> {
|
class AccountPageState extends ConsumerState<AccountPage> {
|
||||||
final _unfocusNode = FocusNode();
|
final _unfocusNode = FocusNode();
|
||||||
TypedKey? _selectedAccount;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_selectedAccount = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -41,31 +43,13 @@ class AccountPageState extends ConsumerState<AccountPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget buildAccountList(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(children: [
|
final theme = Theme.of(context);
|
||||||
Center(child: Text("Small Profile")),
|
final textTheme = theme.textTheme;
|
||||||
Center(child: Text("Contact invitations")),
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
Center(child: Text("Contacts"))
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildUnlockAccount(
|
|
||||||
BuildContext context,
|
|
||||||
IList<LocalAccount> localAccounts,
|
|
||||||
// ignore: prefer_expression_function_bodies
|
|
||||||
) {
|
|
||||||
return Center(child: Text("unlock account"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// We have an active, unlocked, user login
|
|
||||||
Widget buildUserAccount(
|
|
||||||
BuildContext context,
|
|
||||||
IList<LocalAccount> localAccounts,
|
|
||||||
TypedKey activeUserLogin,
|
|
||||||
proto.Account account,
|
|
||||||
// ignore: prefer_expression_function_bodies
|
|
||||||
) {
|
|
||||||
final contactInvitationRecordList =
|
final contactInvitationRecordList =
|
||||||
ref.watch(fetchContactInvitationRecordsProvider).asData?.value ??
|
ref.watch(fetchContactInvitationRecordsProvider).asData?.value ??
|
||||||
const IListConst([]);
|
const IListConst([]);
|
||||||
@ -73,82 +57,30 @@ class AccountPageState extends ConsumerState<AccountPage> {
|
|||||||
const IListConst([]);
|
const IListConst([]);
|
||||||
|
|
||||||
return Column(children: <Widget>[
|
return Column(children: <Widget>[
|
||||||
ProfileWidget(name: account.profile.name, title: account.profile.title),
|
|
||||||
if (contactInvitationRecordList.isNotEmpty)
|
if (contactInvitationRecordList.isNotEmpty)
|
||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
title: Text(translate('account_page.contact_invitations')),
|
tilePadding: EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||||
|
backgroundColor: scale.primaryScale.subtleBorder,
|
||||||
|
collapsedBackgroundColor: scale.primaryScale.subtleBorder,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
collapsedShape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
translate('account_page.contact_invitations'),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: textTheme.titleMedium!
|
||||||
|
.copyWith(color: scale.primaryScale.subtleText),
|
||||||
|
),
|
||||||
initiallyExpanded: true,
|
initiallyExpanded: true,
|
||||||
children: [
|
children: [
|
||||||
ContactInvitationListWidget(
|
ContactInvitationListWidget(
|
||||||
contactInvitationRecordList: contactInvitationRecordList)
|
contactInvitationRecordList: contactInvitationRecordList)
|
||||||
],
|
],
|
||||||
),
|
).paddingLTRB(8, 0, 8, 8),
|
||||||
ContactListWidget(contactList: contactList).expanded(),
|
ContactListWidget(contactList: contactList).expanded(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
// ignore: prefer_expression_function_bodies
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final localAccountsV = ref.watch(localAccountsProvider);
|
|
||||||
final loginsV = ref.watch(loginsProvider);
|
|
||||||
|
|
||||||
if (!localAccountsV.hasValue || !loginsV.hasValue) {
|
|
||||||
return waitingPage(context);
|
|
||||||
}
|
|
||||||
final localAccounts = localAccountsV.requireValue;
|
|
||||||
final logins = loginsV.requireValue;
|
|
||||||
|
|
||||||
final activeUserLogin = logins.activeUserLogin;
|
|
||||||
if (activeUserLogin == null) {
|
|
||||||
// If no logged in user is active, show the list of account
|
|
||||||
return buildAccountList(context);
|
|
||||||
}
|
|
||||||
final accountV = ref
|
|
||||||
.watch(fetchAccountProvider(accountMasterRecordKey: activeUserLogin));
|
|
||||||
if (!accountV.hasValue) {
|
|
||||||
return waitingPage(context);
|
|
||||||
}
|
|
||||||
final account = accountV.requireValue;
|
|
||||||
switch (account.status) {
|
|
||||||
case AccountInfoStatus.noAccount:
|
|
||||||
Future.delayed(0.ms, () async {
|
|
||||||
await showErrorModal(
|
|
||||||
context,
|
|
||||||
translate('account_page.missing_account_title'),
|
|
||||||
translate('account_page.missing_account_text'));
|
|
||||||
// Delete account
|
|
||||||
await ref
|
|
||||||
.read(localAccountsProvider.notifier)
|
|
||||||
.deleteLocalAccount(activeUserLogin);
|
|
||||||
// Switch to no active user login
|
|
||||||
await ref.read(loginsProvider.notifier).switchToAccount(null);
|
|
||||||
});
|
|
||||||
return waitingPage(context);
|
|
||||||
case AccountInfoStatus.accountInvalid:
|
|
||||||
Future.delayed(0.ms, () async {
|
|
||||||
await showErrorModal(
|
|
||||||
context,
|
|
||||||
translate('account_page.invalid_account_title'),
|
|
||||||
translate('account_page.invalid_account_text'));
|
|
||||||
// Delete account
|
|
||||||
await ref
|
|
||||||
.read(localAccountsProvider.notifier)
|
|
||||||
.deleteLocalAccount(activeUserLogin);
|
|
||||||
// Switch to no active user login
|
|
||||||
await ref.read(loginsProvider.notifier).switchToAccount(null);
|
|
||||||
});
|
|
||||||
return waitingPage(context);
|
|
||||||
case AccountInfoStatus.accountLocked:
|
|
||||||
// Show unlock widget
|
|
||||||
return buildUnlockAccount(context, localAccounts);
|
|
||||||
case AccountInfoStatus.accountReady:
|
|
||||||
return buildUserAccount(
|
|
||||||
context,
|
|
||||||
localAccounts,
|
|
||||||
activeUserLogin,
|
|
||||||
account.account!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.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/services.dart';
|
||||||
@ -12,13 +13,25 @@ import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
|
|||||||
|
|
||||||
import '../../components/bottom_sheet_action_button.dart';
|
import '../../components/bottom_sheet_action_button.dart';
|
||||||
import '../../components/paste_invite_dialog.dart';
|
import '../../components/paste_invite_dialog.dart';
|
||||||
|
import '../../components/scan_invite_dialog.dart';
|
||||||
import '../../components/send_invite_dialog.dart';
|
import '../../components/send_invite_dialog.dart';
|
||||||
|
import '../../entities/local_account.dart';
|
||||||
|
import '../../entities/proto.dart' as proto;
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
|
import '../../veilid_support/veilid_support.dart';
|
||||||
import 'account_page.dart';
|
import 'account_page.dart';
|
||||||
import 'chats_page.dart';
|
import 'chats_page.dart';
|
||||||
|
|
||||||
class MainPager extends ConsumerStatefulWidget {
|
class MainPager extends ConsumerStatefulWidget {
|
||||||
const MainPager({super.key});
|
const MainPager(
|
||||||
|
{required this.localAccounts,
|
||||||
|
required this.activeUserLogin,
|
||||||
|
required this.account,
|
||||||
|
super.key});
|
||||||
|
|
||||||
|
final IList<LocalAccount> localAccounts;
|
||||||
|
final TypedKey activeUserLogin;
|
||||||
|
final proto.Account account;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MainPagerState createState() => MainPagerState();
|
MainPagerState createState() => MainPagerState();
|
||||||
@ -45,14 +58,10 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
Icons.person_add_sharp,
|
Icons.person_add_sharp,
|
||||||
Icons.add_comment_sharp,
|
Icons.add_comment_sharp,
|
||||||
];
|
];
|
||||||
final _labelList = <String>[
|
final _bottomLabelList = <String>[
|
||||||
translate('pager.account'),
|
translate('pager.account'),
|
||||||
translate('pager.chats'),
|
translate('pager.chats'),
|
||||||
];
|
];
|
||||||
final List<Widget> _bottomBarPages = [
|
|
||||||
const AccountPage(),
|
|
||||||
const ChatsPage(),
|
|
||||||
];
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@ -89,13 +98,13 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
|
|
||||||
BottomBarItem buildBottomBarItem(int index) {
|
BottomBarItem buildBottomBarItem(int index) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
return BottomBarItem(
|
return BottomBarItem(
|
||||||
title: Text(_labelList[index]),
|
title: Text(_bottomLabelList[index]),
|
||||||
icon: Icon(_selectedIconList[index],
|
icon: Icon(_selectedIconList[index], color: scale.primaryScale.text),
|
||||||
color: theme.colorScheme.onPrimaryContainer),
|
selectedIcon:
|
||||||
selectedIcon: Icon(_selectedIconList[index],
|
Icon(_selectedIconList[index], color: scale.primaryScale.text),
|
||||||
color: theme.colorScheme.onPrimaryContainer),
|
backgroundColor: scale.primaryScale.text,
|
||||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
|
||||||
//unSelectedColor: theme.colorScheme.primaryContainer,
|
//unSelectedColor: theme.colorScheme.primaryContainer,
|
||||||
//selectedColor: theme.colorScheme.primary,
|
//selectedColor: theme.colorScheme.primary,
|
||||||
//badge: const Text('9+'),
|
//badge: const Text('9+'),
|
||||||
@ -105,14 +114,14 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
|
|
||||||
List<BottomBarItem> _buildBottomBarItems() {
|
List<BottomBarItem> _buildBottomBarItems() {
|
||||||
final bottomBarItems = List<BottomBarItem>.empty(growable: true);
|
final bottomBarItems = List<BottomBarItem>.empty(growable: true);
|
||||||
for (var index = 0; index < _bottomBarPages.length; index++) {
|
for (var index = 0; index < _bottomLabelList.length; index++) {
|
||||||
final item = buildBottomBarItem(index);
|
final item = buildBottomBarItem(index);
|
||||||
bottomBarItems.add(item);
|
bottomBarItems.add(item);
|
||||||
}
|
}
|
||||||
return bottomBarItems;
|
return bottomBarItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendContactInvitationDialog(BuildContext context) async {
|
Future<void> scanContactInvitationDialog(BuildContext context) async {
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
@ -125,30 +134,10 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
top: 10,
|
top: 10,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'Send Contact Invite',
|
'Scan Contact Invite',
|
||||||
style: TextStyle(fontSize: 24),
|
style: TextStyle(fontSize: 24),
|
||||||
),
|
),
|
||||||
content: SendInviteDialog());
|
content: ScanInviteDialog());
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> pasteContactInvitationDialog(BuildContext context) async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
// ignore: prefer_expression_function_bodies
|
|
||||||
builder: (context) {
|
|
||||||
return const AlertDialog(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
|
||||||
),
|
|
||||||
contentPadding: EdgeInsets.only(
|
|
||||||
top: 10,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
'Paste Contact Invite',
|
|
||||||
style: TextStyle(fontSize: 24),
|
|
||||||
),
|
|
||||||
content: PasteInviteDialog());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +162,7 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
await sendContactInvitationDialog(context);
|
await SendInviteDialog.show(context);
|
||||||
},
|
},
|
||||||
iconSize: 64,
|
iconSize: 64,
|
||||||
icon: const Icon(Icons.contact_page)),
|
icon: const Icon(Icons.contact_page)),
|
||||||
@ -183,6 +172,7 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
//await scanContactInvitationDialog(context);
|
||||||
},
|
},
|
||||||
iconSize: 64,
|
iconSize: 64,
|
||||||
icon: const Icon(Icons.qr_code_scanner)),
|
icon: const Icon(Icons.qr_code_scanner)),
|
||||||
@ -192,7 +182,7 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
await pasteContactInvitationDialog(context);
|
await PasteInviteDialog.show(context);
|
||||||
},
|
},
|
||||||
iconSize: 64,
|
iconSize: 64,
|
||||||
icon: const Icon(Icons.paste)),
|
icon: const Icon(Icons.paste)),
|
||||||
@ -227,8 +217,8 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
extendBody: true,
|
//extendBody: true,
|
||||||
backgroundColor: scale.grayScale.subtleBackground,
|
backgroundColor: Colors.transparent,
|
||||||
body: NotificationListener<ScrollNotification>(
|
body: NotificationListener<ScrollNotification>(
|
||||||
onNotification: onScrollNotification,
|
onNotification: onScrollNotification,
|
||||||
child: PageView(
|
child: PageView(
|
||||||
@ -239,9 +229,13 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
//physics: const NeverScrollableScrollPhysics(),
|
//physics: const NeverScrollableScrollPhysics(),
|
||||||
children: List.generate(
|
children: [
|
||||||
_bottomBarPages.length, (index) => _bottomBarPages[index]),
|
AccountPage(
|
||||||
)),
|
localAccounts: widget.localAccounts,
|
||||||
|
activeUserLogin: widget.activeUserLogin,
|
||||||
|
account: widget.account),
|
||||||
|
ChatsPage(),
|
||||||
|
])),
|
||||||
// appBar: AppBar(
|
// appBar: AppBar(
|
||||||
// toolbarHeight: 24,
|
// toolbarHeight: 24,
|
||||||
// title: Text(
|
// title: Text(
|
||||||
@ -250,7 +244,7 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
bottomNavigationBar: StylishBottomBar(
|
bottomNavigationBar: StylishBottomBar(
|
||||||
backgroundColor: theme.colorScheme.primaryContainer,
|
backgroundColor: scale.primaryScale.background,
|
||||||
// gradient: LinearGradient(
|
// gradient: LinearGradient(
|
||||||
// begin: Alignment.topCenter,
|
// begin: Alignment.topCenter,
|
||||||
// end: Alignment.bottomCenter,
|
// end: Alignment.bottomCenter,
|
||||||
@ -264,7 +258,7 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
//barAnimation: BarAnimation.fade,
|
//barAnimation: BarAnimation.fade,
|
||||||
iconStyle: IconStyle.animated,
|
iconStyle: IconStyle.animated,
|
||||||
inkEffect: true,
|
inkEffect: true,
|
||||||
inkColor: theme.colorScheme.primary,
|
inkColor: scale.primaryScale.hoverBackground,
|
||||||
//opacity: 0.3,
|
//opacity: 0.3,
|
||||||
),
|
),
|
||||||
items: _buildBottomBarItems(),
|
items: _buildBottomBarItems(),
|
||||||
@ -280,11 +274,11 @@ class MainPagerState extends ConsumerState<MainPager>
|
|||||||
floatingActionButton: BottomSheetActionButton(
|
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: scale.secondaryScale.text,
|
||||||
backgroundColor: theme.colorScheme.secondaryContainer,
|
backgroundColor: scale.secondaryScale.background,
|
||||||
builder: (context) => Icon(
|
builder: (context) => Icon(
|
||||||
_fabIconList[_currentPage],
|
_fabIconList[_currentPage],
|
||||||
color: theme.colorScheme.onSecondaryContainer,
|
color: scale.secondaryScale.text,
|
||||||
),
|
),
|
||||||
bottomSheetBuilder: _bottomSheetBuilder),
|
bottomSheetBuilder: _bottomSheetBuilder),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user