diff --git a/lib/account_manager/account_manager.dart b/lib/account_manager/account_manager.dart index f4b3f22..af728ac 100644 --- a/lib/account_manager/account_manager.dart +++ b/lib/account_manager/account_manager.dart @@ -1,3 +1,4 @@ export 'cubit/cubit.dart'; +export 'models/models.dart'; export 'repository/repository.dart'; export 'views/views.dart'; diff --git a/lib/chat/chat.dart b/lib/chat/chat.dart new file mode 100644 index 0000000..83d1303 --- /dev/null +++ b/lib/chat/chat.dart @@ -0,0 +1 @@ +export 'views/views.dart'; diff --git a/lib/chat/views/build_chat_component.dart b/lib/chat/views/build_chat_component.dart new file mode 100644 index 0000000..aa74ff2 --- /dev/null +++ b/lib/chat/views/build_chat_component.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +import '../../tools/tools.dart'; + +Widget buildChatComponent() { + // final contactList = ref.watch(fetchContactListProvider).asData?.value ?? + // const IListConst([]); + + // final activeChat = ref.watch(activeChatStateProvider); + // if (activeChat == null) { + // return const EmptyChatWidget(); + // } + + // final activeAccountInfo = + // ref.watch(fetchActiveAccountProvider).asData?.value; + // if (activeAccountInfo == null) { + // return const EmptyChatWidget(); + // } + + // final activeChatContactIdx = contactList.indexWhere( + // (c) => + // proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) == + // activeChat, + // ); + // if (activeChatContactIdx == -1) { + // ref.read(activeChatStateProvider.notifier).state = null; + // return const EmptyChatWidget(); + // } + // final activeChatContact = contactList[activeChatContactIdx]; + + // return ChatComponent( + // activeAccountInfo: activeAccountInfo, + // activeChat: activeChat, + // activeChatContact: activeChatContact); + // } + return Builder(builder: waitingPage); +} diff --git a/lib/old_to_refactor/components/chat_component.dart b/lib/chat/views/chat_component.dart similarity index 94% rename from lib/old_to_refactor/components/chat_component.dart rename to lib/chat/views/chat_component.dart index ff1b642..154f148 100644 --- a/lib/old_to_refactor/components/chat_component.dart +++ b/lib/chat/views/chat_component.dart @@ -7,13 +7,13 @@ import 'package:flutter_chat_types/flutter_chat_types.dart' as types; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../proto/proto.dart' as proto; -import '../providers/account.dart'; -import '../providers/chat.dart'; -import '../providers/conversation.dart'; -import '../tools/tools.dart'; -import '../veilid_init.dart'; -import '../veilid_support/veilid_support.dart'; +import '../../old_to_refactor/proto/proto.dart' as proto; +import '../../old_to_refactor/providers/account.dart'; +import '../../old_to_refactor/providers/chat.dart'; +import '../../old_to_refactor/providers/conversation.dart'; +import '../../old_to_refactor/tools/tools.dart'; +import '../../old_to_refactor/veilid_init.dart'; +import '../../old_to_refactor/veilid_support/veilid_support.dart'; class ChatComponent extends ConsumerStatefulWidget { const ChatComponent( diff --git a/lib/chat/views/empty_chat_widget.dart b/lib/chat/views/empty_chat_widget.dart new file mode 100644 index 0000000..a9072cd --- /dev/null +++ b/lib/chat/views/empty_chat_widget.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class EmptyChatWidget extends StatelessWidget { + const EmptyChatWidget({super.key}); + + @override + // ignore: prefer_expression_function_bodies + Widget build( + BuildContext context, + ) => + Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.chat, + color: Theme.of(context).disabledColor, + size: 48, + ), + Text( + 'Say Something', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).disabledColor, + ), + ), + ], + ), + ); +} diff --git a/lib/chat/views/no_conversation_widget.dart b/lib/chat/views/no_conversation_widget.dart new file mode 100644 index 0000000..4657966 --- /dev/null +++ b/lib/chat/views/no_conversation_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +//XXX should rename this +class NoContactWidget extends StatelessWidget { + const NoContactWidget({super.key}); + + @override + // ignore: prefer_expression_function_bodies + Widget build( + BuildContext context, + ) => + Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.emoji_people_outlined, + color: Theme.of(context).disabledColor, + size: 48, + ), + Text( + 'Choose A Conversation To Chat', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).disabledColor, + ), + ), + ], + ), + ); +} diff --git a/lib/chat/views/views.dart b/lib/chat/views/views.dart new file mode 100644 index 0000000..b63153b --- /dev/null +++ b/lib/chat/views/views.dart @@ -0,0 +1 @@ +export 'build_chat_component.dart'; diff --git a/lib/old_to_refactor/managers/contact_list_manager.dart b/lib/chat_list/chat_list.dart similarity index 100% rename from lib/old_to_refactor/managers/contact_list_manager.dart rename to lib/chat_list/chat_list.dart diff --git a/lib/old_to_refactor/components/chat_single_contact_item_widget.dart b/lib/chat_list/views/chat_single_contact_item_widget.dart similarity index 100% rename from lib/old_to_refactor/components/chat_single_contact_item_widget.dart rename to lib/chat_list/views/chat_single_contact_item_widget.dart diff --git a/lib/old_to_refactor/components/chat_single_contact_list_widget.dart b/lib/chat_list/views/chat_single_contact_list_widget.dart similarity index 100% rename from lib/old_to_refactor/components/chat_single_contact_list_widget.dart rename to lib/chat_list/views/chat_single_contact_list_widget.dart diff --git a/lib/old_to_refactor/components/empty_chat_list_widget.dart b/lib/chat_list/views/empty_chat_list_widget.dart similarity index 79% rename from lib/old_to_refactor/components/empty_chat_list_widget.dart rename to lib/chat_list/views/empty_chat_list_widget.dart index 3ef0f97..024cbf0 100644 --- a/lib/old_to_refactor/components/empty_chat_list_widget.dart +++ b/lib/chat_list/views/empty_chat_list_widget.dart @@ -1,15 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../tools/tools.dart'; +import '../../theme/theme.dart'; -class EmptyChatListWidget extends ConsumerWidget { +class EmptyChatListWidget extends StatelessWidget { const EmptyChatListWidget({super.key}); @override // ignore: prefer_expression_function_bodies - Widget build(BuildContext context, WidgetRef ref) { + Widget build(BuildContext context) { final theme = Theme.of(context); final textTheme = theme.textTheme; final scale = theme.extension()!; diff --git a/lib/chat_list/views/views.dart b/lib/chat_list/views/views.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/contact_invitation/contact_invitation.dart b/lib/contact_invitation/contact_invitation.dart new file mode 100644 index 0000000..83d1303 --- /dev/null +++ b/lib/contact_invitation/contact_invitation.dart @@ -0,0 +1 @@ +export 'views/views.dart'; diff --git a/lib/old_to_refactor/components/paste_invite_dialog.dart b/lib/contact_invitation/paste_invite_dialog.dart similarity index 96% rename from lib/old_to_refactor/components/paste_invite_dialog.dart rename to lib/contact_invitation/paste_invite_dialog.dart index b7e545c..a352892 100644 --- a/lib/old_to_refactor/components/paste_invite_dialog.dart +++ b/lib/contact_invitation/paste_invite_dialog.dart @@ -6,9 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../tools/tools.dart'; -import '../veilid_support/veilid_support.dart'; -import 'invite_dialog.dart'; +import '../old_to_refactor/tools/tools.dart'; +import '../old_to_refactor/veilid_support/veilid_support.dart'; +import 'views/invite_dialog.dart'; class PasteInviteDialog extends ConsumerStatefulWidget { const PasteInviteDialog({super.key}); diff --git a/lib/old_to_refactor/components/scan_invite_dialog.dart b/lib/contact_invitation/scan_invite_dialog.dart similarity index 99% rename from lib/old_to_refactor/components/scan_invite_dialog.dart rename to lib/contact_invitation/scan_invite_dialog.dart index a506bcf..c7b4f76 100644 --- a/lib/old_to_refactor/components/scan_invite_dialog.dart +++ b/lib/contact_invitation/scan_invite_dialog.dart @@ -13,8 +13,8 @@ import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:pasteboard/pasteboard.dart'; import 'package:zxing2/qrcode.dart'; -import '../tools/tools.dart'; -import 'invite_dialog.dart'; +import '../old_to_refactor/tools/tools.dart'; +import 'views/invite_dialog.dart'; class BarcodeOverlay extends CustomPainter { BarcodeOverlay({ diff --git a/lib/old_to_refactor/components/contact_invitation_display.dart b/lib/contact_invitation/views/contact_invitation_display.dart similarity index 95% rename from lib/old_to_refactor/components/contact_invitation_display.dart rename to lib/contact_invitation/views/contact_invitation_display.dart index 5f32ca8..dfd2ebf 100644 --- a/lib/old_to_refactor/components/contact_invitation_display.dart +++ b/lib/contact_invitation/views/contact_invitation_display.dart @@ -6,14 +6,13 @@ import 'package:basic_utils/basic_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:qr_flutter/qr_flutter.dart'; +import 'package:veilid_support/veilid_support.dart'; -import '../tools/tools.dart'; -import '../veilid_support/veilid_support.dart'; +import '../../tools/tools.dart'; -class ContactInvitationDisplayDialog extends ConsumerStatefulWidget { +class ContactInvitationDisplayDialog extends StatefulWidget { const ContactInvitationDisplayDialog({ required this.name, required this.message, @@ -40,7 +39,7 @@ class ContactInvitationDisplayDialog extends ConsumerStatefulWidget { } class ContactInvitationDisplayDialogState - extends ConsumerState { + extends State { final focusNode = FocusNode(); final formKey = GlobalKey(); late final AutoDisposeFutureProvider _generateFutureProvider; diff --git a/lib/old_to_refactor/components/contact_invitation_item_widget.dart b/lib/contact_invitation/views/contact_invitation_item_widget.dart similarity index 94% rename from lib/old_to_refactor/components/contact_invitation_item_widget.dart rename to lib/contact_invitation/views/contact_invitation_item_widget.dart index 1a967ba..5b5b9ef 100644 --- a/lib/old_to_refactor/components/contact_invitation_item_widget.dart +++ b/lib/contact_invitation/views/contact_invitation_item_widget.dart @@ -1,15 +1,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../proto/proto.dart' as proto; -import '../providers/account.dart'; -import '../providers/contact_invite.dart'; -import '../tools/tools.dart'; +import '../../proto/proto.dart' as proto; +import '../../theme/theme.dart'; import 'contact_invitation_display.dart'; -class ContactInvitationItemWidget extends ConsumerWidget { +class ContactInvitationItemWidget extends StatelessWidget { const ContactInvitationItemWidget( {required this.contactInvitationRecord, super.key}); @@ -24,7 +21,7 @@ class ContactInvitationItemWidget extends ConsumerWidget { @override // ignore: prefer_expression_function_bodies - Widget build(BuildContext context, WidgetRef ref) { + Widget build(BuildContext context) { final theme = Theme.of(context); //final textTheme = theme.textTheme; final scale = theme.extension()!; diff --git a/lib/old_to_refactor/components/contact_invitation_list_widget.dart b/lib/contact_invitation/views/contact_invitation_list_widget.dart similarity index 91% rename from lib/old_to_refactor/components/contact_invitation_list_widget.dart rename to lib/contact_invitation/views/contact_invitation_list_widget.dart index 372a1cc..e93f746 100644 --- a/lib/old_to_refactor/components/contact_invitation_list_widget.dart +++ b/lib/contact_invitation/views/contact_invitation_list_widget.dart @@ -2,13 +2,12 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../proto/proto.dart' as proto; -import '../tools/tools.dart'; +import '../../proto/proto.dart' as proto; +import '../../theme/theme.dart'; import 'contact_invitation_item_widget.dart'; -class ContactInvitationListWidget extends ConsumerStatefulWidget { +class ContactInvitationListWidget extends StatefulWidget { const ContactInvitationListWidget({ required this.contactInvitationRecordList, super.key, @@ -28,7 +27,7 @@ class ContactInvitationListWidget extends ConsumerStatefulWidget { } class ContactInvitationListWidgetState - extends ConsumerState { + extends State { final ScrollController _scrollController = ScrollController(); @override diff --git a/lib/old_to_refactor/components/invite_dialog.dart b/lib/contact_invitation/views/invite_dialog.dart similarity index 96% rename from lib/old_to_refactor/components/invite_dialog.dart rename to lib/contact_invitation/views/invite_dialog.dart index 870c6fe..dc38361 100644 --- a/lib/old_to_refactor/components/invite_dialog.dart +++ b/lib/contact_invitation/views/invite_dialog.dart @@ -3,19 +3,12 @@ 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 '../entities/local_account.dart'; -import '../providers/account.dart'; -import '../providers/contact.dart'; -import '../providers/contact_invite.dart'; -import '../tools/tools.dart'; -import 'enter_password.dart'; -import 'enter_pin.dart'; -import 'profile_widget.dart'; +import '../../account_manager/account_manager.dart'; +import '../../tools/tools.dart'; -class InviteDialog extends ConsumerStatefulWidget { +class InviteDialog extends StatefulWidget { const InviteDialog( {required this.onValidationCancelled, required this.onValidationSuccess, @@ -58,7 +51,7 @@ class InviteDialog extends ConsumerStatefulWidget { } } -class InviteDialogState extends ConsumerState { +class InviteDialogState extends State { ValidContactInvitation? _validInvitation; bool _isValidating = false; bool _isAccepting = false; diff --git a/lib/contact_invitation/views/new_contact_invitation_bottom_sheet.dart b/lib/contact_invitation/views/new_contact_invitation_bottom_sheet.dart new file mode 100644 index 0000000..9430146 --- /dev/null +++ b/lib/contact_invitation/views/new_contact_invitation_bottom_sheet.dart @@ -0,0 +1,65 @@ +import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_translate/flutter_translate.dart'; + +import '../../theme/theme.dart'; +import 'paste_invite_dialog.dart'; +import 'scan_invite_dialog.dart'; +import 'send_invite_dialog.dart'; + +Widget newContactInvitationBottomSheetBuilder(BuildContext context) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + final scale = theme.extension()!; + + return KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: (ke) { + if (ke.logicalKey == LogicalKeyboardKey.escape) { + Navigator.pop(context); + } + }, + child: SizedBox( + height: 200, + child: Column(children: [ + Text(translate('accounts_menu.invite_contact'), + style: textTheme.titleMedium) + .paddingAll(8), + Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + Column(children: [ + IconButton( + onPressed: () async { + Navigator.pop(context); + await SendInviteDialog.show(context); + }, + iconSize: 64, + icon: const Icon(Icons.contact_page), + color: scale.primaryScale.background), + Text(translate('accounts_menu.create_invite')) + ]), + Column(children: [ + IconButton( + onPressed: () async { + Navigator.pop(context); + await ScanInviteDialog.show(context); + }, + iconSize: 64, + icon: const Icon(Icons.qr_code_scanner), + color: scale.primaryScale.background), + Text(translate('accounts_menu.scan_invite')) + ]), + Column(children: [ + IconButton( + onPressed: () async { + Navigator.pop(context); + await PasteInviteDialog.show(context); + }, + iconSize: 64, + icon: const Icon(Icons.paste), + color: scale.primaryScale.background), + Text(translate('accounts_menu.paste_invite')) + ]) + ]).expanded() + ]))); +} diff --git a/lib/contact_invitation/views/paste_invite_dialog.dart b/lib/contact_invitation/views/paste_invite_dialog.dart new file mode 100644 index 0000000..545a48a --- /dev/null +++ b/lib/contact_invitation/views/paste_invite_dialog.dart @@ -0,0 +1,131 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; +import 'package:veilid_support/veilid_support.dart'; + +import '../../theme/theme.dart'; +import '../../tools/tools.dart'; +import 'invite_dialog.dart'; + +class PasteInviteDialog extends StatefulWidget { + const PasteInviteDialog({super.key}); + + @override + PasteInviteDialogState createState() => PasteInviteDialogState(); + + static Future show(BuildContext context) async { + await showStyledDialog( + context: context, + title: translate('paste_invite_dialog.title'), + child: const PasteInviteDialog()); + } +} + +class PasteInviteDialogState extends State { + final _pasteTextController = TextEditingController(); + + @override + void initState() { + super.initState(); + } + + Future _onPasteChanged( + String text, + Future Function({ + required Uint8List inviteData, + }) validateInviteData) async { + final lines = text.split('\n'); + if (lines.isEmpty) { + 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) { + return; + } + final inviteDataBase64 = lines + .sublist(firstline, lastline) + .join() + .replaceAll(RegExp(r'[^A-Za-z0-9\-_]'), ''); + final inviteData = base64UrlNoPadDecode(inviteDataBase64); + + await validateInviteData(inviteData: inviteData); + } + + void onValidationCancelled() { + _pasteTextController.clear(); + } + + void onValidationSuccess() { + //_pasteTextController.clear(); + } + + void onValidationFailed() { + _pasteTextController.clear(); + } + + bool inviteControlIsValid() => _pasteTextController.text.isNotEmpty; + + Widget buildInviteControl( + BuildContext context, + InviteDialogState dialogState, + Future Function({required Uint8List inviteData}) + validateInviteData) { + final theme = Theme.of(context); + final scale = theme.extension()!; + //final textTheme = theme.textTheme; + //final height = MediaQuery.of(context).size.height; + + final monoStyle = TextStyle( + fontFamily: 'Source Code Pro', + fontSize: 11, + color: scale.primaryScale.text, + ); + + return Column(mainAxisSize: MainAxisSize.min, children: [ + Text( + translate('paste_invite_dialog.paste_invite_here'), + ).paddingLTRB(0, 0, 0, 8), + Container( + constraints: const BoxConstraints(maxHeight: 200), + child: TextField( + enabled: !dialogState.isValidating, + onChanged: (text) async => + _onPasteChanged(text, validateInviteData), + style: monoStyle, + 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') + ), + )).paddingLTRB(0, 0, 0, 8) + ]); + } + + @override + // ignore: prefer_expression_function_bodies + Widget build(BuildContext context) { + return InviteDialog( + onValidationCancelled: onValidationCancelled, + onValidationSuccess: onValidationSuccess, + onValidationFailed: onValidationFailed, + inviteControlIsValid: inviteControlIsValid, + buildInviteControl: buildInviteControl); + } +} diff --git a/lib/contact_invitation/views/scan_invite_dialog.dart b/lib/contact_invitation/views/scan_invite_dialog.dart new file mode 100644 index 0000000..67c0999 --- /dev/null +++ b/lib/contact_invitation/views/scan_invite_dialog.dart @@ -0,0 +1,395 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_translate/flutter_translate.dart'; +import 'package:image/image.dart' as img; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:pasteboard/pasteboard.dart'; +import 'package:zxing2/qrcode.dart'; + +import '../../theme/theme.dart'; +import '../../tools/tools.dart'; +import 'invite_dialog.dart'; + +class BarcodeOverlay extends CustomPainter { + BarcodeOverlay({ + required this.barcode, + required this.arguments, + required this.boxFit, + required this.capture, + }); + + final BarcodeCapture capture; + final Barcode barcode; + final MobileScannerArguments arguments; + final BoxFit boxFit; + + @override + void paint(Canvas canvas, Size size) { + final adjustedSize = applyBoxFit(boxFit, arguments.size, size); + + var verticalPadding = size.height - adjustedSize.destination.height; + var horizontalPadding = size.width - adjustedSize.destination.width; + if (verticalPadding > 0) { + verticalPadding = verticalPadding / 2; + } else { + verticalPadding = 0; + } + + if (horizontalPadding > 0) { + horizontalPadding = horizontalPadding / 2; + } else { + horizontalPadding = 0; + } + + final ratioWidth = (Platform.isIOS ? capture.width : arguments.size.width) / + adjustedSize.destination.width; + final ratioHeight = + (Platform.isIOS ? capture.height : arguments.size.height) / + adjustedSize.destination.height; + + final adjustedOffset = []; + for (final offset in barcode.corners) { + adjustedOffset.add( + Offset( + offset.dx / ratioWidth + horizontalPadding, + offset.dy / ratioHeight + verticalPadding, + ), + ); + } + final cutoutPath = Path()..addPolygon(adjustedOffset, true); + + final backgroundPaint = Paint() + ..color = Colors.red.withOpacity(0.3) + ..style = PaintingStyle.fill + ..blendMode = BlendMode.dstOut; + + canvas.drawPath(cutoutPath, backgroundPaint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +class ScannerOverlay extends CustomPainter { + ScannerOverlay(this.scanWindow); + + final Rect scanWindow; + + @override + void paint(Canvas canvas, Size size) { + final backgroundPath = Path()..addRect(Rect.largest); + final cutoutPath = Path()..addRect(scanWindow); + + final backgroundPaint = Paint() + ..color = Colors.black.withOpacity(0.5) + ..style = PaintingStyle.fill + ..blendMode = BlendMode.dstOut; + + final backgroundWithCutout = Path.combine( + PathOperation.difference, + backgroundPath, + cutoutPath, + ); + canvas.drawPath(backgroundWithCutout, backgroundPaint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +class ScanInviteDialog extends StatefulWidget { + const ScanInviteDialog({super.key}); + + @override + ScanInviteDialogState createState() => ScanInviteDialogState(); + + static Future show(BuildContext context) async { + await showStyledDialog( + context: context, + title: translate('scan_invite_dialog.title'), + child: const ScanInviteDialog()); + } +} + +class ScanInviteDialogState extends State { + bool scanned = false; + + @override + void initState() { + super.initState(); + } + + void onValidationCancelled() { + setState(() { + scanned = false; + }); + } + + void onValidationSuccess() {} + void onValidationFailed() { + setState(() { + scanned = false; + }); + } + + bool inviteControlIsValid() => false; // _pasteTextController.text.isNotEmpty; + + Future scanQRImage(BuildContext context) async { + final theme = Theme.of(context); + //final textTheme = theme.textTheme; + final scale = theme.extension()!; + final windowSize = MediaQuery.of(context).size; + //final maxDialogWidth = min(windowSize.width - 64.0, 800.0 - 64.0); + //final maxDialogHeight = windowSize.height - 64.0; + + final scanWindow = Rect.fromCenter( + center: MediaQuery.of(context).size.center(Offset.zero), + width: 200, + height: 200, + ); + + final cameraController = MobileScannerController(); + try { + return showDialog( + context: context, + builder: (context) => Stack( + fit: StackFit.expand, + children: [ + MobileScanner( + fit: BoxFit.contain, + scanWindow: scanWindow, + controller: cameraController, + errorBuilder: (context, error, child) => + ScannerErrorWidget(error: error), + onDetect: (c) { + final barcode = c.barcodes.firstOrNull; + + final barcodeBytes = barcode?.rawBytes; + if (barcodeBytes != null) { + cameraController.dispose(); + Navigator.pop(context, barcodeBytes); + } + }), + CustomPaint( + painter: ScannerOverlay(scanWindow), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + alignment: Alignment.bottomCenter, + height: 100, + color: Colors.black.withOpacity(0.4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + color: Colors.white, + icon: ValueListenableBuilder( + valueListenable: cameraController.torchState, + builder: (context, state, child) { + switch (state) { + case TorchState.off: + return Icon(Icons.flash_off, + color: + scale.grayScale.subtleBackground); + case TorchState.on: + return Icon(Icons.flash_on, + color: scale.primaryScale.background); + } + }, + ), + iconSize: 32, + onPressed: cameraController.toggleTorch, + ), + SizedBox( + width: windowSize.width - 120, + height: 50, + child: FittedBox( + child: Text( + translate('scan_invite_dialog.instructions'), + overflow: TextOverflow.fade, + style: Theme.of(context) + .textTheme + .labelLarge! + .copyWith(color: Colors.white), + ), + ), + ), + IconButton( + color: Colors.white, + icon: ValueListenableBuilder( + valueListenable: + cameraController.cameraFacingState, + builder: (context, state, child) { + switch (state) { + case CameraFacing.front: + return const Icon(Icons.camera_front); + case CameraFacing.back: + return const Icon(Icons.camera_rear); + } + }, + ), + iconSize: 32, + onPressed: cameraController.switchCamera, + ), + ], + ), + ), + ), + Align( + alignment: Alignment.topRight, + child: IconButton( + color: Colors.white, + icon: Icon(Icons.close, + color: scale.grayScale.background), + iconSize: 32, + onPressed: () => { + SchedulerBinding.instance + .addPostFrameCallback((_) { + cameraController.dispose(); + Navigator.pop(context, null); + }) + })), + ], + )); + } on MobileScannerException catch (e) { + if (e.errorCode == MobileScannerErrorCode.permissionDenied) { + showErrorToast( + context, translate('scan_invite_dialog.permission_error')); + } else { + showErrorToast(context, translate('scan_invite_dialog.error')); + } + } on Exception catch (_) { + showErrorToast(context, translate('scan_invite_dialog.error')); + } + + return null; + } + + Future pasteQRImage(BuildContext context) async { + final imageBytes = await Pasteboard.image; + if (imageBytes == null) { + if (context.mounted) { + showErrorToast(context, translate('scan_invite_dialog.not_an_image')); + } + return null; + } + + final image = img.decodeImage(imageBytes); + if (image == null) { + if (context.mounted) { + showErrorToast( + context, translate('scan_invite_dialog.could_not_decode_image')); + } + return null; + } + + try { + final source = RGBLuminanceSource( + image.width, + image.height, + image + .convert(numChannels: 4) + .getBytes(order: img.ChannelOrder.abgr) + .buffer + .asInt32List()); + final bitmap = BinaryBitmap(HybridBinarizer(source)); + + final reader = QRCodeReader(); + final result = reader.decode(bitmap); + + final segs = result.resultMetadata[ResultMetadataType.byteSegments]! + as List; + return Uint8List.fromList(segs[0].toList()); + } on Exception catch (_) { + if (context.mounted) { + showErrorToast( + context, translate('scan_invite_dialog.not_a_valid_qr_code')); + } + return null; + } + } + + Widget buildInviteControl( + BuildContext context, + InviteDialogState dialogState, + Future Function({required Uint8List inviteData}) + validateInviteData) { + //final theme = Theme.of(context); + //final scale = theme.extension()!; + //final textTheme = theme.textTheme; + //final height = MediaQuery.of(context).size.height; + + if (isiOS || isAndroid) { + return Column(mainAxisSize: MainAxisSize.min, children: [ + if (!scanned) + Text( + translate('scan_invite_dialog.scan_qr_here'), + ).paddingLTRB(0, 0, 0, 8), + if (!scanned) + Container( + constraints: const BoxConstraints(maxHeight: 200), + child: ElevatedButton( + onPressed: dialogState.isValidating + ? null + : () async { + final inviteData = await scanQRImage(context); + if (inviteData != null) { + setState(() { + scanned = true; + }); + await validateInviteData(inviteData: inviteData); + } + }, + child: Text(translate('scan_invite_dialog.scan'))), + ).paddingLTRB(0, 0, 0, 8) + ]); + } + return Column(mainAxisSize: MainAxisSize.min, children: [ + if (!scanned) + Text( + translate('scan_invite_dialog.paste_qr_here'), + ).paddingLTRB(0, 0, 0, 8), + if (!scanned) + Container( + constraints: const BoxConstraints(maxHeight: 200), + child: ElevatedButton( + onPressed: dialogState.isValidating + ? null + : () async { + final inviteData = await pasteQRImage(context); + if (inviteData != null) { + await validateInviteData(inviteData: inviteData); + setState(() { + scanned = true; + }); + } + }, + child: Text(translate('scan_invite_dialog.paste'))), + ).paddingLTRB(0, 0, 0, 8) + ]); + } + + @override + // ignore: prefer_expression_function_bodies + Widget build(BuildContext context) { + return InviteDialog( + onValidationCancelled: onValidationCancelled, + onValidationSuccess: onValidationSuccess, + onValidationFailed: onValidationFailed, + inviteControlIsValid: inviteControlIsValid, + buildInviteControl: buildInviteControl); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('scanned', scanned)); + } +} diff --git a/lib/old_to_refactor/components/send_invite_dialog.dart b/lib/contact_invitation/views/send_invite_dialog.dart similarity index 93% rename from lib/old_to_refactor/components/send_invite_dialog.dart rename to lib/contact_invitation/views/send_invite_dialog.dart index 49adb68..417a18a 100644 --- a/lib/old_to_refactor/components/send_invite_dialog.dart +++ b/lib/contact_invitation/views/send_invite_dialog.dart @@ -5,19 +5,14 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; +import 'package:veilid_support/veilid_support.dart'; -import '../entities/local_account.dart'; -import '../providers/account.dart'; -import '../providers/contact_invite.dart'; -import '../tools/tools.dart'; -import '../veilid_support/veilid_support.dart'; +import '../../account_manager/account_manager.dart'; +import '../../tools/tools.dart'; import 'contact_invitation_display.dart'; -import 'enter_password.dart'; -import 'enter_pin.dart'; -class SendInviteDialog extends ConsumerStatefulWidget { +class SendInviteDialog extends StatefulWidget { const SendInviteDialog({super.key}); @override @@ -31,7 +26,7 @@ class SendInviteDialog extends ConsumerStatefulWidget { } } -class SendInviteDialogState extends ConsumerState { +class SendInviteDialogState extends State { final _messageTextController = TextEditingController( text: translate('send_invite_dialog.connect_with_me')); @@ -135,7 +130,8 @@ class SendInviteDialogState extends ConsumerState { final navigator = Navigator.of(context); // Start generation - final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future); + final activeAccountInfo = + await AccountRepository.instance.fetchActiveAccountInfo(); if (activeAccountInfo == null) { navigator.pop(); return; diff --git a/lib/contact_invitation/views/views.dart b/lib/contact_invitation/views/views.dart new file mode 100644 index 0000000..d9f599b --- /dev/null +++ b/lib/contact_invitation/views/views.dart @@ -0,0 +1,8 @@ +export 'contact_invitation_display.dart'; +export 'contact_invitation_item_widget.dart'; +export 'contact_invitation_list_widget.dart'; +export 'invite_dialog.dart'; +export 'new_contact_invitation_bottom_sheet.dart'; +export 'paste_invite_dialog.dart'; +export 'scan_invite_dialog.dart'; +export 'send_invite_dialog.dart'; diff --git a/lib/contacts/contacts.dart b/lib/contacts/contacts.dart new file mode 100644 index 0000000..83d1303 --- /dev/null +++ b/lib/contacts/contacts.dart @@ -0,0 +1 @@ +export 'views/views.dart'; diff --git a/lib/old_to_refactor/components/contact_item_widget.dart b/lib/contacts/views/contact_item_widget.dart similarity index 92% rename from lib/old_to_refactor/components/contact_item_widget.dart rename to lib/contacts/views/contact_item_widget.dart index ed57997..5ffaaa4 100644 --- a/lib/old_to_refactor/components/contact_item_widget.dart +++ b/lib/contacts/views/contact_item_widget.dart @@ -1,24 +1,21 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../proto/proto.dart' as proto; -import '../pages/main_pager/main_pager.dart'; -import '../providers/account.dart'; -import '../providers/chat.dart'; -import '../providers/contact.dart'; -import '../theme/theme.dart'; +import '../../proto/proto.dart' as proto; +import '../../theme/theme.dart'; -class ContactItemWidget extends ConsumerWidget { +class ContactItemWidget extends StatelessWidget { const ContactItemWidget({required this.contact, super.key}); final proto.Contact contact; @override // ignore: prefer_expression_function_bodies - Widget build(BuildContext context, WidgetRef ref) { + Widget build( + BuildContext context, + ) { final theme = Theme.of(context); //final textTheme = theme.textTheme; final scale = theme.extension()!; diff --git a/lib/old_to_refactor/components/contact_list_widget.dart b/lib/contacts/views/contact_list_widget.dart similarity index 100% rename from lib/old_to_refactor/components/contact_list_widget.dart rename to lib/contacts/views/contact_list_widget.dart diff --git a/lib/old_to_refactor/components/empty_contact_list_widget.dart b/lib/contacts/views/empty_contact_list_widget.dart similarity index 80% rename from lib/old_to_refactor/components/empty_contact_list_widget.dart rename to lib/contacts/views/empty_contact_list_widget.dart index bcd832b..db07b4a 100644 --- a/lib/old_to_refactor/components/empty_contact_list_widget.dart +++ b/lib/contacts/views/empty_contact_list_widget.dart @@ -1,15 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../tools/tools.dart'; +import '../../theme/theme.dart'; -class EmptyContactListWidget extends ConsumerWidget { +class EmptyContactListWidget extends StatelessWidget { const EmptyContactListWidget({super.key}); @override // ignore: prefer_expression_function_bodies - Widget build(BuildContext context, WidgetRef ref) { + Widget build( + BuildContext context, + ) { final theme = Theme.of(context); final textTheme = theme.textTheme; final scale = theme.extension()!; diff --git a/lib/contacts/views/views.dart b/lib/contacts/views/views.dart new file mode 100644 index 0000000..8c98b0f --- /dev/null +++ b/lib/contacts/views/views.dart @@ -0,0 +1,3 @@ +export 'contact_item_widget.dart'; +export 'contact_list_widget.dart'; +export 'empty_contact_list_widget.dart'; diff --git a/lib/layout/chat_only.dart b/lib/layout/chat_only.dart index ad81b4c..6f4e645 100644 --- a/lib/layout/chat_only.dart +++ b/lib/layout/chat_only.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../providers/window_control.dart'; -import 'home.dart'; +import '../chat/chat.dart'; +import '../tools/tools.dart'; class ChatOnlyPage extends StatefulWidget { const ChatOnlyPage({super.key}); @@ -11,7 +10,7 @@ class ChatOnlyPage extends StatefulWidget { ChatOnlyPageState createState() => ChatOnlyPageState(); } -class ChatOnlyPageState extends ConsumerState +class ChatOnlyPageState extends State with TickerProviderStateMixin { final _unfocusNode = FocusNode(); @@ -21,7 +20,7 @@ class ChatOnlyPageState extends ConsumerState WidgetsBinding.instance.addPostFrameCallback((_) async { setState(() {}); - await ref.read(windowControlProvider.notifier).changeWindowSetup( + await changeWindowSetup( TitleBarStyle.normal, OrientationCapability.normal); }); } @@ -33,13 +32,9 @@ class ChatOnlyPageState extends ConsumerState } @override - Widget build(BuildContext context) { - ref.watch(windowControlProvider); - - return SafeArea( - child: GestureDetector( - onTap: () => FocusScope.of(context).requestFocus(_unfocusNode), - child: HomePage.buildChatComponent(context, ref), - )); - } + Widget build(BuildContext context) => SafeArea( + child: GestureDetector( + onTap: () => FocusScope.of(context).requestFocus(_unfocusNode), + child: buildChatComponent(), + )); } diff --git a/lib/layout/edit_contact.dart b/lib/layout/edit_contact.dart index 169874f..480ff1f 100644 --- a/lib/layout/edit_contact.dart +++ b/lib/layout/edit_contact.dart @@ -1,28 +1,30 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -class ContactsPage extends ConsumerWidget { +class ContactsPage extends StatelessWidget { const ContactsPage({super.key}); static const path = '/contacts'; @override - Widget build(BuildContext context, WidgetRef ref) => const Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Contacts Page'), - // ElevatedButton( - // onPressed: () async { - // ref.watch(authNotifierProvider.notifier).login( - // "myEmail", - // "myPassword", - // ); - // }, - // child: const Text("Login"), - // ), - ], + Widget build( + BuildContext context, + ) => + const Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Contacts Page'), + // ElevatedButton( + // onPressed: () async { + // ref.watch(authNotifierProvider.notifier).login( + // "myEmail", + // "myPassword", + // ); + // }, + // child: const Text("Login"), + // ), + ], + ), ), - ), - ); + ); } diff --git a/lib/layout/home.dart b/lib/layout/home.dart index 86e838f..eeec9dc 100644 --- a/lib/layout/home.dart +++ b/lib/layout/home.dart @@ -9,7 +9,7 @@ import 'package:veilid_support/veilid_support.dart'; import '../../proto/proto.dart' as proto; import '../account_manager/account_manager.dart'; -import '../account_manager/models/models.dart'; +import '../chat/chat.dart'; import '../theme/theme.dart'; import '../tools/tools.dart'; import 'main_pager/main_pager.dart'; @@ -19,38 +19,6 @@ class HomePage extends StatefulWidget { @override HomePageState createState() => HomePageState(); - - static Widget buildChatComponent() { - final contactList = ref.watch(fetchContactListProvider).asData?.value ?? - const IListConst([]); - - final activeChat = ref.watch(activeChatStateProvider); - if (activeChat == null) { - return const EmptyChatWidget(); - } - - final activeAccountInfo = - ref.watch(fetchActiveAccountProvider).asData?.value; - if (activeAccountInfo == null) { - return const EmptyChatWidget(); - } - - final activeChatContactIdx = contactList.indexWhere( - (c) => - proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) == - activeChat, - ); - if (activeChatContactIdx == -1) { - ref.read(activeChatStateProvider.notifier).state = null; - return const EmptyChatWidget(); - } - final activeChatContact = contactList[activeChatContactIdx]; - - return ChatComponent( - activeAccountInfo: activeAccountInfo, - activeChat: activeChat, - activeChatContact: activeChatContact); - } } class HomePageState extends State with TickerProviderStateMixin { @@ -196,7 +164,7 @@ class HomePageState extends State with TickerProviderStateMixin { Widget buildTabletLeftPane() => Material(color: Colors.transparent, child: buildUserPanel()); - Widget buildTabletRightPane() => HomePage.buildChatComponent(); + Widget buildTabletRightPane() => buildChatComponent(); // ignore: prefer_expression_function_bodies Widget buildTablet() => Builder(builder: (context) { diff --git a/lib/layout/main_pager/account.dart b/lib/layout/main_pager/account.dart index 553c288..ece96c1 100644 --- a/lib/layout/main_pager/account.dart +++ b/lib/layout/main_pager/account.dart @@ -4,20 +4,15 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.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:veilid_support/veilid_support.dart'; -import '../../../components/contact_invitation_list_widget.dart'; -import '../../../components/contact_list_widget.dart'; -import '../../../entities/local_account.dart'; import '../../../proto/proto.dart' as proto; -import '../../providers/contact.dart'; -import '../../providers/contact_invite.dart'; -import '../../../theme/theme.dart'; -import '../../../tools/tools.dart'; -import '../../../../packages/veilid_support/veilid_support.dart'; +import '../../account_manager/account_manager.dart'; +import '../../contact_invitation/contact_invitation.dart'; +import '../../contacts/contacts.dart'; -class AccountPage extends ConsumerStatefulWidget { +class AccountPage extends StatefulWidget { const AccountPage({ required this.localAccounts, required this.activeUserLogin, @@ -41,7 +36,7 @@ class AccountPage extends ConsumerStatefulWidget { } } -class AccountPageState extends ConsumerState { +class AccountPageState extends State { final _unfocusNode = FocusNode(); @override diff --git a/lib/old_to_refactor/components/bottom_sheet_action_button.dart b/lib/layout/main_pager/bottom_sheet_action_button.dart similarity index 90% rename from lib/old_to_refactor/components/bottom_sheet_action_button.dart rename to lib/layout/main_pager/bottom_sheet_action_button.dart index 4330d33..c34e478 100644 --- a/lib/old_to_refactor/components/bottom_sheet_action_button.dart +++ b/lib/layout/main_pager/bottom_sheet_action_button.dart @@ -1,8 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -class BottomSheetActionButton extends ConsumerStatefulWidget { +class BottomSheetActionButton extends StatefulWidget { const BottomSheetActionButton( {required this.bottomSheetBuilder, required this.builder, @@ -32,8 +31,7 @@ class BottomSheetActionButton extends ConsumerStatefulWidget { } } -class BottomSheetActionButtonState - extends ConsumerState { +class BottomSheetActionButtonState extends State { bool _showFab = true; @override diff --git a/lib/layout/main_pager/chats.dart b/lib/layout/main_pager/chats.dart index d35fe6b..56756e2 100644 --- a/lib/layout/main_pager/chats.dart +++ b/lib/layout/main_pager/chats.dart @@ -1,28 +1,19 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../components/chat_single_contact_list_widget.dart'; -import '../../../components/empty_chat_list_widget.dart'; -import '../../../entities/local_account.dart'; import '../../../proto/proto.dart' as proto; -import '../../providers/account.dart'; -import '../../providers/chat.dart'; -import '../../providers/contact.dart'; -import '../../../local_accounts/local_accounts.dart'; -import '../../providers/logins.dart'; -import '../../../tools/tools.dart'; -import '../../../../packages/veilid_support/veilid_support.dart'; +import '../../account_manager/account_manager.dart'; +import '../../tools/tools.dart'; -class ChatsPage extends ConsumerStatefulWidget { +class ChatsPage extends StatefulWidget { const ChatsPage({super.key}); @override ChatsPageState createState() => ChatsPageState(); } -class ChatsPageState extends ConsumerState { +class ChatsPageState extends State { final _unfocusNode = FocusNode(); @override diff --git a/lib/layout/main_pager/main_pager.dart b/lib/layout/main_pager/main_pager.dart index 37c55fd..05e5979 100644 --- a/lib/layout/main_pager/main_pager.dart +++ b/lib/layout/main_pager/main_pager.dart @@ -1,27 +1,23 @@ import 'dart:async'; -import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_animate/flutter_animate.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:preload_page_view/preload_page_view.dart'; import 'package:stylish_bottom_bar/model/bar_items.dart'; import 'package:stylish_bottom_bar/stylish_bottom_bar.dart'; +import 'package:veilid_support/veilid_support.dart'; -import '../../../components/bottom_sheet_action_button.dart'; -import '../../../components/paste_invite_dialog.dart'; -import '../../../components/scan_invite_dialog.dart'; -import '../../../components/send_invite_dialog.dart'; -import '../../../entities/local_account.dart'; import '../../../proto/proto.dart' as proto; import '../../../tools/tools.dart'; -import '../../../../packages/veilid_support/veilid_support.dart'; +import '../../account_manager/account_manager.dart'; +import '../../contact_invitation/contact_invitation.dart'; +import '../../theme/theme.dart'; import 'account.dart'; +import 'bottom_sheet_action_button.dart'; import 'chats.dart'; class MainPager extends StatefulWidget { @@ -50,8 +46,7 @@ class MainPager extends StatefulWidget { } } -class MainPagerState extends ConsumerState - with TickerProviderStateMixin { +class MainPagerState extends State with TickerProviderStateMixin { ////////////////////////////////////////////////////////////////// final _unfocusNode = FocusNode(); @@ -151,64 +146,6 @@ class MainPagerState extends ConsumerState }); } - Widget _newContactInvitationBottomSheetBuilder( - // ignore: prefer_expression_function_bodies - BuildContext context) { - final theme = Theme.of(context); - final textTheme = theme.textTheme; - final scale = theme.extension()!; - - return KeyboardListener( - focusNode: FocusNode(), - onKeyEvent: (ke) { - if (ke.logicalKey == LogicalKeyboardKey.escape) { - Navigator.pop(context); - } - }, - child: SizedBox( - height: 200, - child: Column(children: [ - Text(translate('accounts_menu.invite_contact'), - style: textTheme.titleMedium) - .paddingAll(8), - Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Column(children: [ - IconButton( - onPressed: () async { - Navigator.pop(context); - await SendInviteDialog.show(context); - }, - iconSize: 64, - icon: const Icon(Icons.contact_page), - color: scale.primaryScale.background), - Text(translate('accounts_menu.create_invite')) - ]), - Column(children: [ - IconButton( - onPressed: () async { - Navigator.pop(context); - await ScanInviteDialog.show(context); - }, - iconSize: 64, - icon: const Icon(Icons.qr_code_scanner), - color: scale.primaryScale.background), - Text(translate('accounts_menu.scan_invite')) - ]), - Column(children: [ - IconButton( - onPressed: () async { - Navigator.pop(context); - await PasteInviteDialog.show(context); - }, - iconSize: 64, - icon: const Icon(Icons.paste), - color: scale.primaryScale.background), - Text(translate('accounts_menu.paste_invite')) - ]) - ]).expanded() - ]))); - } - // ignore: prefer_expression_function_bodies Widget _onNewChatBottomSheetBuilder(BuildContext context) { return const SizedBox( @@ -221,7 +158,7 @@ class MainPagerState extends ConsumerState Widget _bottomSheetBuilder(BuildContext context) { if (_currentPage == 0) { // New contact invitation - return _newContactInvitationBottomSheetBuilder(context); + return newContactInvitationBottomSheetBuilder(context); } else if (_currentPage == 1) { // New chat return _onNewChatBottomSheetBuilder(context); diff --git a/lib/old_to_refactor/components/empty_chat_widget.dart b/lib/old_to_refactor/components/empty_chat_widget.dart deleted file mode 100644 index dbe184d..0000000 --- a/lib/old_to_refactor/components/empty_chat_widget.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class EmptyChatWidget extends ConsumerWidget { - const EmptyChatWidget({super.key}); - - @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context, WidgetRef ref) { - // - - return Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.chat, - color: Theme.of(context).disabledColor, - size: 48, - ), - Text( - 'Say Something', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).disabledColor, - ), - ), - ], - ), - ); - } -} diff --git a/lib/old_to_refactor/components/no_conversation_widget.dart b/lib/old_to_refactor/components/no_conversation_widget.dart deleted file mode 100644 index faf820f..0000000 --- a/lib/old_to_refactor/components/no_conversation_widget.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class NoContactWidget extends ConsumerWidget { - const NoContactWidget({super.key}); - - @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context, WidgetRef ref) { - // - return Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.emoji_people_outlined, - color: Theme.of(context).disabledColor, - size: 48, - ), - Text( - 'Choose A Conversation To Chat', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).disabledColor, - ), - ), - ], - ), - ); - } -} diff --git a/lib/old_to_refactor/managers/valid_contact_invitation.dart b/lib/old_to_refactor/managers/valid_contact_invitation.dart deleted file mode 100644 index 8b13789..0000000 --- a/lib/old_to_refactor/managers/valid_contact_invitation.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/settings/preferences_cubit.dart b/lib/settings/preferences_cubit.dart index e9f69b5..5d85ce5 100644 --- a/lib/settings/preferences_cubit.dart +++ b/lib/settings/preferences_cubit.dart @@ -1,8 +1,6 @@ import '../tools/tools.dart'; import 'settings.dart'; -xxx convert to non-asyncvalue based wrapper since there's always a default here - class PreferencesCubit extends StreamWrapperCubit { PreferencesCubit(PreferencesRepository repository) : super(repository.stream, defaultState: repository.value); diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index e13fd3c..73c9ba7 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -1,17 +1,13 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import 'package:veilid_support/veilid_support.dart'; import '../layout/default_app_bar.dart'; import '../theme/theme.dart'; +import '../tools/tools.dart'; import '../veilid_processor/veilid_processor.dart'; -import 'preferences_cubit.dart'; -import 'preferences_repository.dart'; import 'settings.dart'; class SettingsPage extends StatefulWidget { @@ -68,8 +64,8 @@ class SettingsPageState extends State { } @override - Widget build(BuildContext context) => BlocBuilder>( + Widget build(BuildContext context) => AsyncBlocBuilder( builder: (context, state) => ThemeSwitchingArea( child: Scaffold( // resizeToAvoidBottomInset: false, @@ -94,14 +90,16 @@ class SettingsPageState extends State { label: Text(translate('settings_page.color_theme'))), items: _getThemeDropdownItems(), - initialValue: themePreferences.colorPreference, + initialValue: state.themePreferences.colorPreference, onChanged: (value) async { - final newPrefs = themePreferences.copyWith( - colorPreference: value as ColorPreference); - await themeService.save(newPrefs); + final newPrefs = state.copyWith( + themePreferences: state.themePreferences + .copyWith( + colorPreference: + value as ColorPreference)); switcher.changeTheme( - theme: themeService.get(newPrefs)); - ref.invalidate(themeServiceProvider); + theme: newPrefs.themePreferences.themeData()); + await PreferencesRepository.instance.set(newPrefs); setState(() {}); })), ThemeSwitcher.withTheme( @@ -111,15 +109,17 @@ class SettingsPageState extends State { label: Text( translate('settings_page.brightness_mode'))), items: _getBrightnessDropdownItems(), - initialValue: themePreferences.brightnessPreference, + initialValue: + state.themePreferences.brightnessPreference, onChanged: (value) async { - final newPrefs = themePreferences.copyWith( - brightnessPreference: - value as BrightnessPreference); - await themeService.save(newPrefs); + final newPrefs = state.copyWith( + themePreferences: state.themePreferences + .copyWith( + brightnessPreference: + value as BrightnessPreference)); switcher.changeTheme( - theme: themeService.get(newPrefs)); - ref.invalidate(themeServiceProvider); + theme: newPrefs.themePreferences.themeData()); + await PreferencesRepository.instance.set(newPrefs); setState(() {}); })), ], diff --git a/lib/old_to_refactor/components/enter_password.dart b/lib/tools/enter_password.dart similarity index 94% rename from lib/old_to_refactor/components/enter_password.dart rename to lib/tools/enter_password.dart index a1b06ab..42880ee 100644 --- a/lib/old_to_refactor/components/enter_password.dart +++ b/lib/tools/enter_password.dart @@ -2,12 +2,11 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../tools/tools.dart'; +import '../theme/theme.dart'; -class EnterPasswordDialog extends ConsumerStatefulWidget { +class EnterPasswordDialog extends StatefulWidget { const EnterPasswordDialog({ this.matchPass, this.description, @@ -29,7 +28,7 @@ class EnterPasswordDialog extends ConsumerStatefulWidget { } } -class EnterPasswordDialogState extends ConsumerState { +class EnterPasswordDialogState extends State { final passwordController = TextEditingController(); final focusNode = FocusNode(); final formKey = GlobalKey(); diff --git a/lib/old_to_refactor/components/enter_pin.dart b/lib/tools/enter_pin.dart similarity index 95% rename from lib/old_to_refactor/components/enter_pin.dart rename to lib/tools/enter_pin.dart index a8def81..3128710 100644 --- a/lib/old_to_refactor/components/enter_pin.dart +++ b/lib/tools/enter_pin.dart @@ -2,13 +2,12 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:pinput/pinput.dart'; -import '../tools/tools.dart'; +import '../theme/theme.dart'; -class EnterPinDialog extends ConsumerStatefulWidget { +class EnterPinDialog extends StatefulWidget { const EnterPinDialog({ required this.reenter, required this.description, @@ -30,7 +29,7 @@ class EnterPinDialog extends ConsumerStatefulWidget { } } -class EnterPinDialogState extends ConsumerState { +class EnterPinDialogState extends State { final pinController = TextEditingController(); final focusNode = FocusNode(); final formKey = GlobalKey(); diff --git a/lib/tools/tools.dart b/lib/tools/tools.dart index 5532a56..6750c62 100644 --- a/lib/tools/tools.dart +++ b/lib/tools/tools.dart @@ -1,10 +1,13 @@ export 'animations.dart'; +export 'enter_password.dart'; +export 'enter_pin.dart'; export 'loggy.dart'; export 'phono_byte.dart'; export 'responsive.dart'; export 'scanner_error_widget.dart'; export 'shared_preferences.dart'; export 'state_logger.dart'; +export 'stream_listenable.dart'; export 'stream_wrapper_cubit.dart'; export 'widget_helpers.dart'; export 'window_control.dart'; diff --git a/lib/tools/widget_helpers.dart b/lib/tools/widget_helpers.dart index 90fb9bf..7812c5e 100644 --- a/lib/tools/widget_helpers.dart +++ b/lib/tools/widget_helpers.dart @@ -1,6 +1,7 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:motion_toast/motion_toast.dart'; @@ -60,6 +61,22 @@ extension AsyncValueBuilderExt on AsyncValue { asyncValueBuilder(this, builder); } +class AsyncBlocBuilder>, S> + extends BlocBuilder> { + AsyncBlocBuilder({ + required BlocWidgetBuilder builder, + Widget Function(BuildContext)? loading, + Widget Function(BuildContext, Object, StackTrace?)? error, + super.key, + super.bloc, + super.buildWhen, + }) : super( + builder: (context, state) => state.when( + loading: () => (loading ?? waitingPage)(context), + error: (e, st) => (error ?? errorPage)(context, e, st), + data: (d) => builder(context, d))); +} + Future showErrorModal( BuildContext context, String title, String text) async { await QuickAlert.show(