import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/paynym/paynym_account.dart'; import 'package:stackwallet/pages/paynym/subwidgets/featured_paynyms_widget.dart'; import 'package:stackwallet/pages/paynym/subwidgets/paynym_card.dart'; import 'package:stackwallet/providers/global/paynym_api_provider.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/paynym_search_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; class AddNewPaynymFollowView extends ConsumerStatefulWidget { const AddNewPaynymFollowView({ Key? key, required this.walletId, }) : super(key: key); final String walletId; static const String routeName = "/addNewPaynymFollow"; @override ConsumerState createState() => _AddNewPaynymFollowViewState(); } class _AddNewPaynymFollowViewState extends ConsumerState { late final TextEditingController _searchController; late final FocusNode searchFieldFocusNode; String _searchString = ""; bool _didSearch = false; PaynymAccount? _searchResult; final isDesktop = Util.isDesktop; Future _search() async { _didSearch = true; bool didPopLoading = false; unawaited( showDialog( barrierDismissible: false, context: context, builder: (context) => const LoadingIndicator( width: 200, ), ).then((_) => didPopLoading = true), ); final paynymAccount = await ref.read(paynymAPIProvider).nym(_searchString); if (mounted) { if (!didPopLoading) { Navigator.of(context).pop(); } setState(() { _searchResult = paynymAccount.value; }); } } Future _clear() async { _searchString = ""; setState(() { _searchController.text = ""; }); } Future _paste() async { final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); if (data?.text != null && data!.text!.isNotEmpty) { String content = data.text!.trim(); if (content.contains("\n")) { content = content.substring( 0, content.indexOf( "\n", ), ); } _searchString = content; setState(() { _searchController.text = content; _searchController.selection = TextSelection.collapsed( offset: content.length, ); }); } } Future _scanQr() async { try { if (!isDesktop && FocusScope.of(context).hasFocus) { FocusScope.of(context).unfocus(); await Future.delayed(const Duration(milliseconds: 75)); } final qrResult = await const BarcodeScannerWrapper().scan(); final pCodeString = qrResult.rawContent; _searchString = pCodeString; setState(() { _searchController.text = pCodeString; _searchController.selection = TextSelection.collapsed( offset: pCodeString.length, ); }); } catch (_) { // scan failed } } @override void initState() { _searchController = TextEditingController(); searchFieldFocusNode = FocusNode(); super.initState(); } @override void dispose() { _searchController.dispose(); searchFieldFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); return ConditionalParent( condition: !isDesktop, builder: (child) => MasterScaffold( isDesktop: isDesktop, appBar: AppBar( leading: AppBarBackButton( onPressed: () { Navigator.of(context).pop(); }, ), titleSpacing: 0, title: Text( "New follow", style: STextStyles.navBarTitle(context), overflow: TextOverflow.ellipsis, ), ), body: SafeArea( child: LayoutBuilder( builder: (context, constraints) => SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints( minHeight: constraints.maxHeight, ), child: IntrinsicHeight( child: Padding( padding: const EdgeInsets.all(16), child: child, ), ), ), ), ), ), ), child: ConditionalParent( condition: isDesktop, builder: (child) => DesktopDialog( maxWidth: 580, maxHeight: double.infinity, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.only(left: 32), child: Text( "New follow", style: STextStyles.desktopH3(context), ), ), const DesktopDialogCloseButton(), ], ), Padding( padding: const EdgeInsets.only( left: 32, right: 32, bottom: 32, ), child: child, ), ], ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox( height: 10, ), Text( "Featured PayNyms", style: isDesktop ? STextStyles.desktopTextExtraExtraSmall(context) : STextStyles.sectionLabelMedium12(context), ), const SizedBox( height: 12, ), FeaturedPaynymsWidget( walletId: widget.walletId, ), const SizedBox( height: 24, ), Text( "Add new", style: isDesktop ? STextStyles.desktopTextExtraExtraSmall(context) : STextStyles.sectionLabelMedium12(context), ), const SizedBox( height: 12, ), if (isDesktop) Row( children: [ Expanded( child: Stack( children: [ RoundedContainer( padding: const EdgeInsets.all(0), color: Theme.of(context) .extension()! .textFieldDefaultBG, height: 56, child: Center( child: TextField( autocorrect: !isDesktop, enableSuggestions: !isDesktop, controller: _searchController, focusNode: searchFieldFocusNode, onChanged: (value) { setState(() { _searchString = value; }); }, style: STextStyles.desktopTextExtraExtraSmall( context) .copyWith( color: Theme.of(context) .extension()! .textFieldActiveText, // height: 1.8, ), decoration: InputDecoration( hintText: "Paste payment code", hoverColor: Colors.transparent, fillColor: Colors.transparent, contentPadding: const EdgeInsets.all(16), hintStyle: STextStyles.desktopTextFieldLabel(context) .copyWith( fontSize: 14, ), enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, errorBorder: InputBorder.none, disabledBorder: InputBorder.none, focusedErrorBorder: InputBorder.none, suffixIcon: Padding( padding: const EdgeInsets.only(right: 8), child: UnconstrainedBox( child: Row( children: [ _searchController.text.isNotEmpty ? TextFieldIconButton( onTap: _clear, child: RoundedContainer( padding: const EdgeInsets.all(8), color: Theme.of(context) .extension()! .buttonBackSecondary, child: const XIcon(), ), ) : TextFieldIconButton( key: const Key( "paynymPasteAddressFieldButtonKey"), onTap: _paste, child: RoundedContainer( padding: const EdgeInsets.all(8), color: Theme.of(context) .extension()! .buttonBackSecondary, child: const ClipboardIcon(), ), ), TextFieldIconButton( key: const Key( "paynymScanQrButtonKey"), onTap: _scanQr, child: RoundedContainer( padding: const EdgeInsets.all(8), color: Theme.of(context) .extension()! .buttonBackSecondary, child: const QrCodeIcon(), ), ) ], ), ), ), ), ), ), ), ], ), ), const SizedBox( width: 10, ), PaynymSearchButton(onPressed: _search), ], ), if (!isDesktop) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), child: TextField( autocorrect: !isDesktop, enableSuggestions: !isDesktop, controller: _searchController, focusNode: searchFieldFocusNode, onChanged: (value) { setState(() { _searchString = value; }); }, style: STextStyles.field(context), decoration: standardInputDecoration( "Paste payment code", searchFieldFocusNode, context, desktopMed: isDesktop, ).copyWith( suffixIcon: Padding( padding: const EdgeInsets.only(right: 8), child: UnconstrainedBox( child: Row( children: [ _searchController.text.isNotEmpty ? TextFieldIconButton( onTap: _clear, child: const XIcon(), ) : TextFieldIconButton( key: const Key( "paynymPasteAddressFieldButtonKey"), onTap: _paste, child: const ClipboardIcon(), ), TextFieldIconButton( key: const Key("paynymScanQrButtonKey"), onTap: _scanQr, child: const QrCodeIcon(), ) ], ), ), ), ), ), ), if (!isDesktop) const SizedBox( height: 12, ), if (!isDesktop) SecondaryButton( label: "Search", onPressed: _search, ), if (_didSearch) const SizedBox( height: 20, ), if (_didSearch && _searchResult == null) RoundedWhiteContainer( borderColor: isDesktop ? Theme.of(context) .extension()! .backgroundAppBar : null, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Nothing found. Please check the payment code.", style: isDesktop ? STextStyles.desktopTextExtraExtraSmall(context) : STextStyles.label(context), ), ], ), ), if (_didSearch && _searchResult != null) RoundedWhiteContainer( padding: const EdgeInsets.all(0), borderColor: isDesktop ? Theme.of(context) .extension()! .backgroundAppBar : null, child: PaynymCard( key: UniqueKey(), label: _searchResult!.nymName, paymentCodeString: _searchResult!.codes.first.code, walletId: widget.walletId, ), ), ], ), ), ); } }