WIP desktop send flow address book address chooser

This commit is contained in:
julian 2022-11-01 15:58:41 -06:00
parent 60b332ad8a
commit 4d8804f78b
4 changed files with 385 additions and 84 deletions

View file

@ -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<AddressBookAddressChooser> createState() =>
_AddressBookAddressChooserState();
}
class _AddressBookAddressChooserState extends State<AddressBookAddressChooser> {
int _compareContactFavorite(Contact a, Contact b) {
if (a.isFavorite && b.isFavorite) {
return 0;
} else if (a.isFavorite) {
return 1;
} else {
return -1;
}
}
List<Contact> pullOutFavorites(List<Contact> contacts) {
final List<Contact> favorites = [];
contacts.removeWhere((contact) {
if (contact.isFavorite) {
favorites.add(contact);
return true;
}
return false;
});
return favorites;
}
List<Contact> filter(List<Contact> 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<Contact> 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,
);
}
},
);
},
),
),
),
],
);
}
}

View file

@ -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<StackColors>()!.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<StackColors>()!
.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<StackColors>()!
.textDark,
),
),
Text(
e.address,
style: STextStyles
.desktopTextExtraExtraSmall(context),
),
],
),
],
),
BlueTextButton(
text: "Select wallet",
onTap: () {
Navigator.of(context).pop(e);
},
),
],
),
)
],
),
),
],
),
),
);
}
}

View file

@ -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<DesktopSend> {
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<DesktopSend> {
if (sendToController.text.isEmpty)
TextFieldIconButton(
key: const Key("sendViewAddressBookButtonKey"),
onTap: () {
Navigator.of(context).pushNamed(
AddressBookView.routeName,
arguments: coin,
onTap: () async {
final entry =
await showDialog<ContactAddressEntry?>(
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(),
),

View file

@ -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<AddressBookCard> {
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<AddressBookCard> {
}
}
return RoundedWhiteContainer(
padding: const EdgeInsets.all(4),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
padding: const EdgeInsets.all(0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
showDialog<void>(
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<StackColors>()!
.myStackContactIconBG
: Theme.of(context)
.extension<StackColors>()!
.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<StackColors>()!
.myStackContactIconBG
: Theme.of(context)
.extension<StackColors>()!
.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<StackColors>()!.highlight,
padding: const EdgeInsets.all(0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
showDialog<void>(
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,
),
),
),