navigation cleanup

This commit is contained in:
Christien Rioux 2024-04-05 22:03:04 -04:00
parent 5da68b2d94
commit b3e9cbd4f3
32 changed files with 475 additions and 314 deletions

View File

@ -60,16 +60,16 @@
}, },
"accounts_menu": { "accounts_menu": {
"invite_contact": "Invite Contact", "invite_contact": "Invite Contact",
"create_invite": "Create Invite", "create_invite": "Create Invitation",
"scan_invite": "Scan Invite", "scan_invite": "Scan Invitation",
"paste_invite": "Paste Invite" "paste_invite": "Paste Invitation"
}, },
"send_invite_dialog": { "create_invitation_dialog": {
"title": "Send Contact Invite", "title": "Create Contact Invitation",
"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 Invitation",
"message": "Message", "message": "Message",
"unlocked": "Unlocked", "unlocked": "Unlocked",
"pin": "PIN", "pin": "PIN",
@ -85,23 +85,23 @@
"copy_invitation": "Copy Invitation", "copy_invitation": "Copy Invitation",
"invitation_copied": "Invitation Copied" "invitation_copied": "Invitation Copied"
}, },
"invite_dialog": { "invitation_dialog": {
"message_from_contact": "Message from contact", "message_from_contact": "Message from contact",
"validating": "Validating...", "validating": "Validating...",
"failed_to_accept": "Failed to accept contact invite", "failed_to_accept": "Failed to accept contact invitation",
"failed_to_reject": "Failed to reject contact invite", "failed_to_reject": "Failed to reject contact invitation",
"invalid_invitation": "Invalid invitation", "invalid_invitation": "Invalid invitation",
"protected_with_pin": "Contact invite is protected with a PIN", "protected_with_pin": "Contact invitation is protected with a PIN",
"protected_with_password": "Contact invite is protected with a password", "protected_with_password": "Contact invitation is protected with a password",
"invalid_pin": "Invalid PIN", "invalid_pin": "Invalid PIN",
"invalid_password": "Invalid password" "invalid_password": "Invalid password"
}, },
"paste_invite_dialog": { "paste_invitation_dialog": {
"title": "Paste Contact Invite", "title": "Paste Contact Invite",
"paste_invite_here": "Paste your contact invite here:", "paste_invite_here": "Paste your contact invite here:",
"paste": "Paste" "paste": "Paste"
}, },
"scan_invite_dialog": { "scan_invitation_dialog": {
"title": "Scan Contact Invite", "title": "Scan Contact Invite",
"instructions": "Position the contact invite QR code in the frame", "instructions": "Position the contact invite QR code in the frame",
"scan_qr_here": "Click here to scan a contact invite QR code:", "scan_qr_here": "Click here to scan a contact invite QR code:",

View File

@ -38,37 +38,37 @@ class VeilidChatApp extends StatelessWidget {
// Once init is done, we proceed with the app // Once init is done, we proceed with the app
final localizationDelegate = LocalizedApp.of(context).delegate; final localizationDelegate = LocalizedApp.of(context).delegate;
return ThemeProvider( return ThemeProvider(
initTheme: initialThemeData, initTheme: initialThemeData,
builder: (_, theme) => LocalizationProvider( builder: (_, theme) => LocalizationProvider(
state: LocalizationProvider.of(context).state, state: LocalizationProvider.of(context).state,
child: MultiBlocProvider( child: MultiBlocProvider(
providers: [ providers: [
BlocProvider<ConnectionStateCubit>( BlocProvider<ConnectionStateCubit>(
create: (context) => ConnectionStateCubit( create: (context) =>
ProcessorRepository.instance)), ConnectionStateCubit(ProcessorRepository.instance)),
BlocProvider<RouterCubit>( BlocProvider<RouterCubit>(
create: (context) => create: (context) =>
RouterCubit(AccountRepository.instance), RouterCubit(AccountRepository.instance),
), ),
BlocProvider<LocalAccountsCubit>( BlocProvider<LocalAccountsCubit>(
create: (context) => create: (context) =>
LocalAccountsCubit(AccountRepository.instance), LocalAccountsCubit(AccountRepository.instance),
), ),
BlocProvider<UserLoginsCubit>( BlocProvider<UserLoginsCubit>(
create: (context) => create: (context) =>
UserLoginsCubit(AccountRepository.instance), UserLoginsCubit(AccountRepository.instance),
), ),
BlocProvider<ActiveLocalAccountCubit>( BlocProvider<ActiveLocalAccountCubit>(
create: (context) => ActiveLocalAccountCubit( create: (context) =>
AccountRepository.instance), ActiveLocalAccountCubit(AccountRepository.instance),
), ),
BlocProvider<PreferencesCubit>( BlocProvider<PreferencesCubit>(
create: (context) => create: (context) =>
PreferencesCubit(PreferencesRepository.instance), PreferencesCubit(PreferencesRepository.instance),
) )
], ],
child: BackgroundTicker( child: BackgroundTicker(
builder: (context) => MaterialApp.router( builder: (context) => MaterialApp.router(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
routerConfig: context.watch<RouterCubit>().router(), routerConfig: context.watch<RouterCubit>().router(),
title: translate('app.title'), title: translate('app.title'),
@ -82,9 +82,9 @@ class VeilidChatApp extends StatelessWidget {
supportedLocales: supportedLocales:
localizationDelegate.supportedLocales, localizationDelegate.supportedLocales,
locale: localizationDelegate.currentLocale, locale: localizationDelegate.currentLocale,
), )),
)), )),
)); );
}); });
@override @override

View File

