diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart new file mode 100644 index 000000000..372c86e2f --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart'; +import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; + +class AddressBookAddressChooser extends StatefulWidget { + const AddressBookAddressChooser({ + Key? key, + this.coin, + }) : super(key: key); + + final Coin? coin; + + @override + State createState() => + _AddressBookAddressChooserState(); +} + +class _AddressBookAddressChooserState extends State { + int _compareContactFavorite(Contact a, Contact b) { + if (a.isFavorite && b.isFavorite) { + return 0; + } else if (a.isFavorite) { + return 1; + } else { + return -1; + } + } + + List pullOutFavorites(List contacts) { + final List favorites = []; + contacts.removeWhere((contact) { + if (contact.isFavorite) { + favorites.add(contact); + return true; + } + return false; + }); + + return favorites; + } + + List filter(List contacts) { + if (widget.coin != null) { + contacts.removeWhere( + (e) => e.addresses.where((a) => a.coin == widget.coin!).isEmpty); + } + + if (contacts.length < 2) { + return contacts; + } + + contacts.sort(_compareContactFavorite); + + // TODO: other filtering? + + return contacts; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // search field + const TextField(), + const SizedBox( + height: 16, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Consumer( + builder: (context, ref, _) { + List contacts = ref + .watch(addressBookServiceProvider + .select((value) => value.contacts)) + .toList(); + + contacts = filter(contacts); + + final favorites = pullOutFavorites(contacts); + + return ListView.builder( + primary: false, + shrinkWrap: true, + itemCount: favorites.length + + contacts.length + + 2, // +2 for "fav" and "all" headers + itemBuilder: (context, index) { + if (index == 0) { + return Padding( + padding: const EdgeInsets.only( + bottom: 10, + ), + child: Text( + "Favorites", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + ); + } else if (index <= favorites.length) { + final id = favorites[index - 1].id; + return ContactListItem( + key: Key("contactCard_${id}_key"), + contactId: id, + filterByCoin: widget.coin, + ); + } else if (index == favorites.length + 1) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + child: Text( + "All contacts", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + ); + } else { + final id = contacts[index - favorites.length - 1].id; + return ContactListItem( + key: Key("contactCard_${id}_key"), + contactId: id, + filterByCoin: widget.coin, + ); + } + }, + ); + }, + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart new file mode 100644 index 000000000..593ae2bc4 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/address_book_card.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/expandable.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart'; + +class ContactListItem extends ConsumerWidget { + const ContactListItem({ + Key? key, + required this.contactId, + this.filterByCoin, + }) : super(key: key); + + final String contactId; + final Coin? filterByCoin; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final contact = ref.watch(addressBookServiceProvider + .select((value) => value.getContactById(contactId))); + + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context).extension()!.background, + child: Expandable( + header: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 14, + ), + child: AddressBookCard( + contactId: contactId, + ), + ), + body: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // filter addresses by coin is provided before building address list + ...contact.addresses + .where((e) => + filterByCoin != null ? e.coin == filterByCoin! : true) + .map( + (e) => Column( + key: Key("contactAddress_${e.address}_${e.label}_key"), + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 1, + color: Theme.of(context) + .extension()! + .background, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 14, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + WalletInfoCoinIcon(coin: e.coin), + const SizedBox( + width: 12, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${contactId == "default" ? e.other! : e.label} (${e.coin.ticker})", + style: STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + Text( + e.address, + style: STextStyles + .desktopTextExtraExtraSmall(context), + ), + ], + ), + ], + ), + BlueTextButton( + text: "Select wallet", + onTap: () { + Navigator.of(context).pop(e); + }, + ), + ], + ), + ) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index a47b2b9dd..26f2f806c 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -5,17 +5,17 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; -import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; -import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/utilities/address_utils.dart'; @@ -42,7 +42,6 @@ import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:tuple/tuple.dart'; class DesktopSend extends ConsumerStatefulWidget { const DesktopSend({ @@ -330,10 +329,10 @@ class _DesktopSendState extends ConsumerState { builder: (context) => DesktopDialog( maxHeight: double.infinity, maxWidth: 580, - child: ConfirmTransactionView( - transactionInfo: txData, - walletId: walletId, - ), + child: ConfirmTransactionView( + transactionInfo: txData, + walletId: walletId, + ), ), ), ); @@ -1184,11 +1183,34 @@ class _DesktopSendState extends ConsumerState { if (sendToController.text.isEmpty) TextFieldIconButton( key: const Key("sendViewAddressBookButtonKey"), - onTap: () { - Navigator.of(context).pushNamed( - AddressBookView.routeName, - arguments: coin, + onTap: () async { + final entry = + await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 696, + maxHeight: 600, + child: AddressBookAddressChooser( + coin: coin, + ), + ), ); + + if (entry != null) { + sendToController.text = + entry.other ?? entry.label; + + _address = entry.address; + + _updatePreviewButtonState( + _address, + _amountToSend, + ); + + setState(() { + _addressToggleFlag = true; + }); + } }, child: const AddressBookIcon(), ), diff --git a/lib/widgets/address_book_card.dart b/lib/widgets/address_book_card.dart index 7a2fca19f..6a51edbad 100644 --- a/lib/widgets/address_book_card.dart +++ b/lib/widgets/address_book_card.dart @@ -8,6 +8,8 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.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/rounded_white_container.dart'; class AddressBookCard extends ConsumerStatefulWidget { @@ -21,11 +23,12 @@ class AddressBookCard extends ConsumerStatefulWidget { class _AddressBookCardState extends ConsumerState { late final String contactId; + late final bool isDesktop; @override void initState() { contactId = widget.contactId; - + isDesktop = Util.isDesktop; super.initState(); } @@ -51,82 +54,101 @@ class _AddressBookCardState extends ConsumerState { } } - return RoundedWhiteContainer( - padding: const EdgeInsets.all(4), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - padding: const EdgeInsets.all(0), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - showDialog( - context: context, - useSafeArea: true, - barrierDismissible: true, - builder: (_) => ContactPopUp( - contactId: contact.id, + return ConditionalParent( + condition: !isDesktop, + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: contact.id == "default" + ? Theme.of(context) + .extension()! + .myStackContactIconBG + : Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular(32), ), - ); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: contact.id == "default" - ? Theme.of(context) - .extension()! - .myStackContactIconBG - : Theme.of(context) - .extension()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular(32), - ), - child: contact.id == "default" + child: contact.id == "default" + ? Center( + child: SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 20, + ), + ) + : contact.emojiChar != null ? Center( - child: SvgPicture.asset( - Assets.svg.stackIcon(context), - width: 20, - ), + child: Text(contact.emojiChar!), ) - : contact.emojiChar != null - ? Center( - child: Text(contact.emojiChar!), - ) - : Center( - child: SvgPicture.asset( - Assets.svg.user, - width: 18, - ), - ), + : Center( + child: SvgPicture.asset( + Assets.svg.user, + width: 18, + ), + ), + ), + const SizedBox( + width: 12, + ), + if (isDesktop) + Text( + contact.name, + style: STextStyles.itemSubtitle12(context), + ), + if (isDesktop) + const SizedBox( + width: 16, + ), + if (isDesktop) + Text( + coinsString, + style: STextStyles.label(context), + ), + if (!isDesktop) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + contact.name, + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox( + height: 4, + ), + Text( + coinsString, + style: STextStyles.label(context), + ), + ], + ) + ], + ), + builder: (child) => RoundedWhiteContainer( + padding: const EdgeInsets.all(4), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + showDialog( + context: context, + useSafeArea: true, + barrierDismissible: true, + builder: (_) => ContactPopUp( + contactId: contact.id, ), - const SizedBox( - width: 12, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - contact.name, - style: STextStyles.itemSubtitle12(context), - ), - const SizedBox( - height: 4, - ), - Text( - coinsString, - style: STextStyles.label(context), - ), - ], - ) - ], + ); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: child, ), ), ),