@ -135,7 +135,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Called when the local messages list gets a change // Called when the local messages list gets a change
void _updateLocalMessagesState( void _updateLocalMessagesState(
BlocBusyState<AsyncValue<IList<proto.Message>>> avmessages) { BlocBusyState<AsyncValue<IList<proto.Message>>> avmessages) {
final localMessages = avmessages.state.data?.value; final localMessages = avmessages.state.asData?.value;
if (localMessages == null) { if (localMessages == null) {
return; return;
} }
@ -147,7 +147,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Called when the remote messages list gets a change // Called when the remote messages list gets a change
void _updateRemoteMessagesState( void _updateRemoteMessagesState(
BlocBusyState<AsyncValue<IList<proto.Message>>> avmessages) { BlocBusyState<AsyncValue<IList<proto.Message>>> avmessages) {
final remoteMessages = avmessages.state.data?.value; final remoteMessages = avmessages.state.asData?.value;
if (remoteMessages == null) { if (remoteMessages == null) {
return; return;
} }

View File

@ -43,12 +43,12 @@ class ChatComponent extends StatelessWidget {
// Get all watched dependendies // Get all watched dependendies
final activeAccountInfo = context.watch<ActiveAccountInfo>(); final activeAccountInfo = context.watch<ActiveAccountInfo>();
final accountRecordInfo = final accountRecordInfo =
context.watch<AccountRecordCubit>().state.data?.value; context.watch<AccountRecordCubit>().state.asData?.value;
if (accountRecordInfo == null) { if (accountRecordInfo == null) {
return debugPage('should always have an account record here'); return debugPage('should always have an account record here');
} }
final contactList = final contactList =
context.watch<ContactListCubit>().state.state.data?.value; context.watch<ContactListCubit>().state.state.asData?.value;
if (contactList == null) { if (contactList == null) {
return debugPage('should always have a contact list here'); return debugPage('should always have a contact list here');
} }
@ -58,7 +58,7 @@ class ChatComponent extends StatelessWidget {
if (avconversation == null) { if (avconversation == null) {
return waitingPage(); return waitingPage();
} }
final conversation = avconversation.data?.value; final conversation = avconversation.asData?.value;
if (conversation == null) { if (conversation == null) {
return avconversation.buildNotData(); return avconversation.buildNotData();
} }
@ -140,7 +140,7 @@ class ChatComponent extends StatelessWidget {
final textTheme = Theme.of(context).textTheme; final textTheme = Theme.of(context).textTheme;
final chatTheme = makeChatTheme(scale, textTheme); final chatTheme = makeChatTheme(scale, textTheme);
final messages = _messagesState.data?.value; final messages = _messagesState.asData?.value;
if (messages == null) { if (messages == null) {
return _messagesState.buildNotData(); return _messagesState.buildNotData();
} }

View File

@ -80,7 +80,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
@override @override
Future<void> updateState(TypedKey key, proto.Chat value) async { Future<void> updateState(TypedKey key, proto.Chat value) async {
final contactList = _contactListCubit.state.state.data?.value; final contactList = _contactListCubit.state.state.asData?.value;
if (contactList == null) { if (contactList == null) {
await addState(key, const AsyncValue.loading()); await addState(key, const AsyncValue.loading());
return; return;

View File

@ -56,7 +56,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
Future<void> updateState( Future<void> updateState(
TypedKey key, AsyncValue<ActiveConversationState> value) async { TypedKey key, AsyncValue<ActiveConversationState> value) async {
// Get the contact object for this single contact chat // Get the contact object for this single contact chat
final contactList = _contactListCubit.state.state.data?.value; final contactList = _contactListCubit.state.state.asData?.value;
if (contactList == null) { if (contactList == null) {
await addState(key, const AsyncValue.loading()); await addState(key, const AsyncValue.loading());
return; return;
@ -71,7 +71,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
final contact = contactList[contactIndex]; final contact = contactList[contactIndex];
// Get the chat object for this single contact chat // Get the chat object for this single contact chat
final chatList = _chatListCubit.state.state.data?.value; final chatList = _chatListCubit.state.state.asData?.value;
if (chatList == null) { if (chatList == null) {
await addState(key, const AsyncValue.loading()); await addState(key, const AsyncValue.loading());
return; return;

View File

@ -130,7 +130,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
/// StateMapFollowable ///////////////////////// /// StateMapFollowable /////////////////////////
@override @override
IMap<TypedKey, proto.Chat> getStateMap(ChatListCubitState state) { IMap<TypedKey, proto.Chat> getStateMap(ChatListCubitState state) {
final stateValue = state.state.data?.value; final stateValue = state.state.asData?.value;
if (stateValue == null) { if (stateValue == null) {
return IMap(); return IMap();
} }

View File

@ -245,7 +245,7 @@ class ContactInvitationListCubit
// inbox with our list of extant invitations // inbox with our list of extant invitations
// If we're chatting to ourselves, // If we're chatting to ourselves,
// we are validating an invitation we have created // we are validating an invitation we have created
final isSelf = state.state.data!.value.indexWhere((cir) => final isSelf = state.state.asData!.value.indexWhere((cir) =>
cir.contactRequestInbox.recordKey.toVeilid() == cir.contactRequestInbox.recordKey.toVeilid() ==
contactRequestInboxKey) != contactRequestInboxKey) !=
-1; -1;
@ -310,7 +310,7 @@ class ContactInvitationListCubit
@override @override
IMap<TypedKey, proto.ContactInvitationRecord> getStateMap( IMap<TypedKey, proto.ContactInvitationRecord> getStateMap(
ContactInvitiationListState state) { ContactInvitiationListState state) {
final stateValue = state.state.data?.value; final stateValue = state.state.asData?.value;
if (stateValue == null) { if (stateValue == null) {
return IMap(); return IMap();
} }

View File

@ -82,7 +82,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
var retryCount = 20; var retryCount = 20;
do { do {
await conversation.refresh(); await conversation.refresh();
remoteConversation = conversation.state.data?.value.remoteConversation; remoteConversation = conversation.state.asData?.value.remoteConversation;
if (remoteConversation != null) { if (remoteConversation != null) {
break; break;
} }

View File

@ -5,47 +5,29 @@ import 'package:basic_utils/basic_utils.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/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import 'package:provider/provider.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../tools/tools.dart'; import '../../tools/tools.dart';
import '../contact_invitation.dart'; import '../contact_invitation.dart';
class ContactInvitationDisplayDialog extends StatefulWidget { class ContactInvitationDisplayDialog extends StatelessWidget {
const ContactInvitationDisplayDialog({ const ContactInvitationDisplayDialog._({
required this.modalContext,
required this.message, required this.message,
super.key,
}); });
final BuildContext modalContext;
final String message; final String message;
@override
State<ContactInvitationDisplayDialog> createState() =>
_ContactInvitationDisplayDialogState();
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(StringProperty('message', message)); properties
} ..add(StringProperty('message', message))
} ..add(DiagnosticsProperty<BuildContext>('modalContext', modalContext));
class _ContactInvitationDisplayDialogState
extends State<ContactInvitationDisplayDialog> {
final focusNode = FocusNode();
final formKey = GlobalKey<FormState>();
@override
void initState() {
super.initState();
}
@override
void dispose() {
focusNode.dispose();
super.dispose();
} }
String makeTextInvite(String message, Uint8List data) { String makeTextInvite(String message, Uint8List data) {
@ -72,61 +54,67 @@ class _ContactInvitationDisplayDialogState
final cardsize = final cardsize =
min<double>(MediaQuery.of(context).size.shortestSide - 48.0, 400); min<double>(MediaQuery.of(context).size.shortestSide - 48.0, 400);
return Dialog( return PopControl(
backgroundColor: Colors.white, dismissible: !signedContactInvitationBytesV.isLoading,
child: ConstrainedBox( child: Dialog(
constraints: BoxConstraints( backgroundColor: Colors.white,
minWidth: cardsize, child: ConstrainedBox(
maxWidth: cardsize, constraints: BoxConstraints(
minHeight: cardsize, minWidth: cardsize,
maxHeight: cardsize), maxWidth: cardsize,
child: signedContactInvitationBytesV.when( minHeight: cardsize,
loading: buildProgressIndicator, maxHeight: cardsize),
data: (data) => Form( child: signedContactInvitationBytesV.when(
key: formKey, loading: buildProgressIndicator,
child: Column(children: [ data: (data) => Column(children: [
FittedBox( FittedBox(
child: Text( child: Text(
translate(
'create_invitation_dialog.contact_invitation'),
style: textTheme.headlineSmall!
.copyWith(color: Colors.black)))
.paddingAll(8),
FittedBox(
child: QrImageView.withQr(
size: 300,
qr: QrCode.fromUint8List(
data: data,
errorCorrectLevel:
QrErrorCorrectLevel.L)))
.expanded(),
Text(message,
softWrap: true,
style: textTheme.labelLarge!
.copyWith(color: Colors.black))
.paddingAll(8),
ElevatedButton.icon(
icon: const Icon(Icons.copy),
label: Text(translate(
'create_invitation_dialog.copy_invitation')),
onPressed: () async {
showInfoToast(
context,
translate( translate(
'send_invite_dialog.contact_invitation'), 'create_invitation_dialog.invitation_copied'));
style: textTheme.headlineSmall! await Clipboard.setData(ClipboardData(
.copyWith(color: Colors.black))) text: makeTextInvite(message, data)));
.paddingAll(8), },
FittedBox( ).paddingAll(16),
child: QrImageView.withQr( ]),
size: 300, error: errorPage))));
qr: QrCode.fromUint8List(
data: data,
errorCorrectLevel:
QrErrorCorrectLevel.L)))
.expanded(),
Text(widget.message,
softWrap: true,
style: textTheme.labelLarge!
.copyWith(color: Colors.black))
.paddingAll(8),
ElevatedButton.icon(
icon: const Icon(Icons.copy),
label: Text(
translate('send_invite_dialog.copy_invitation')),
onPressed: () async {
showInfoToast(
context,
translate(
'send_invite_dialog.invitation_copied'));
await Clipboard.setData(ClipboardData(
text: makeTextInvite(widget.message, data)));
},
).paddingAll(16),
])),
error: errorPage)));
} }
@override static Future<void> show(
void debugFillProperties(DiagnosticPropertiesBuilder properties) { {required BuildContext context,
super.debugFillProperties(properties); required InvitationGeneratorCubit Function(BuildContext) create,
properties required String message}) async {
..add(DiagnosticsProperty<FocusNode>('focusNode', focusNode)) await showPopControlDialog<void>(
..add(DiagnosticsProperty<GlobalKey<FormState>>('formKey', formKey)); context: context,
builder: (context) => BlocProvider(
create: create,
child: ContactInvitationDisplayDialog._(
modalContext: context,
message: message,
)));
} }
} }

View File

@ -105,15 +105,12 @@ class ContactInvitationItemWidget extends StatelessWidget {
if (!context.mounted) { if (!context.mounted) {
return; return;
} }
await showDialog<void>( await ContactInvitationDisplayDialog.show(
context: context, context: context,
builder: (context) => BlocProvider( message: contactInvitationRecord.message,
create: (context) => InvitationGeneratorCubit create: (context) => InvitationGeneratorCubit.value(
.value(Uint8List.fromList( Uint8List.fromList(
contactInvitationRecord.invitation)), contactInvitationRecord.invitation)));
child: ContactInvitationDisplayDialog(
message: contactInvitationRecord.message,
)));
}, },
title: Text( title: Text(
contactInvitationRecord.message.isEmpty contactInvitationRecord.message.isEmpty

View File

@ -13,17 +13,17 @@ import '../../account_manager/account_manager.dart';
import '../../tools/tools.dart'; import '../../tools/tools.dart';
import '../contact_invitation.dart'; import '../contact_invitation.dart';
class SendInviteDialog extends StatefulWidget { class CreateInvitationDialog extends StatefulWidget {
const SendInviteDialog({required this.modalContext, super.key}); const CreateInvitationDialog._({required this.modalContext});
@override @override
SendInviteDialogState createState() => SendInviteDialogState(); CreateInvitationDialogState createState() => CreateInvitationDialogState();
static Future<void> show(BuildContext context) async { static Future<void> show(BuildContext context) async {
await showStyledDialog<void>( await StyledDialog.show<void>(
context: context, context: context,
title: translate('send_invite_dialog.title'), title: translate('create_invitation_dialog.title'),
child: SendInviteDialog(modalContext: context)); child: CreateInvitationDialog._(modalContext: context));
} }
final BuildContext modalContext; final BuildContext modalContext;
@ -36,9 +36,9 @@ class SendInviteDialog extends StatefulWidget {
} }
} }
class SendInviteDialogState extends State<SendInviteDialog> { class CreateInvitationDialogState extends State<CreateInvitationDialog> {
final _messageTextController = TextEditingController( final _messageTextController = TextEditingController(
text: translate('send_invite_dialog.connect_with_me')); text: translate('create_invitation_dialog.connect_with_me'));
EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none; EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none;
String _encryptionKey = ''; String _encryptionKey = '';
@ -58,7 +58,7 @@ class SendInviteDialogState extends State<SendInviteDialog> {
} }
Future<void> _onPinEncryptionSelected(bool selected) async { Future<void> _onPinEncryptionSelected(bool selected) async {
final description = translate('send_invite_dialog.pin_description'); final description = translate('create_invitation_dialog.pin_description');
final pin = await showDialog<String>( final pin = await showDialog<String>(
context: context, context: context,
builder: (context) => builder: (context) =>
@ -87,7 +87,7 @@ class SendInviteDialogState extends State<SendInviteDialog> {
return; return;
} }
showErrorToast( showErrorToast(
context, translate('send_invite_dialog.pin_does_not_match')); context, translate('create_invitation_dialog.pin_does_not_match'));
setState(() { setState(() {
_encryptionKeyType = EncryptionKeyType.none; _encryptionKeyType = EncryptionKeyType.none;
_encryptionKey = ''; _encryptionKey = '';
@ -96,7 +96,8 @@ class SendInviteDialogState extends State<SendInviteDialog> {
} }
Future<void> _onPasswordEncryptionSelected(bool selected) async { Future<void> _onPasswordEncryptionSelected(bool selected) async {
final description = translate('send_invite_dialog.password_description'); final description =
translate('create_invitation_dialog.password_description');
final password = await showDialog<String>( final password = await showDialog<String>(
context: context, context: context,
builder: (context) => EnterPasswordDialog(description: description)); builder: (context) => EnterPasswordDialog(description: description));
@ -123,8 +124,8 @@ class SendInviteDialogState extends State<SendInviteDialog> {
if (!mounted) { if (!mounted) {
return; return;
} }
showErrorToast( showErrorToast(context,
context, translate('send_invite_dialog.password_does_not_match')); translate('create_invitation_dialog.password_does_not_match'));
setState(() { setState(() {
_encryptionKeyType = EncryptionKeyType.none; _encryptionKeyType = EncryptionKeyType.none;
_encryptionKey = ''; _encryptionKey = '';
@ -145,13 +146,10 @@ class SendInviteDialogState extends State<SendInviteDialog> {
message: _messageTextController.text, message: _messageTextController.text,
expiration: _expiration); expiration: _expiration);
await showDialog<void>( await ContactInvitationDisplayDialog.show(
context: context, context: context,
builder: (context) => BlocProvider( message: _messageTextController.text,
create: (context) => InvitationGeneratorCubit(generator), create: (context) => InvitationGeneratorCubit(generator));
child: ContactInvitationDisplayDialog(
message: _messageTextController.text,
)));
navigator.pop(); navigator.pop();
} }
@ -176,7 +174,7 @@ class SendInviteDialogState extends State<SendInviteDialog> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text( Text(
translate('send_invite_dialog.message_to_contact'), translate('create_invitation_dialog.message_to_contact'),
).paddingAll(8), ).paddingAll(8),
TextField( TextField(
controller: _messageTextController, controller: _messageTextController,
@ -185,26 +183,27 @@ class SendInviteDialogState extends State<SendInviteDialog> {
], ],
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
hintText: translate('send_invite_dialog.enter_message_hint'), hintText:
labelText: translate('send_invite_dialog.message')), translate('create_invitation_dialog.enter_message_hint'),
labelText: translate('create_invitation_dialog.message')),
).paddingAll(8), ).paddingAll(8),
const SizedBox(height: 10), const SizedBox(height: 10),
Text(translate('send_invite_dialog.protect_this_invitation'), Text(translate('create_invitation_dialog.protect_this_invitation'),
style: textTheme.labelLarge) style: textTheme.labelLarge)
.paddingAll(8), .paddingAll(8),
Wrap(spacing: 5, children: [ Wrap(spacing: 5, children: [
ChoiceChip( ChoiceChip(
label: Text(translate('send_invite_dialog.unlocked')), label: Text(translate('create_invitation_dialog.unlocked')),
selected: _encryptionKeyType == EncryptionKeyType.none, selected: _encryptionKeyType == EncryptionKeyType.none,
onSelected: _onNoneEncryptionSelected, onSelected: _onNoneEncryptionSelected,
), ),
ChoiceChip( ChoiceChip(
label: Text(translate('send_invite_dialog.pin')), label: Text(translate('create_invitation_dialog.pin')),
selected: _encryptionKeyType == EncryptionKeyType.pin, selected: _encryptionKeyType == EncryptionKeyType.pin,
onSelected: _onPinEncryptionSelected, onSelected: _onPinEncryptionSelected,
), ),
ChoiceChip( ChoiceChip(
label: Text(translate('send_invite_dialog.password')), label: Text(translate('create_invitation_dialog.password')),
selected: _encryptionKeyType == EncryptionKeyType.password, selected: _encryptionKeyType == EncryptionKeyType.password,
onSelected: _onPasswordEncryptionSelected, onSelected: _onPasswordEncryptionSelected,
) )
@ -216,13 +215,13 @@ class SendInviteDialogState extends State<SendInviteDialog> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _onGenerateButtonPressed, onPressed: _onGenerateButtonPressed,
child: Text( child: Text(
translate('send_invite_dialog.generate'), translate('create_invitation_dialog.generate'),
), ),
), ),
), ),
Text(translate('send_invite_dialog.note')).paddingAll(8), Text(translate('create_invitation_dialog.note')).paddingAll(8),
Text( Text(
translate('send_invite_dialog.note_text'), translate('create_invitation_dialog.note_text'),
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
).paddingAll(8), ).paddingAll(8),
], ],

View File

@ -11,8 +11,8 @@ import '../../contacts/contacts.dart';
import '../../tools/tools.dart'; import '../../tools/tools.dart';
import '../contact_invitation.dart'; import '../contact_invitation.dart';
class InviteDialog extends StatefulWidget { class InvitationDialog extends StatefulWidget {
const InviteDialog( const InvitationDialog(
{required this.modalContext, {required this.modalContext,
required this.onValidationCancelled, required this.onValidationCancelled,
required this.onValidationSuccess, required this.onValidationSuccess,
@ -27,13 +27,13 @@ class InviteDialog extends StatefulWidget {
final bool Function() inviteControlIsValid; final bool Function() inviteControlIsValid;
final Widget Function( final Widget Function(
BuildContext context, BuildContext context,
InviteDialogState dialogState, InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData}) Future<void> Function({required Uint8List inviteData})
validateInviteData) buildInviteControl; validateInviteData) buildInviteControl;
final BuildContext modalContext; final BuildContext modalContext;
@override @override
InviteDialogState createState() => InviteDialogState(); InvitationDialogState createState() => InvitationDialogState();
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
@ -49,7 +49,7 @@ class InviteDialog extends StatefulWidget {
..add(ObjectFlagProperty< ..add(ObjectFlagProperty<
Widget Function( Widget Function(
BuildContext context, BuildContext context,
InviteDialogState dialogState, InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData}) Future<void> Function({required Uint8List inviteData})
validateInviteData)>.has( validateInviteData)>.has(
'buildInviteControl', buildInviteControl)) 'buildInviteControl', buildInviteControl))
@ -57,7 +57,7 @@ class InviteDialog extends StatefulWidget {
} }
} }
class InviteDialogState extends State<InviteDialog> { class InvitationDialogState extends State<InvitationDialog> {
ValidContactInvitation? _validInvitation; ValidContactInvitation? _validInvitation;
bool _isValidating = false; bool _isValidating = false;
bool _isAccepting = false; bool _isAccepting = false;
@ -98,8 +98,8 @@ class InviteDialogState extends State<InviteDialog> {
); );
} }
} else { } else {
if (context.mounted) { if (mounted) {
showErrorToast(context, 'invite_dialog.failed_to_accept'); showErrorToast(context, 'invitation_dialog.failed_to_accept');
} }
} }
} }
@ -120,8 +120,8 @@ class InviteDialogState extends State<InviteDialog> {
if (await validInvitation.reject()) { if (await validInvitation.reject()) {
// do nothing right now // do nothing right now
} else { } else {
if (context.mounted) { if (mounted) {
showErrorToast(context, 'invite_dialog.failed_to_reject'); showErrorToast(context, 'invitation_dialog.failed_to_reject');
} }
} }
} }
@ -153,8 +153,8 @@ class InviteDialogState extends State<InviteDialog> {
encryptionKey = ''; encryptionKey = '';
case EncryptionKeyType.pin: case EncryptionKeyType.pin:
final description = final description =
translate('invite_dialog.protected_with_pin'); translate('invitation_dialog.protected_with_pin');
if (!context.mounted) { if (!mounted) {
return null; return null;
} }
final pin = await showDialog<String>( final pin = await showDialog<String>(
@ -167,8 +167,8 @@ class InviteDialogState extends State<InviteDialog> {
encryptionKey = pin; encryptionKey = pin;
case EncryptionKeyType.password: case EncryptionKeyType.password:
final description = final description =
translate('invite_dialog.protected_with_password'); translate('invitation_dialog.protected_with_password');
if (!context.mounted) { if (!mounted) {
return null; return null;
} }
final password = await showDialog<String>( final password = await showDialog<String>(
@ -208,13 +208,13 @@ class InviteDialogState extends State<InviteDialog> {
String errorText; String errorText;
switch (e.type) { switch (e.type) {
case EncryptionKeyType.none: case EncryptionKeyType.none:
errorText = translate('invite_dialog.invalid_invitation'); errorText = translate('invitation_dialog.invalid_invitation');
case EncryptionKeyType.pin: case EncryptionKeyType.pin:
errorText = translate('invite_dialog.invalid_pin'); errorText = translate('invitation_dialog.invalid_pin');
case EncryptionKeyType.password: case EncryptionKeyType.password:
errorText = translate('invite_dialog.invalid_password'); errorText = translate('invitation_dialog.invalid_password');
} }
if (context.mounted) { if (mounted) {
showErrorToast(context, errorText); showErrorToast(context, errorText);
} }
setState(() { setState(() {
@ -259,7 +259,7 @@ class InviteDialogState extends State<InviteDialog> {
widget.buildInviteControl(context, this, _validateInviteData), widget.buildInviteControl(context, this, _validateInviteData),
if (_isValidating) if (_isValidating)
Column(children: [ Column(children: [
Text(translate('invite_dialog.validating')) Text(translate('invitation_dialog.validating'))
.paddingLTRB(0, 0, 0, 16), .paddingLTRB(0, 0, 0, 16),
buildProgressIndicator().paddingAll(16), buildProgressIndicator().paddingAll(16),
]).toCenter(), ]).toCenter(),
@ -267,7 +267,7 @@ class InviteDialogState extends State<InviteDialog> {
!_isValidating && !_isValidating &&
widget.inviteControlIsValid()) widget.inviteControlIsValid())
Column(children: [ Column(children: [
Text(translate('invite_dialog.invalid_invitation')), Text(translate('invitation_dialog.invalid_invitation')),
const Icon(Icons.error) const Icon(Icons.error)
]).paddingAll(16).toCenter(), ]).paddingAll(16).toCenter(),
if (_validInvitation != null && !_isValidating) if (_validInvitation != null && !_isValidating)

View File

@ -4,9 +4,9 @@ import 'package:flutter/services.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import '../../theme/theme.dart'; import '../../theme/theme.dart';
import 'paste_invite_dialog.dart'; import 'paste_invitation_dialog.dart';
import 'scan_invite_dialog.dart'; import 'scan_invitation_dialog.dart';
import 'send_invite_dialog.dart'; import 'create_invitation_dialog.dart';
Widget newContactInvitationBottomSheetBuilder( Widget newContactInvitationBottomSheetBuilder(
BuildContext sheetContext, BuildContext context) { BuildContext sheetContext, BuildContext context) {
@ -32,7 +32,7 @@ Widget newContactInvitationBottomSheetBuilder(
IconButton( IconButton(
onPressed: () async { onPressed: () async {
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
await SendInviteDialog.show(context); await CreateInvitationDialog.show(context);
}, },
iconSize: 64, iconSize: 64,
icon: const Icon(Icons.contact_page), icon: const Icon(Icons.contact_page),
@ -43,7 +43,7 @@ Widget newContactInvitationBottomSheetBuilder(
IconButton( IconButton(
onPressed: () async { onPressed: () async {
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
await ScanInviteDialog.show(context); await ScanInvitationDialog.show(context);
}, },
iconSize: 64, iconSize: 64,
icon: const Icon(Icons.qr_code_scanner), icon: const Icon(Icons.qr_code_scanner),
@ -54,7 +54,7 @@ Widget newContactInvitationBottomSheetBuilder(
IconButton( IconButton(
onPressed: () async { onPressed: () async {
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
await PasteInviteDialog.show(context); await PasteInvitationDialog.show(context);
}, },
iconSize: 64, iconSize: 64,
icon: const Icon(Icons.paste), icon: const Icon(Icons.paste),

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -9,19 +8,19 @@ import 'package:veilid_support/veilid_support.dart';
import '../../theme/theme.dart'; import '../../theme/theme.dart';
import '../../tools/tools.dart'; import '../../tools/tools.dart';
import 'invite_dialog.dart'; import 'invitation_dialog.dart';
class PasteInviteDialog extends StatefulWidget { class PasteInvitationDialog extends StatefulWidget {
const PasteInviteDialog({required this.modalContext, super.key}); const PasteInvitationDialog({required this.modalContext, super.key});
@override @override
PasteInviteDialogState createState() => PasteInviteDialogState(); PasteInvitationDialogState createState() => PasteInvitationDialogState();
static Future<void> show(BuildContext context) async { static Future<void> show(BuildContext context) async {
await showStyledDialog<void>( await StyledDialog.show<void>(
context: context, context: context,
title: translate('paste_invite_dialog.title'), title: translate('paste_invitation_dialog.title'),
child: PasteInviteDialog(modalContext: context)); child: PasteInvitationDialog(modalContext: context));
} }
final BuildContext modalContext; final BuildContext modalContext;
@ -34,7 +33,7 @@ class PasteInviteDialog extends StatefulWidget {
} }
} }
class PasteInviteDialogState extends State<PasteInviteDialog> { class PasteInvitationDialogState extends State<PasteInvitationDialog> {
final _pasteTextController = TextEditingController(); final _pasteTextController = TextEditingController();
@override @override
@ -89,7 +88,7 @@ class PasteInviteDialogState extends State<PasteInviteDialog> {
Widget buildInviteControl( Widget buildInviteControl(
BuildContext context, BuildContext context,
InviteDialogState dialogState, InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData}) Future<void> Function({required Uint8List inviteData})
validateInviteData) { validateInviteData) {
final theme = Theme.of(context); final theme = Theme.of(context);
@ -105,7 +104,7 @@ class PasteInviteDialogState extends State<PasteInviteDialog> {
return Column(mainAxisSize: MainAxisSize.min, children: [ return Column(mainAxisSize: MainAxisSize.min, children: [
Text( Text(
translate('paste_invite_dialog.paste_invite_here'), translate('paste_invitation_dialog.paste_invite_here'),
).paddingLTRB(0, 0, 0, 8), ).paddingLTRB(0, 0, 0, 8),
Container( Container(
constraints: const BoxConstraints(maxHeight: 200), constraints: const BoxConstraints(maxHeight: 200),
@ -122,7 +121,7 @@ class PasteInviteDialogState extends State<PasteInviteDialog> {
hintText: '--- BEGIN VEILIDCHAT CONTACT INVITE ----\n' hintText: '--- BEGIN VEILIDCHAT CONTACT INVITE ----\n'
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n' 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n'
'---- END VEILIDCHAT CONTACT INVITE -----\n', '---- END VEILIDCHAT CONTACT INVITE -----\n',
//labelText: translate('paste_invite_dialog.paste') //labelText: translate('paste_invitation_dialog.paste')
), ),
)).paddingLTRB(0, 0, 0, 8) )).paddingLTRB(0, 0, 0, 8)
]); ]);
@ -131,7 +130,7 @@ class PasteInviteDialogState extends State<PasteInviteDialog> {
@override @override
// ignore: prefer_expression_function_bodies // ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InviteDialog( return InvitationDialog(
modalContext: widget.modalContext, modalContext: widget.modalContext,
onValidationCancelled: onValidationCancelled, onValidationCancelled: onValidationCancelled,
onValidationSuccess: onValidationSuccess, onValidationSuccess: onValidationSuccess,

View File

@ -14,7 +14,7 @@ import 'package:zxing2/qrcode.dart';
import '../../theme/theme.dart'; import '../../theme/theme.dart';
import '../../tools/tools.dart'; import '../../tools/tools.dart';
import 'invite_dialog.dart'; import 'invitation_dialog.dart';
class BarcodeOverlay extends CustomPainter { class BarcodeOverlay extends CustomPainter {
BarcodeOverlay({ BarcodeOverlay({
@ -103,17 +103,17 @@ class ScannerOverlay extends CustomPainter {
bool shouldRepaint(covariant CustomPainter oldDelegate) => false; bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
} }
class ScanInviteDialog extends StatefulWidget { class ScanInvitationDialog extends StatefulWidget {
const ScanInviteDialog({required this.modalContext, super.key}); const ScanInvitationDialog({required this.modalContext, super.key});
@override @override
ScanInviteDialogState createState() => ScanInviteDialogState(); ScanInvitationDialogState createState() => ScanInvitationDialogState();
static Future<void> show(BuildContext context) async { static Future<void> show(BuildContext context) async {
await showStyledDialog<void>( await StyledDialog.show<void>(
context: context, context: context,
title: translate('scan_invite_dialog.title'), title: translate('scan_invitation_dialog.title'),
child: ScanInviteDialog(modalContext: context)); child: ScanInvitationDialog(modalContext: context));
} }
final BuildContext modalContext; final BuildContext modalContext;
@ -126,7 +126,7 @@ class ScanInviteDialog extends StatefulWidget {
} }
} }
class ScanInviteDialogState extends State<ScanInviteDialog> { class ScanInvitationDialogState extends State<ScanInvitationDialog> {
bool scanned = false; bool scanned = false;
@override @override
@ -221,7 +221,8 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
height: 50, height: 50,
child: FittedBox( child: FittedBox(
child: Text( child: Text(
translate('scan_invite_dialog.instructions'), translate(
'scan_invitation_dialog.instructions'),
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
@ -270,12 +271,12 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
} on MobileScannerException catch (e) { } on MobileScannerException catch (e) {
if (e.errorCode == MobileScannerErrorCode.permissionDenied) { if (e.errorCode == MobileScannerErrorCode.permissionDenied) {
showErrorToast( showErrorToast(
context, translate('scan_invite_dialog.permission_error')); context, translate('scan_invitation_dialog.permission_error'));
} else { } else {
showErrorToast(context, translate('scan_invite_dialog.error')); showErrorToast(context, translate('scan_invitation_dialog.error'));
} }
} on Exception catch (_) { } on Exception catch (_) {
showErrorToast(context, translate('scan_invite_dialog.error')); showErrorToast(context, translate('scan_invitation_dialog.error'));
} }
return null; return null;
@ -285,7 +286,8 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
final imageBytes = await Pasteboard.image; final imageBytes = await Pasteboard.image;
if (imageBytes == null) { if (imageBytes == null) {
if (context.mounted) { if (context.mounted) {
showErrorToast(context, translate('scan_invite_dialog.not_an_image')); showErrorToast(
context, translate('scan_invitation_dialog.not_an_image'));
} }
return null; return null;
} }
@ -293,8 +295,8 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
final image = img.decodeImage(imageBytes); final image = img.decodeImage(imageBytes);
if (image == null) { if (image == null) {
if (context.mounted) { if (context.mounted) {
showErrorToast( showErrorToast(context,
context, translate('scan_invite_dialog.could_not_decode_image')); translate('scan_invitation_dialog.could_not_decode_image'));
} }
return null; return null;
} }
@ -319,7 +321,7 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
} on Exception catch (_) { } on Exception catch (_) {
if (context.mounted) { if (context.mounted) {
showErrorToast( showErrorToast(
context, translate('scan_invite_dialog.not_a_valid_qr_code')); context, translate('scan_invitation_dialog.not_a_valid_qr_code'));
} }
return null; return null;
} }
@ -327,7 +329,7 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
Widget buildInviteControl( Widget buildInviteControl(
BuildContext context, BuildContext context,
InviteDialogState dialogState, InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData}) Future<void> Function({required Uint8List inviteData})
validateInviteData) { validateInviteData) {
//final theme = Theme.of(context); //final theme = Theme.of(context);
@ -339,7 +341,7 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
return Column(mainAxisSize: MainAxisSize.min, children: [ return Column(mainAxisSize: MainAxisSize.min, children: [
if (!scanned) if (!scanned)
Text( Text(
translate('scan_invite_dialog.scan_qr_here'), translate('scan_invitation_dialog.scan_qr_here'),
).paddingLTRB(0, 0, 0, 8), ).paddingLTRB(0, 0, 0, 8),
if (!scanned) if (!scanned)
Container( Container(
@ -356,14 +358,14 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
await validateInviteData(inviteData: inviteData); await validateInviteData(inviteData: inviteData);
} }
}, },
child: Text(translate('scan_invite_dialog.scan'))), child: Text(translate('scan_invitation_dialog.scan'))),
).paddingLTRB(0, 0, 0, 8) ).paddingLTRB(0, 0, 0, 8)
]); ]);
} }
return Column(mainAxisSize: MainAxisSize.min, children: [ return Column(mainAxisSize: MainAxisSize.min, children: [
if (!scanned) if (!scanned)
Text( Text(
translate('scan_invite_dialog.paste_qr_here'), translate('scan_invitation_dialog.paste_qr_here'),
).paddingLTRB(0, 0, 0, 8), ).paddingLTRB(0, 0, 0, 8),
if (!scanned) if (!scanned)
Container( Container(
@ -380,7 +382,7 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
}); });
} }
}, },
child: Text(translate('scan_invite_dialog.paste'))), child: Text(translate('scan_invitation_dialog.paste'))),
).paddingLTRB(0, 0, 0, 8) ).paddingLTRB(0, 0, 0, 8)
]); ]);
} }
@ -388,7 +390,7 @@ class ScanInviteDialogState extends State<ScanInviteDialog> {
@override @override
// ignore: prefer_expression_function_bodies // ignore: prefer_expression_function_bodies
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InviteDialog( return InvitationDialog(
modalContext: widget.modalContext, modalContext: widget.modalContext,
onValidationCancelled: onValidationCancelled, onValidationCancelled: onValidationCancelled,
onValidationSuccess: onValidationSuccess, onValidationSuccess: onValidationSuccess,

View File

@ -1,8 +1,8 @@
export 'contact_invitation_display.dart'; export 'contact_invitation_display.dart';
export 'contact_invitation_item_widget.dart'; export 'contact_invitation_item_widget.dart';
export 'contact_invitation_list_widget.dart'; export 'contact_invitation_list_widget.dart';
export 'invite_dialog.dart'; export 'create_invitation_dialog.dart';
export 'invitation_dialog.dart';
export 'new_contact_invitation_bottom_sheet.dart'; export 'new_contact_invitation_bottom_sheet.dart';
export 'paste_invite_dialog.dart'; export 'paste_invitation_dialog.dart';
export 'scan_invite_dialog.dart'; export 'scan_invitation_dialog.dart';
export 'send_invite_dialog.dart';

View File

@ -162,7 +162,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final deleteSet = DelayedWaitSet(); final deleteSet = DelayedWaitSet();
if (localConversationCubit != null) { if (localConversationCubit != null) {
final data = localConversationCubit.state.data; final data = localConversationCubit.state.asData;
if (data == null) { if (data == null) {
log.warning('could not delete local conversation'); log.warning('could not delete local conversation');
return false; return false;
@ -180,7 +180,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
} }
if (remoteConversationCubit != null) { if (remoteConversationCubit != null) {
final data = remoteConversationCubit.state.data; final data = remoteConversationCubit.state.asData;
if (data == null) { if (data == null) {
log.warning('could not delete remote conversation'); log.warning('could not delete remote conversation');
return false; return false;

View File

@ -75,7 +75,7 @@ class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
for (final entry in newState.entries) { for (final entry in newState.entries) {
final contactRequestInboxRecordKey = entry.key; final contactRequestInboxRecordKey = entry.key;
final invStatus = entry.value.data?.value; final invStatus = entry.value.asData?.value;
// Skip invitations that have not yet been accepted or rejected // Skip invitations that have not yet been accepted or rejected
if (invStatus == null) { if (invStatus == null) {
continue; continue;
@ -109,7 +109,7 @@ class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final account = context.watch<AccountRecordCubit>().state.data?.value; final account = context.watch<AccountRecordCubit>().state.asData?.value;
if (account == null) { if (account == null) {
return waitingPage(); return waitingPage();
} }

View File

@ -41,11 +41,11 @@ class AccountPageState extends State<AccountPage> {
final cilState = context.watch<ContactInvitationListCubit>().state; final cilState = context.watch<ContactInvitationListCubit>().state;
final cilBusy = cilState.busy; final cilBusy = cilState.busy;
final contactInvitationRecordList = final contactInvitationRecordList =
cilState.state.data?.value ?? const IListConst([]); cilState.state.asData?.value ?? const IListConst([]);
final ciState = context.watch<ContactListCubit>().state; final ciState = context.watch<ContactListCubit>().state;
final ciBusy = ciState.busy; final ciBusy = ciState.busy;
final contactList = ciState.state.data?.value ?? const IListConst([]); final contactList = ciState.state.asData?.value ?? const IListConst([]);
return SizedBox( return SizedBox(
child: Column(children: <Widget>[ child: Column(children: <Widget>[

View File

@ -121,7 +121,7 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
'Scan Contact Invite', 'Scan Contact Invite',
style: TextStyle(fontSize: 24), style: TextStyle(fontSize: 24),
), ),
content: ScanInviteDialog( content: ScanInvitationDialog(
modalContext: context, modalContext: context,
)); ));
}); });

View File

@ -23,7 +23,9 @@ class _SplashState extends State<Splash> {
} }
@override @override
Widget build(BuildContext context) => DecoratedBox( Widget build(BuildContext context) => PopScope(
canPop: false,
child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
@ -49,5 +51,5 @@ class _SplashState extends State<Splash> {
'assets/images/title.svg', 'assets/images/title.svg',
)) ))
]))), ]))),
); ));
} }

View File

@ -17,7 +17,7 @@ class EnterPasswordDialog extends StatefulWidget {
final String? description; final String? description;
@override @override
EnterPasswordDialogState createState() => EnterPasswordDialogState(); State<EnterPasswordDialog> createState() => _EnterPasswordDialogState();
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@ -28,7 +28,7 @@ class EnterPasswordDialog extends StatefulWidget {
} }
} }
class EnterPasswordDialogState extends State<EnterPasswordDialog> { class _EnterPasswordDialogState extends State<EnterPasswordDialog> {
final passwordController = TextEditingController(); final passwordController = TextEditingController();
final focusNode = FocusNode(); final focusNode = FocusNode();
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();

View File

@ -18,7 +18,7 @@ class EnterPinDialog extends StatefulWidget {
final String? description; final String? description;
@override @override
EnterPinDialogState createState() => EnterPinDialogState(); State<EnterPinDialog> createState() => _EnterPinDialogState();
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@ -29,7 +29,7 @@ class EnterPinDialog extends StatefulWidget {
} }
} }
class EnterPinDialogState extends State<EnterPinDialog> { class _EnterPinDialogState extends State<EnterPinDialog> {
final pinController = TextEditingController(); final pinController = TextEditingController();
final focusNode = FocusNode(); final focusNode = FocusNode();
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();

130
lib/tools/pop_control.dart Normal file
View File

@ -0,0 +1,130 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class PopControl extends StatelessWidget {
const PopControl({
required this.child,
required this.dismissible,
super.key,
});
void _doDismiss(NavigatorState navigator) {
if (!dismissible) {
return;
}
navigator.pop();
}
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
final navigator = Navigator.of(context);
final route = ModalRoute.of(context);
if (route != null && route is PopControlDialogRoute) {
route.barrierDismissible = dismissible;
}
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (didPop) {
return;
}
_doDismiss(navigator);
},
child: child);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('dismissible', dismissible));
}
final bool dismissible;
final Widget child;
}
class PopControlDialogRoute<T> extends DialogRoute<T> {
PopControlDialogRoute(
{required super.context,
required super.builder,
super.themes,
super.barrierColor = Colors.black54,
super.barrierDismissible,
super.barrierLabel,
super.useSafeArea,
super.settings,
super.anchorPoint,
super.traversalEdgeBehavior})
: _barrierDismissible = barrierDismissible;
@override
bool get barrierDismissible => _barrierDismissible;
set barrierDismissible(bool d) {
_barrierDismissible = d;
}
bool _barrierDismissible;
}
bool _debugIsActive(BuildContext context) {
if (context is Element && !context.debugIsActive) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('This BuildContext is no longer valid.'),
ErrorDescription(
'The showPopControlDialog function context parameter is a '
'BuildContext that is no longer valid.'),
ErrorHint(
'This can commonly occur when the showPopControlDialog function is '
'called after awaiting a Future. '
'In this situation the BuildContext might refer to a widget that has '
'already been disposed during the await. '
'Consider using a parent context instead.',
),
]);
}
return true;
}
Future<T?> showPopControlDialog<T>({
required BuildContext context,
required WidgetBuilder builder,
bool barrierDismissible = true,
Color? barrierColor,
String? barrierLabel,
bool useSafeArea = true,
bool useRootNavigator = true,
RouteSettings? routeSettings,
Offset? anchorPoint,
TraversalEdgeBehavior? traversalEdgeBehavior,
}) {
assert(_debugIsActive(context), 'debug is active check');
assert(debugCheckHasMaterialLocalizations(context),
'check has material localizations');
final themes = InheritedTheme.capture(
from: context,
to: Navigator.of(
context,
rootNavigator: useRootNavigator,
).context,
);
return Navigator.of(context, rootNavigator: useRootNavigator)
.push<T>(PopControlDialogRoute<T>(
context: context,
builder: builder,
barrierColor: barrierColor ?? Colors.black54,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
useSafeArea: useSafeArea,
settings: routeSettings,
themes: themes,
anchorPoint: anchorPoint,
traversalEdgeBehavior:
traversalEdgeBehavior ?? TraversalEdgeBehavior.closedLoop,
));
}

View File

@ -0,0 +1,58 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../theme/theme.dart';
class StyledDialog extends StatelessWidget {
const StyledDialog({required this.title, required this.child, super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final textTheme = theme.textTheme;
return AlertDialog(
elevation: 0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
contentPadding: const EdgeInsets.all(4),
backgroundColor: scale.primaryScale.border,
title: Text(
title,
style: textTheme.titleMedium,
textAlign: TextAlign.center,
),
titlePadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
content: DecoratedBox(
decoration: ShapeDecoration(
color: scale.primaryScale.border,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16))),
child: DecoratedBox(
decoration: ShapeDecoration(
color: scale.primaryScale.appBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12))),
child: child.paddingAll(0))));
}
static Future<T?> show<T>(
{required BuildContext context,
required String title,
required Widget child}) async =>
showDialog<T>(
context: context,
builder: (context) => StyledDialog(title: title, child: child));
final String title;
final Widget child;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('title', title));
}
}

View File

@ -3,10 +3,12 @@ export 'enter_password.dart';
export 'enter_pin.dart'; export 'enter_pin.dart';
export 'loggy.dart'; export 'loggy.dart';
export 'phono_byte.dart'; export 'phono_byte.dart';
export 'pop_control.dart';
export 'responsive.dart'; export 'responsive.dart';
export 'scanner_error_widget.dart'; export 'scanner_error_widget.dart';
export 'shared_preferences.dart'; export 'shared_preferences.dart';
export 'state_logger.dart'; export 'state_logger.dart';
export 'stream_listenable.dart'; export 'stream_listenable.dart';
export 'styled_dialog.dart';
export 'widget_helpers.dart'; export 'widget_helpers.dart';
export 'window_control.dart'; export 'window_control.dart';

View File

@ -164,42 +164,6 @@ Widget styledTitleContainer(
])); ]));
} }
Future<T?> showStyledDialog<T>(
{required BuildContext context,
required String title,
required Widget child}) async {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final textTheme = theme.textTheme;
return showDialog<T>(
context: context,
builder: (context) => AlertDialog(
elevation: 0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
contentPadding: const EdgeInsets.all(4),
backgroundColor: scale.primaryScale.border,
title: Text(
title,
style: textTheme.titleMedium,
textAlign: TextAlign.center,
),
titlePadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
content: DecoratedBox(
decoration: ShapeDecoration(
color: scale.primaryScale.border,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16))),
child: DecoratedBox(
decoration: ShapeDecoration(
color: scale.primaryScale.appBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12))),
child: child.paddingAll(0)))));
}
bool get isPlatformDark => bool get isPlatformDark =>
WidgetsBinding.instance.platformDispatcher.platformBrightness == WidgetsBinding.instance.platformDispatcher.platformBrightness ==
Brightness.dark; Brightness.dark;

View File

@ -35,7 +35,7 @@ part 'async_value.freezed.dart';
/// ``` /// ```
/// ///
/// If a consumer of an [AsyncValue] does not care about the loading/error /// If a consumer of an [AsyncValue] does not care about the loading/error
/// state, consider using [data] to read the state: /// state, consider using [asData] to read the state:
/// ///
/// ```dart /// ```dart
/// Widget build(BuildContext context, ScopedReader watch) { /// Widget build(BuildContext context, ScopedReader watch) {
@ -127,7 +127,7 @@ abstract class AsyncValue<T> with _$AsyncValue<T> {
/// The current data, or null if in loading/error. /// The current data, or null if in loading/error.
/// ///
/// This is safe to use, as Dart (will) have non-nullable types. /// This is safe to use, as Dart (will) have non-nullable types.
/// As such reading [data] still forces to handle the loading/error cases /// As such reading [asData] still forces to handle the loading/error cases
/// by having to check `data != null`. /// by having to check `data != null`.
/// ///
/// ## Why does [AsyncValue<T>.data] return [AsyncData<T>] instead of [T]? /// ## Why does [AsyncValue<T>.data] return [AsyncData<T>] instead of [T]?
@ -151,12 +151,32 @@ abstract class AsyncValue<T> with _$AsyncValue<T> {
/// print(configs.data); // null, currently loading /// print(configs.data); // null, currently loading
/// print(configs.data.value); // throws null exception /// print(configs.data.value); // throws null exception
/// ``` /// ```
AsyncData<T>? get data => map( AsyncData<T>? get asData => map(
data: (data) => data, data: (data) => data,
loading: (_) => null, loading: (_) => null,
error: (_) => null, error: (_) => null,
); );
bool get isData => asData != null;
/// Check if this is loading
AsyncLoading<T>? get asLoading => map(
data: (_) => null,
loading: (loading) => loading,
error: (_) => null,
);
bool get isLoading => asLoading != null;
/// Check if this is an error
AsyncError<T>? get asError => map(
data: (_) => null,
loading: (_) => null,
error: (e) => e,
);
bool get isError => asError != null;
/// Shorthand for [when] to handle only the `data` case. /// Shorthand for [when] to handle only the `data` case.
AsyncValue<R> whenData<R>(R Function(T value) cb) => when( AsyncValue<R> whenData<R>(R Function(T value) cb) => when(
data: (value) { data: (value) {

View File

@ -24,7 +24,7 @@ class AsyncTransformerCubit<T, S> extends Cubit<AsyncValue<T>> {
} else if (newState is AsyncError<S>) { } else if (newState is AsyncError<S>) {
emit(AsyncValue.error(newState.error, newState.stackTrace)); emit(AsyncValue.error(newState.error, newState.stackTrace));
} else { } else {
final transformedState = await transform(newState.data!.value); final transformedState = await transform(newState.asData!.value);
emit(transformedState); emit(transformedState);
} }
} on Exception catch (e, st) { } on Exception catch (e, st) {

View File

@ -179,10 +179,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.9.1" version: "8.9.2"
cached_network_image: cached_network_image:
dependency: transitive dependency: transitive
description: description:
@ -219,18 +219,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: camera_android name: camera_android
sha256: "1100e527b44a96906987a91ef78c8dacb539e34612a8058de89023380acf67f1" sha256: ae5b9a996dfb8d77b02031b67f5500873d6402f33bd6a5283e932eef08542a51
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.10.8+18" version: "0.10.9"
camera_avfoundation: camera_avfoundation:
dependency: transitive dependency: transitive
description: description:
name: camera_avfoundation name: camera_avfoundation
sha256: "8b113e43ee4434c9244c03c905432a0d5956cedaded3cd7381abaab89ce50297" sha256: "5d009ae48de1c8ab621b1c4496dadb6e2a83f3223b76c6e6a4a252414105f561"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.14+1" version: "0.9.15"
camera_platform_interface: camera_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -243,10 +243,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: camera_web name: camera_web
sha256: f18ccfb33b2a7c49a52ad5aa3f07330b7422faaecbdfd9b9fe8e51182f6ad67d sha256: "9e9aba2fbab77ce2472924196ff8ac4dd8f9126c4f9a3096171cd1d870d6b26c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.2+4" version: "0.3.3"
change_case: change_case:
dependency: "direct main" dependency: "direct main"
description: description:
@ -594,10 +594,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: freezed name: freezed
sha256: "57247f692f35f068cae297549a46a9a097100685c6780fe67177503eea5ed4e5" sha256: "24f77b50776d4285cc4b3a1665bb79852714c09b878363efbe64788c179c4284"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.7" version: "2.5.0"
freezed_annotation: freezed_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
@ -977,10 +977,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.4" version: "3.8.0"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -1270,10 +1270,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite name: sqflite
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 sha256: "5ce2e1a15e822c3b4bfb5400455775e421da7098eed8adc8f26298ada7c9308c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.3"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
@ -1318,10 +1318,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: stylish_bottom_bar name: stylish_bottom_bar
sha256: "54970e4753b4273239b6dea0d1175c56beabcf39b5c65df4cbf86f1b86568d2b" sha256: ca72557a5bd8f44caae9017eb3a73002e9189d7a9d2fac598fa55be13724f32b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.1.0"
synchronized: synchronized:
dependency: transitive dependency: transitive
description: description:
@ -1504,7 +1504,7 @@ packages:
path: "../veilid/veilid-flutter" path: "../veilid/veilid-flutter"
relative: true relative: true
source: path source: path
version: "0.3.0" version: "0.3.1"
veilid_support: veilid_support:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -76,7 +76,7 @@ dependencies:
split_view: ^3.2.1 split_view: ^3.2.1
stack_trace: ^1.11.1 stack_trace: ^1.11.1
stream_transform: ^2.1.0 stream_transform: ^2.1.0
stylish_bottom_bar: ^1.0.3 stylish_bottom_bar: ^1.1.0
uuid: ^4.3.3 uuid: ^4.3.3
veilid: veilid:
# veilid: ^0.0.1 # veilid: ^0.0.1
@ -89,7 +89,7 @@ dependencies:
dev_dependencies: dev_dependencies:
build_runner: ^2.4.9 build_runner: ^2.4.9
freezed: ^2.4.7 freezed: ^2.5.0
icons_launcher: ^2.1.7 icons_launcher: ^2.1.7
json_serializable: ^6.7.1 json_serializable: ^6.7.1
lint_hard: ^4.0.0 lint_hard: ^4.0.0