diff --git a/assets/svg/chevron-up.svg b/assets/svg/chevron-up.svg new file mode 100644 index 000000000..630f8df69 --- /dev/null +++ b/assets/svg/chevron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/pages_desktop_specific/home/settings_menu/advanced_settings.dart b/lib/pages_desktop_specific/home/advanced_settings/advanced_settings.dart similarity index 60% rename from lib/pages_desktop_specific/home/settings_menu/advanced_settings.dart rename to lib/pages_desktop_specific/home/advanced_settings/advanced_settings.dart index e55326527..7361d9773 100644 --- a/lib/pages_desktop_specific/home/settings_menu/advanced_settings.dart +++ b/lib/pages_desktop_specific/home/advanced_settings/advanced_settings.dart @@ -1,17 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages/stack_privacy_calls.dart'; +import 'package:stackwallet/pages_desktop_specific/home/advanced_settings/stack_privacy_dialog.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import '../../../pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; -import '../../../providers/global/prefs_provider.dart'; -import '../../../widgets/custom_buttons/draggable_switch_button.dart'; - class AdvancedSettings extends ConsumerStatefulWidget { const AdvancedSettings({Key? key}) : super(key: key); @@ -109,63 +106,69 @@ class _AdvancedSettings extends ConsumerState { ), /// TODO: Make a dialog popup - Padding( - padding: const EdgeInsets.all(10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Stack Experience", - style: - STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark), - textAlign: TextAlign.left, - ), - Text( - "Easy Crypto", - style: STextStyles.desktopTextExtraExtraSmall( - context), - ), - ], - ), - const StackPrivacyButton(), - ], - ), - ), - const Padding( - padding: EdgeInsets.all(10.0), - child: Divider( - thickness: 0.5, - ), - ), - - /// TODO: Make a dialog popup - Padding( - padding: const EdgeInsets.all(10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Debug info", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark), - textAlign: TextAlign.left, - ), - ShowLogsButton(), - ], - ), - ), + Consumer(builder: (_, ref, __) { + final externalCalls = ref.watch( + prefsChangeNotifierProvider + .select((value) => value.externalCalls), + ); + return Padding( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Stack Experience", + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark), + textAlign: TextAlign.left, + ), + Text( + externalCalls ? "Easy crypto" : "Incognito", + style: STextStyles.desktopTextExtraExtraSmall( + context), + ), + ], + ), + const StackPrivacyButton(), + ], + ), + ); + }), ], ), + const Padding( + padding: EdgeInsets.all(10.0), + child: Divider( + thickness: 0.5, + ), + ), + + /// TODO: Make a dialog popup + Padding( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Debug info", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark), + textAlign: TextAlign.left, + ), + const ShowLogsButton(), + ], + ), + ), ], ), ), @@ -181,6 +184,17 @@ class StackPrivacyButton extends ConsumerWidget { }) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { + Future changePrivacySettings() async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const StackPrivacyDialog(); + }, + ); + } + return SizedBox( width: 84, height: 37, @@ -189,10 +203,11 @@ class StackPrivacyButton extends ConsumerWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - Navigator.of(context).pushNamed( - StackPrivacyCalls.routeName, - arguments: false, - ); + // Navigator.of(context).pushNamed( + // StackPrivacyCalls.routeName, + // arguments: false, + // ); + changePrivacySettings(); }, child: Text( "Change", @@ -208,6 +223,18 @@ class ShowLogsButton extends ConsumerWidget { const ShowLogsButton({ Key? key, }) : super(key: key); + + Future viewDebugLogs() async { + // await showDialog( + // context: context, + // useSafeArea: false, + // barrierDismissible: true, + // builder: (context) { + // return const DebugInfoDialog(); + // }, + // ); + } + @override Widget build(BuildContext context, WidgetRef ref) { return SizedBox( @@ -218,7 +245,8 @@ class ShowLogsButton extends ConsumerWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - Navigator.of(context).pushNamed(DebugView.routeName); + // + viewDebugLogs(); }, child: Text( "Show logs", diff --git a/lib/pages_desktop_specific/home/advanced_settings/debug_info_dialog.dart b/lib/pages_desktop_specific/home/advanced_settings/debug_info_dialog.dart new file mode 100644 index 000000000..e69de29bb diff --git a/lib/pages_desktop_specific/home/advanced_settings/stack_privacy_dialog.dart b/lib/pages_desktop_specific/home/advanced_settings/stack_privacy_dialog.dart new file mode 100644 index 000000000..8e385fd37 --- /dev/null +++ b/lib/pages_desktop_specific/home/advanced_settings/stack_privacy_dialog.dart @@ -0,0 +1,437 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; + +import '../../../hive/db.dart'; +import '../../../providers/global/prefs_provider.dart'; +import '../../../providers/global/price_provider.dart'; +import '../../../services/exchange/exchange_data_loading_service.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/constants.dart'; +import '../../../utilities/theme/stack_colors.dart'; +import '../../../utilities/util.dart'; +import '../../../widgets/rounded_white_container.dart'; + +class StackPrivacyDialog extends ConsumerStatefulWidget { + const StackPrivacyDialog({Key? key}) : super(key: key); + + @override + ConsumerState createState() => _StackPrivacyDialog(); +} + +class _StackPrivacyDialog extends ConsumerState { + late final bool isDesktop; + late bool isEasy; + late bool infoToggle; + + @override + void initState() { + isDesktop = Util.isDesktop; + isEasy = ref.read(prefsChangeNotifierProvider).externalCalls; + infoToggle = isEasy; + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxHeight: 650, + maxWidth: 600, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(32), + child: Text( + "Choose Your Stack Experience", + style: STextStyles.desktopH3(context), + textAlign: TextAlign.center, + ), + ), + const DesktopDialogCloseButton(), + ], + ), + const SizedBox( + height: 35, + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 32), + child: PrivacyToggle( + externalCallsEnabled: isEasy, + onChanged: (externalCalls) { + isEasy = externalCalls; + setState(() { + infoToggle = isEasy; + }); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(32.0), + child: RoundedWhiteContainer( + borderColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: Center( + child: RichText( + textAlign: TextAlign.left, + text: TextSpan( + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.label(context).copyWith( + fontSize: 12.0, + ), + children: infoToggle + ? [ + const TextSpan( + text: + "Exchange data preloaded for a seamless experience."), + const TextSpan( + text: + "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), + TextSpan( + text: "\n\nRecommended for most crypto users.", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall600( + context) + : TextStyle( + color: Theme.of(context) + .extension()! + .textDark, + fontWeight: FontWeight.w600, + ), + ), + ] + : [ + const TextSpan( + text: + "Exchange data not preloaded (slower experience)."), + const TextSpan( + text: + "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), + TextSpan( + text: + "\n\nRecommended for the privacy conscious.", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall600( + context) + : TextStyle( + color: Theme.of(context) + .extension()! + .textDark, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + ), + // const Spacer(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: () {}, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Save", + onPressed: () { + ref.read(prefsChangeNotifierProvider).externalCalls = + isEasy; + + DB.instance + .put( + boxName: DB.boxNamePrefs, + key: "externalCalls", + value: isEasy) + .then((_) { + if (isEasy) { + unawaited(ExchangeDataLoadingService().loadAll(ref)); + ref + .read(priceAnd24hChangeNotifierProvider) + .start(true); + } + }); + if (isDesktop) { + Navigator.pop(context); + } + }, + ), + ) + ], + ), + ), + ], + ), + ); + } +} + +class PrivacyToggle extends StatefulWidget { + const PrivacyToggle({ + Key? key, + required this.externalCallsEnabled, + this.onChanged, + }) : super(key: key); + + final bool externalCallsEnabled; + final void Function(bool)? onChanged; + + @override + State createState() => _PrivacyToggleState(); +} + +class _PrivacyToggleState extends State { + late bool externalCallsEnabled; + + late final bool isDesktop; + + @override + void initState() { + isDesktop = Util.isDesktop; + // initial toggle state + externalCallsEnabled = widget.externalCallsEnabled; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: RawMaterialButton( + elevation: 0, + hoverElevation: 0, + fillColor: Theme.of(context).extension()!.popupBG, + shape: RoundedRectangleBorder( + side: !externalCallsEnabled + ? BorderSide.none + : BorderSide( + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 2, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius * 2, + ), + ), + onPressed: () { + setState(() { + // update toggle state + externalCallsEnabled = true; + }); + // call callback with newly set value + widget.onChanged?.call(externalCallsEnabled); + }, + child: Padding( + padding: const EdgeInsets.all( + 12, + ), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (isDesktop) + const SizedBox( + height: 10, + ), + SvgPicture.asset( + Assets.svg.personaEasy, + width: 120, + height: 120, + ), + if (isDesktop) + const SizedBox( + height: 12, + ), + Center( + child: Text( + "Easy Crypto", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.label700(context), + ), + ), + Center( + child: Text( + "Recommended", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.label(context), + ), + ), + if (isDesktop) + const SizedBox( + height: 12, + ), + ], + ), + if (externalCallsEnabled) + Positioned( + top: 4, + right: 4, + child: SvgPicture.asset( + Assets.svg.checkCircle, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + ), + if (!externalCallsEnabled) + Positioned( + top: 4, + right: 4, + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(1000), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + ), + ), + ), + ], + ), + ), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: RawMaterialButton( + elevation: 0, + hoverElevation: 0, + fillColor: Theme.of(context).extension()!.popupBG, + shape: RoundedRectangleBorder( + side: externalCallsEnabled + ? BorderSide.none + : BorderSide( + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 2, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius * 2, + ), + ), + onPressed: () { + setState(() { + // update toggle state + externalCallsEnabled = false; + }); + // call callback with newly set value + widget.onChanged?.call(externalCallsEnabled); + }, + child: Padding( + padding: const EdgeInsets.all( + 12, + ), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (isDesktop) + const SizedBox( + height: 10, + ), + SvgPicture.asset( + Assets.svg.personaIncognito, + width: 120, + height: 120, + ), + if (isDesktop) + const SizedBox( + height: 12, + ), + Center( + child: Text( + "Incognito", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.label700(context), + ), + ), + Center( + child: Text( + "Privacy conscious", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.label(context), + ), + ), + if (isDesktop) + const SizedBox( + height: 12, + ), + ], + ), + if (!externalCallsEnabled) + Positioned( + top: 4, + right: 4, + child: SvgPicture.asset( + Assets.svg.checkCircle, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + ), + if (externalCallsEnabled) + Positioned( + top: 4, + right: 4, + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(1000), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/desktop_settings_view.dart b/lib/pages_desktop_specific/home/desktop_settings_view.dart index 719a37a3a..34cb07f27 100644 --- a/lib/pages_desktop_specific/home/desktop_settings_view.dart +++ b/lib/pages_desktop_specific/home/desktop_settings_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages_desktop_specific/home/settings_menu/advanced_settings.dart'; +import 'package:stackwallet/pages_desktop_specific/home/advanced_settings/advanced_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/appearance_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/currency_settings.dart'; 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 index 372c86e2f..9f309a08e 100644 --- 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 @@ -1,10 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.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/assets.dart'; +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/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; class AddressBookAddressChooser extends StatefulWidget { const AddressBookAddressChooser({ @@ -20,6 +28,12 @@ class AddressBookAddressChooser extends StatefulWidget { } class _AddressBookAddressChooserState extends State { + late final bool isDesktop; + late final TextEditingController _searchController; + late final FocusNode searchFieldFocusNode; + + String _searchTerm = ""; + int _compareContactFavorite(Contact a, Contact b) { if (a.isFavorite && b.isFavorite) { return 0; @@ -43,29 +57,128 @@ class _AddressBookAddressChooserState extends State { return favorites; } - List filter(List contacts) { + List filter(List contacts, String searchTerm) { if (widget.coin != null) { contacts.removeWhere( (e) => e.addresses.where((a) => a.coin == widget.coin!).isEmpty); } + contacts.retainWhere((e) => _matches(searchTerm, e)); + if (contacts.length < 2) { return contacts; } + // redundant due to pullOutFavorites? contacts.sort(_compareContactFavorite); - // TODO: other filtering? - return contacts; } + bool _matches(String term, Contact contact) { + final text = term.toLowerCase(); + if (contact.name.toLowerCase().contains(text)) { + return true; + } + for (int i = 0; i < contact.addresses.length; i++) { + if (contact.addresses[i].label.toLowerCase().contains(text) || + contact.addresses[i].coin.name.toLowerCase().contains(text) || + contact.addresses[i].coin.prettyName.toLowerCase().contains(text) || + contact.addresses[i].coin.ticker.toLowerCase().contains(text) || + contact.addresses[i].address.toLowerCase().contains(text)) { + return true; + } + } + return false; + } + + @override + void initState() { + isDesktop = Util.isDesktop; + searchFieldFocusNode = FocusNode(); + _searchController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Column( children: [ // search field - const TextField(), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchTerm = value; + }); + }, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + searchFieldFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: EdgeInsets.symmetric( + horizontal: isDesktop ? 12 : 10, + vertical: isDesktop ? 18 : 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: isDesktop ? 20 : 16, + height: isDesktop ? 20 : 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchTerm = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), const SizedBox( height: 16, ), @@ -83,7 +196,7 @@ class _AddressBookAddressChooserState extends State { .select((value) => value.contacts)) .toList(); - contacts = filter(contacts); + contacts = filter(contacts, _searchTerm); final favorites = pullOutFavorites(contacts); @@ -96,6 +209,8 @@ class _AddressBookAddressChooserState extends State { itemBuilder: (context, index) { if (index == 0) { return Padding( + key: const Key( + "addressBookCAddressChooserFavoritesHeaderItemKey"), padding: const EdgeInsets.only( bottom: 10, ), @@ -108,12 +223,14 @@ class _AddressBookAddressChooserState extends State { } else if (index <= favorites.length) { final id = favorites[index - 1].id; return ContactListItem( - key: Key("contactCard_${id}_key"), + key: Key("contactContactListItem_${id}_key"), contactId: id, filterByCoin: widget.coin, ); } else if (index == favorites.length + 1) { return Padding( + key: const Key( + "addressBookCAddressChooserAllContactsHeaderItemKey"), padding: const EdgeInsets.symmetric( vertical: 10, ), @@ -126,7 +243,7 @@ class _AddressBookAddressChooserState extends State { } else { final id = contacts[index - favorites.length - 1].id; return ContactListItem( - key: Key("contactCard_${id}_key"), + key: Key("contactContactListItem_${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 index 593ae2bc4..e030f9882 100644 --- 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 @@ -10,7 +10,7 @@ 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 { +class ContactListItem extends ConsumerStatefulWidget { const ContactListItem({ Key? key, required this.contactId, @@ -21,7 +21,24 @@ class ContactListItem extends ConsumerWidget { final Coin? filterByCoin; @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => _ContactListItemState(); +} + +class _ContactListItemState extends ConsumerState { + late final String contactId; + late final Coin? filterByCoin; + + ExpandableState _state = ExpandableState.collapsed; + + @override + void initState() { + contactId = widget.contactId; + filterByCoin = widget.filterByCoin; + super.initState(); + } + + @override + Widget build(BuildContext context) { final contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(contactId))); @@ -29,6 +46,11 @@ class ContactListItem extends ConsumerWidget { padding: const EdgeInsets.all(0), borderColor: Theme.of(context).extension()!.background, child: Expandable( + onExpandChanged: (state) { + setState(() { + _state = state; + }); + }, header: Padding( padding: const EdgeInsets.symmetric( horizontal: 20, @@ -36,6 +58,7 @@ class ContactListItem extends ConsumerWidget { ), child: AddressBookCard( contactId: contactId, + indicatorDown: _state == ExpandableState.expanded, ), ), body: Column( 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 26f2f806c..710bc8685 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 @@ -1190,8 +1190,32 @@ class _DesktopSendState extends ConsumerState { builder: (context) => DesktopDialog( maxWidth: 696, maxHeight: 600, - child: AddressBookAddressChooser( - coin: coin, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Address book", + style: + STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: AddressBookAddressChooser( + coin: coin, + ), + ), + ], ), ), ); diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 136dc7a34..613e0d509 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -85,13 +85,13 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_sear import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/pages_desktop_specific/create_password/create_password_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/advanced_settings/advanced_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/my_stack_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart'; -import 'package:stackwallet/pages_desktop_specific/home/settings_menu/advanced_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/appearance_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart'; import 'package:stackwallet/pages_desktop_specific/home/settings_menu/currency_settings.dart'; diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 432ebbec9..78535c19b 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -96,6 +96,7 @@ class _SVG { String get qrcode => "assets/svg/qrcode1.svg"; String get ellipsis => "assets/svg/gear-3.svg"; String get chevronDown => "assets/svg/chevron-down.svg"; + String get chevronUp => "assets/svg/chevron-up.svg"; String get swap => "assets/svg/swap.svg"; String get downloadFolder => "assets/svg/folder-down.svg"; String get lock => "assets/svg/lock-keyhole.svg"; diff --git a/lib/widgets/address_book_card.dart b/lib/widgets/address_book_card.dart index 6a51edbad..cebcf166f 100644 --- a/lib/widgets/address_book_card.dart +++ b/lib/widgets/address_book_card.dart @@ -13,9 +13,14 @@ import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class AddressBookCard extends ConsumerStatefulWidget { - const AddressBookCard({Key? key, required this.contactId}) : super(key: key); + const AddressBookCard({ + Key? key, + required this.contactId, + this.indicatorDown, + }) : super(key: key); final String contactId; + final bool? indicatorDown; @override ConsumerState createState() => _AddressBookCardState(); @@ -122,7 +127,17 @@ class _AddressBookCardState extends ConsumerState { style: STextStyles.label(context), ), ], - ) + ), + if (isDesktop) const Spacer(), + if (isDesktop) + SvgPicture.asset( + widget.indicatorDown == true + ? Assets.svg.chevronDown + : Assets.svg.chevronUp, + width: 10, + height: 5, + color: Theme.of(context).extension()!.textSubtitle2, + ), ], ), builder: (child) => RoundedWhiteContainer( diff --git a/lib/widgets/transaction_card.dart b/lib/widgets/transaction_card.dart index 8867e34f6..15dcf2b4d 100644 --- a/lib/widgets/transaction_card.dart +++ b/lib/widgets/transaction_card.dart @@ -102,15 +102,13 @@ class _TransactionCardState extends ConsumerState { .select((value) => value.getPrice(coin))) .item1; - late final String prefix; + String prefix = ""; if (Util.isDesktop) { if (_transaction.txType == "Sent") { prefix = "-"; } else if (_transaction.txType == "Received") { prefix = "+"; } - } else { - prefix = ""; } return Material( diff --git a/pubspec.yaml b/pubspec.yaml index 7bd53de06..29d25cb84 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -232,6 +232,7 @@ flutter: - assets/svg/gear-3.svg - assets/svg/swap.svg - assets/svg/chevron-down.svg + - assets/svg/chevron-up.svg - assets/svg/lock-keyhole.svg - assets/svg/rotate-exclamation.svg - assets/svg/folder-down.svg diff --git a/test/widget_tests/address_book_card_test.dart b/test/widget_tests/address_book_card_test.dart index 8c8c44abd..07b1387df 100644 --- a/test/widget_tests/address_book_card_test.dart +++ b/test/widget_tests/address_book_card_test.dart @@ -1,6 +1,22 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/pages/address_book_views/subviews/contact_popup.dart'; +import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/services/address_book_service.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/theme/light_colors.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/address_book_card.dart'; + +import 'address_book_card_test.mocks.dart'; class MockedFunctions extends Mock { void showDialog(); @@ -8,46 +24,53 @@ class MockedFunctions extends Mock { @GenerateMocks([AddressBookService]) void main() { - // testWidgets('test returns Contact Address Entry', (widgetTester) async { - // final service = MockAddressBookService(); - // - // when(service.getContactById("default")) - // .thenAnswer((realInvocation) => Contact( - // name: "John Doe", - // addresses: [ - // const ContactAddressEntry( - // coin: Coin.bitcoincash, - // address: "some bch address", - // label: "Bills") - // ], - // isFavorite: true)); - // - // await widgetTester.pumpWidget( - // ProviderScope( - // overrides: [ - // addressBookServiceProvider.overrideWithValue( - // service, - // ), - // ], - // child: MaterialApp( - // theme: ThemeData( - // extensions: [ - // StackColors.fromStackColorTheme( - // LightColors(), - // ), - // ], - // ), - // home: const AddressBookCard( - // contactId: "default", - // ), - // ), - // ), - // ); - // - // expect(find.text("John Doe"), findsOneWidget); - // expect(find.text("BCH"), findsOneWidget); - // expect(find.text(Coin.bitcoincash.ticker), findsOneWidget); - // - // await widgetTester.tap(find.byType(RawMaterialButton)); - // }); + testWidgets('test returns Contact Address Entry', (widgetTester) async { + final service = MockAddressBookService(); + + when(service.getContactById("default")).thenAnswer( + (realInvocation) => Contact( + name: "John Doe", + addresses: [ + const ContactAddressEntry( + coin: Coin.bitcoincash, + address: "some bch address", + label: "Bills") + ], + isFavorite: true, + ), + ); + + await widgetTester.pumpWidget( + ProviderScope( + overrides: [ + addressBookServiceProvider.overrideWithValue( + service, + ), + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + LightColors(), + ), + ], + ), + home: const AddressBookCard( + contactId: "default", + ), + ), + ), + ); + + expect(find.text("John Doe"), findsOneWidget); + expect(find.text("BCH"), findsOneWidget); + expect(find.text(Coin.bitcoincash.ticker), findsOneWidget); + + if (Platform.isIOS || Platform.isAndroid) { + await widgetTester.tap(find.byType(RawMaterialButton)); + expect(find.byType(ContactPopUp), findsOneWidget); + } else if (Util.isDesktop) { + expect(find.byType(RawMaterialButton), findsNothing); + } + }); } diff --git a/test/widget_tests/transaction_card_test.dart b/test/widget_tests/transaction_card_test.dart index 6c36fce8d..f28c5f81d 100644 --- a/test/widget_tests/transaction_card_test.dart +++ b/test/widget_tests/transaction_card_test.dart @@ -1,4 +1,14 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockingjay/mockingjay.dart' as mockingjay; import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/models.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; +import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; @@ -6,7 +16,15 @@ import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/notes_service.dart'; import 'package:stackwallet/services/price_service.dart'; import 'package:stackwallet/services/wallets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/utilities/theme/light_colors.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/transaction_card.dart'; +import 'package:tuple/tuple.dart'; + +import 'transaction_card_test.mocks.dart'; @GenerateMocks([ Wallets, @@ -19,362 +37,370 @@ import 'package:stackwallet/utilities/prefs.dart'; NotesService ], customMocks: []) void main() { - // TestWidgetsFlutterBinding.ensureInitialized(); - // testWidgets("Sent confirmed tx displays correctly", (tester) async { - // final mockManager = MockManager(); - // final mockLocaleService = MockLocaleService(); - // final wallets = MockWallets(); - // final mockPrefs = MockPrefs(); - // final mockPriceService = MockPriceService(); - // - // final tx = Transaction( - // txid: "some txid", - // confirmedStatus: true, - // timestamp: 1648595998, - // txType: "Sent", - // amount: 100000000, - // aliens: [], - // worthNow: "0.01", - // worthAtBlockTimestamp: "0.01", - // fees: 3794, - // inputSize: 1, - // outputSize: 1, - // inputs: [], - // outputs: [], - // address: "", - // height: 450123, - // subType: "", - // confirmations: 10, - // isCancelled: false); - // - // final CoinServiceAPI wallet = MockFiroWallet(); - // - // when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); - // when(mockLocaleService.locale).thenAnswer((_) => "en_US"); - // when(mockPrefs.currency).thenAnswer((_) => "USD"); - // when(mockPrefs.externalCalls).thenAnswer((_) => true); - // when(mockPriceService.getPrice(Coin.firo)) - // .thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00)); - // - // when(wallet.coin).thenAnswer((_) => Coin.firo); - // - // when(wallets.getManager("wallet-id")) - // .thenAnswer((realInvocation) => Manager(wallet)); - // // - // await tester.pumpWidget( - // ProviderScope( - // overrides: [ - // walletsChangeNotifierProvider.overrideWithValue(wallets), - // localeServiceChangeNotifierProvider - // .overrideWithValue(mockLocaleService), - // prefsChangeNotifierProvider.overrideWithValue(mockPrefs), - // priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) - // ], - // child: MaterialApp( - // theme: ThemeData( - // extensions: [ - // StackColors.fromStackColorTheme( - // LightColors(), - // ), - // ], - // ), - // home: TransactionCard(transaction: tx, walletId: "wallet-id"), - // ), - // ), - // ); - // - // // - // final title = find.text("Sent"); - // // final price1 = find.text("0.00 USD"); - // final amount = find.text("1.00000000 FIRO"); - // - // final icon = find.byIcon(FeatherIcons.arrowUp); - // - // expect(title, findsOneWidget); - // // expect(price1, findsOneWidget); - // expect(amount, findsOneWidget); - // // expect(icon, findsOneWidget); - // // - // await tester.pumpAndSettle(Duration(seconds: 2)); - // // - // // final price2 = find.text("\$10.00"); - // // expect(price2, findsOneWidget); - // // - // // verify(mockManager.addListener(any)).called(1); - // verify(mockLocaleService.addListener(any)).called(1); - // - // verify(mockPrefs.currency).called(1); - // verify(mockPriceService.getPrice(Coin.firo)).called(1); - // verify(wallet.coin.ticker).called(1); - // - // verify(mockLocaleService.locale).called(1); - // - // verifyNoMoreInteractions(mockManager); - // verifyNoMoreInteractions(mockLocaleService); - // }); - // - // testWidgets("Anonymized confirmed tx displays correctly", (tester) async { - // final mockManager = MockManager(); - // final mockLocaleService = MockLocaleService(); - // final wallets = MockWallets(); - // final mockPrefs = MockPrefs(); - // final mockPriceService = MockPriceService(); - // - // final tx = Transaction( - // txid: "some txid", - // confirmedStatus: true, - // timestamp: 1648595998, - // txType: "Anonymized", - // amount: 100000000, - // aliens: [], - // worthNow: "0.01", - // worthAtBlockTimestamp: "0.01", - // fees: 3794, - // inputSize: 1, - // outputSize: 1, - // inputs: [], - // outputs: [], - // address: "", - // height: 450123, - // subType: "mint", - // confirmations: 10, - // isCancelled: false); - // - // final CoinServiceAPI wallet = MockFiroWallet(); - // - // when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); - // when(mockLocaleService.locale).thenAnswer((_) => "en_US"); - // when(mockPrefs.currency).thenAnswer((_) => "USD"); - // when(mockPrefs.externalCalls).thenAnswer((_) => true); - // when(mockPriceService.getPrice(Coin.firo)) - // .thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00)); - // - // when(wallet.coin).thenAnswer((_) => Coin.firo); - // - // when(wallets.getManager("wallet-id")) - // .thenAnswer((realInvocation) => Manager(wallet)); - // // - // await tester.pumpWidget( - // ProviderScope( - // overrides: [ - // walletsChangeNotifierProvider.overrideWithValue(wallets), - // localeServiceChangeNotifierProvider - // .overrideWithValue(mockLocaleService), - // prefsChangeNotifierProvider.overrideWithValue(mockPrefs), - // priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) - // ], - // child: MaterialApp( - // theme: ThemeData( - // extensions: [ - // StackColors.fromStackColorTheme( - // LightColors(), - // ), - // ], - // ), - // home: TransactionCard(transaction: tx, walletId: "wallet-id"), - // ), - // ), - // ); - // - // // - // final title = find.text("Anonymized"); - // // final price1 = find.text("0.00 USD"); - // final amount = find.text("1.00000000 FIRO"); - // - // final icon = find.byIcon(FeatherIcons.arrowUp); - // - // expect(title, findsOneWidget); - // // expect(price1, findsOneWidget); - // expect(amount, findsOneWidget); - // // expect(icon, findsOneWidget); - // // - // await tester.pumpAndSettle(Duration(seconds: 2)); - // // - // // final price2 = find.text("\$10.00"); - // // expect(price2, findsOneWidget); - // // - // // verify(mockManager.addListener(any)).called(1); - // verify(mockLocaleService.addListener(any)).called(1); - // - // verify(mockPrefs.currency).called(1); - // verify(mockPriceService.getPrice(Coin.firo)).called(1); - // verify(wallet.coin.ticker).called(1); - // - // verify(mockLocaleService.locale).called(1); - // - // verifyNoMoreInteractions(mockManager); - // verifyNoMoreInteractions(mockLocaleService); - // }); - // - // testWidgets("Received unconfirmed tx displays correctly", (tester) async { - // final mockManager = MockManager(); - // final mockLocaleService = MockLocaleService(); - // final wallets = MockWallets(); - // final mockPrefs = MockPrefs(); - // final mockPriceService = MockPriceService(); - // - // final tx = Transaction( - // txid: "some txid", - // confirmedStatus: false, - // timestamp: 1648595998, - // txType: "Received", - // amount: 100000000, - // aliens: [], - // worthNow: "0.01", - // worthAtBlockTimestamp: "0.01", - // fees: 3794, - // inputSize: 1, - // outputSize: 1, - // inputs: [], - // outputs: [], - // address: "", - // height: 0, - // subType: "", - // confirmations: 0, - // ); - // - // final CoinServiceAPI wallet = MockFiroWallet(); - // - // when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); - // when(mockLocaleService.locale).thenAnswer((_) => "en_US"); - // when(mockPrefs.currency).thenAnswer((_) => "USD"); - // when(mockPrefs.externalCalls).thenAnswer((_) => true); - // when(mockPriceService.getPrice(Coin.firo)) - // .thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00)); - // - // when(wallet.coin).thenAnswer((_) => Coin.firo); - // - // when(wallets.getManager("wallet-id")) - // .thenAnswer((realInvocation) => Manager(wallet)); - // - // await tester.pumpWidget( - // ProviderScope( - // overrides: [ - // walletsChangeNotifierProvider.overrideWithValue(wallets), - // localeServiceChangeNotifierProvider - // .overrideWithValue(mockLocaleService), - // prefsChangeNotifierProvider.overrideWithValue(mockPrefs), - // priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) - // ], - // child: MaterialApp( - // theme: ThemeData( - // extensions: [ - // StackColors.fromStackColorTheme( - // LightColors(), - // ), - // ], - // ), - // home: TransactionCard(transaction: tx, walletId: "wallet-id"), - // ), - // ), - // ); - // - // final title = find.text("Receiving"); - // final amount = find.text("1.00000000 FIRO"); - // - // expect(title, findsOneWidget); - // expect(amount, findsOneWidget); - // - // await tester.pumpAndSettle(Duration(seconds: 2)); - // - // verify(mockLocaleService.addListener(any)).called(1); - // - // verify(mockPrefs.currency).called(1); - // verify(mockPriceService.getPrice(Coin.firo)).called(1); - // verify(wallet.coin.ticker).called(1); - // - // verify(mockLocaleService.locale).called(1); - // - // verifyNoMoreInteractions(mockManager); - // verifyNoMoreInteractions(mockLocaleService); - // }); - // - // testWidgets("Tap gesture", (tester) async { - // final mockManager = MockManager(); - // final mockLocaleService = MockLocaleService(); - // final wallets = MockWallets(); - // final mockPrefs = MockPrefs(); - // final mockPriceService = MockPriceService(); - // final navigator = mockingjay.MockNavigator(); - // - // final tx = Transaction( - // txid: "some txid", - // confirmedStatus: false, - // timestamp: 1648595998, - // txType: "Received", - // amount: 100000000, - // aliens: [], - // worthNow: "0.01", - // worthAtBlockTimestamp: "0.01", - // fees: 3794, - // inputSize: 1, - // outputSize: 1, - // inputs: [], - // outputs: [], - // address: "", - // height: 250, - // subType: "", - // confirmations: 10, - // ); - // - // final CoinServiceAPI wallet = MockFiroWallet(); - // - // when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); - // when(mockLocaleService.locale).thenAnswer((_) => "en_US"); - // when(mockPrefs.currency).thenAnswer((_) => "USD"); - // when(mockPrefs.externalCalls).thenAnswer((_) => true); - // when(mockPriceService.getPrice(Coin.firo)) - // .thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00)); - // - // when(wallet.coin).thenAnswer((_) => Coin.firo); - // - // when(wallets.getManager("wallet id")) - // .thenAnswer((realInvocation) => Manager(wallet)); - // - // mockingjay - // .when(() => navigator.pushNamed("/transactionDetails", - // arguments: Tuple3(tx, Coin.firo, "wallet id"))) - // .thenAnswer((_) async => {}); - // - // await tester.pumpWidget( - // ProviderScope( - // overrides: [ - // walletsChangeNotifierProvider.overrideWithValue(wallets), - // localeServiceChangeNotifierProvider - // .overrideWithValue(mockLocaleService), - // prefsChangeNotifierProvider.overrideWithValue(mockPrefs), - // priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) - // ], - // child: MaterialApp( - // theme: ThemeData( - // extensions: [ - // StackColors.fromStackColorTheme(LightColors()), - // ], - // ), - // home: mockingjay.MockNavigatorProvider( - // navigator: navigator, - // child: TransactionCard(transaction: tx, walletId: "wallet id")), - // ), - // ), - // ); - // - // expect(find.byType(GestureDetector), findsOneWidget); - // - // await tester.tap(find.byType(GestureDetector)); - // await tester.pump(); - // - // verify(mockLocaleService.addListener(any)).called(1); - // - // verify(mockPrefs.currency).called(1); - // verify(mockLocaleService.locale).called(1); - // verify(wallet.coin.ticker).called(1); - // - // verifyNoMoreInteractions(wallet); - // verifyNoMoreInteractions(mockLocaleService); - // - // mockingjay - // .verify(() => navigator.pushNamed("/transactionDetails", - // arguments: Tuple3(tx, Coin.firo, "wallet id"))) - // .called(1); - // }); + TestWidgetsFlutterBinding.ensureInitialized(); + testWidgets("Sent confirmed tx displays correctly", (tester) async { + final mockManager = MockManager(); + final mockLocaleService = MockLocaleService(); + final wallets = MockWallets(); + final mockPrefs = MockPrefs(); + final mockPriceService = MockPriceService(); + + final tx = Transaction( + txid: "some txid", + confirmedStatus: true, + timestamp: 1648595998, + txType: "Sent", + amount: 100000000, + aliens: [], + worthNow: "0.01", + worthAtBlockTimestamp: "0.01", + fees: 3794, + inputSize: 1, + outputSize: 1, + inputs: [], + outputs: [], + address: "", + height: 450123, + subType: "", + confirmations: 10, + isCancelled: false); + + final CoinServiceAPI wallet = MockFiroWallet(); + + when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); + when(mockLocaleService.locale).thenAnswer((_) => "en_US"); + when(mockPrefs.currency).thenAnswer((_) => "USD"); + when(mockPrefs.externalCalls).thenAnswer((_) => true); + when(mockPriceService.getPrice(Coin.firo)) + .thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00)); + + when(wallet.coin).thenAnswer((_) => Coin.firo); + + when(wallets.getManager("wallet-id")) + .thenAnswer((realInvocation) => Manager(wallet)); + // + await tester.pumpWidget( + ProviderScope( + overrides: [ + walletsChangeNotifierProvider.overrideWithValue(wallets), + localeServiceChangeNotifierProvider + .overrideWithValue(mockLocaleService), + prefsChangeNotifierProvider.overrideWithValue(mockPrefs), + priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + LightColors(), + ), + ], + ), + home: TransactionCard(transaction: tx, walletId: "wallet-id"), + ), + ), + ); + + // + final title = find.text("Sent"); + // final price1 = find.text("0.00 USD"); + final amount = Util.isDesktop + ? find.text("-1.00000000 FIRO") + : find.text("1.00000000 FIRO"); + + final icon = find.byIcon(FeatherIcons.arrowUp); + + expect(title, findsOneWidget); + // expect(price1, findsOneWidget); + expect(amount, findsOneWidget); + // expect(icon, findsOneWidget); + // + await tester.pumpAndSettle(const Duration(seconds: 2)); + // + // final price2 = find.text("\$10.00"); + // expect(price2, findsOneWidget); + // + // verify(mockManager.addListener(any)).called(1); + verify(mockLocaleService.addListener(any)).called(1); + + verify(mockPrefs.currency).called(1); + verify(mockPriceService.getPrice(Coin.firo)).called(1); + verify(wallet.coin.ticker).called(1); + + verify(mockLocaleService.locale).called(1); + + verifyNoMoreInteractions(mockManager); + verifyNoMoreInteractions(mockLocaleService); + }); + + testWidgets("Anonymized confirmed tx displays correctly", (tester) async { + final mockManager = MockManager(); + final mockLocaleService = MockLocaleService(); + final wallets = MockWallets(); + final mockPrefs = MockPrefs(); + final mockPriceService = MockPriceService(); + + final tx = Transaction( + txid: "some txid", + confirmedStatus: true, + timestamp: 1648595998, + txType: "Anonymized", + amount: 100000000, + aliens: [], + worthNow: "0.01", + worthAtBlockTimestamp: "0.01", + fees: 3794, + inputSize: 1, + outputSize: 1, + inputs: [], + outputs: [], + address: "", + height: 450123, + subType: "mint", + confirmations: 10, + isCancelled: false); + + final CoinServiceAPI wallet = MockFiroWallet(); + + when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); + when(mockLocaleService.locale).thenAnswer((_) => "en_US"); + when(mockPrefs.currency).thenAnswer((_) => "USD"); + when(mockPrefs.externalCalls).thenAnswer((_) => true); + when(mockPriceService.getPrice(Coin.firo)) + .thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00)); + + when(wallet.coin).thenAnswer((_) => Coin.firo); + + when(wallets.getManager("wallet-id")) + .thenAnswer((realInvocation) => Manager(wallet)); + // + await tester.pumpWidget( + ProviderScope( + overrides: [ + walletsChangeNotifierProvider.overrideWithValue(wallets), + localeServiceChangeNotifierProvider + .overrideWithValue(mockLocaleService), + prefsChangeNotifierProvider.overrideWithValue(mockPrefs), + priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + LightColors(), + ), + ], + ), + home: TransactionCard(transaction: tx, walletId: "wallet-id"), + ), + ), + ); + + // + final title = find.text("Anonymized"); + // final price1 = find.text("0.00 USD"); + final amount = find.text("1.00000000 FIRO"); + + final icon = find.byIcon(FeatherIcons.arrowUp); + + expect(title, findsOneWidget); + // expect(price1, findsOneWidget); + expect(amount, findsOneWidget); + // expect(icon, findsOneWidget); + // + await tester.pumpAndSettle(const Duration(seconds: 2)); + // + // final price2 = find.text("\$10.00"); + // expect(price2, findsOneWidget); + // + // verify(mockManager.addListener(any)).called(1); + verify(mockLocaleService.addListener(any)).called(1); + + verify(mockPrefs.currency).called(1); + verify(mockPriceService.getPrice(Coin.firo)).called(1); + verify(wallet.coin.ticker).called(1); + + verify(mockLocaleService.locale).called(1); + + verifyNoMoreInteractions(mockManager); + verifyNoMoreInteractions(mockLocaleService); + }); + + testWidgets("Received unconfirmed tx displays correctly", (tester) async { + final mockManager = MockManager(); + final mockLocaleService = MockLocaleService(); + final wallets = MockWallets(); + final mockPrefs = MockPrefs(); + final mockPriceService = MockPriceService(); + + final tx = Transaction( + txid: "some txid", + confirmedStatus: false, + timestamp: 1648595998, + txType: "Received", + amount: 100000000, + aliens: [], + worthNow: "0.01", + worthAtBlockTimestamp: "0.01", + fees: 3794, + inputSize: 1, + outputSize: 1, + inputs: [], + outputs: [], + address: "", + height: 0, + subType: "", + confirmations: 0, + ); + + final CoinServiceAPI wallet = MockFiroWallet(); + + when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); + when(mockLocaleService.locale).thenAnswer((_) => "en_US"); + when(mockPrefs.currency).thenAnswer((_) => "USD"); + when(mockPrefs.externalCalls).thenAnswer((_) => true); + when(mockPriceService.getPrice(Coin.firo)) + .thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00)); + + when(wallet.coin).thenAnswer((_) => Coin.firo); + + when(wallets.getManager("wallet-id")) + .thenAnswer((realInvocation) => Manager(wallet)); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + walletsChangeNotifierProvider.overrideWithValue(wallets), + localeServiceChangeNotifierProvider + .overrideWithValue(mockLocaleService), + prefsChangeNotifierProvider.overrideWithValue(mockPrefs), + priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + LightColors(), + ), + ], + ), + home: TransactionCard(transaction: tx, walletId: "wallet-id"), + ), + ), + ); + + final title = find.text("Receiving"); + final amount = Util.isDesktop + ? find.text("+1.00000000 FIRO") + : find.text("1.00000000 FIRO"); + + expect(title, findsOneWidget); + expect(amount, findsOneWidget); + + await tester.pumpAndSettle(const Duration(seconds: 2)); + + verify(mockLocaleService.addListener(any)).called(1); + + verify(mockPrefs.currency).called(1); + verify(mockPriceService.getPrice(Coin.firo)).called(1); + verify(wallet.coin.ticker).called(1); + + verify(mockLocaleService.locale).called(1); + + verifyNoMoreInteractions(mockManager); + verifyNoMoreInteractions(mockLocaleService); + }); + + testWidgets("Tap gesture", (tester) async { + final mockManager = MockManager(); + final mockLocaleService = MockLocaleService(); + final wallets = MockWallets(); + final mockPrefs = MockPrefs(); + final mockPriceService = MockPriceService(); + final navigator = mockingjay.MockNavigator(); + + final tx = Transaction( + txid: "some txid", + confirmedStatus: false, + timestamp: 1648595998, + txType: "Received", + amount: 100000000, + aliens: [], + worthNow: "0.01", + worthAtBlockTimestamp: "0.01", + fees: 3794, + inputSize: 1, + outputSize: 1, + inputs: [], + outputs: [], + address: "", + height: 250, + subType: "", + confirmations: 10, + ); + + final CoinServiceAPI wallet = MockFiroWallet(); + + when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); + when(mockLocaleService.locale).thenAnswer((_) => "en_US"); + when(mockPrefs.currency).thenAnswer((_) => "USD"); + when(mockPrefs.externalCalls).thenAnswer((_) => true); + when(mockPriceService.getPrice(Coin.firo)) + .thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00)); + + when(wallet.coin).thenAnswer((_) => Coin.firo); + + when(wallets.getManager("wallet id")) + .thenAnswer((realInvocation) => Manager(wallet)); + + mockingjay + .when(() => navigator.pushNamed("/transactionDetails", + arguments: Tuple3(tx, Coin.firo, "wallet id"))) + .thenAnswer((_) async => {}); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + walletsChangeNotifierProvider.overrideWithValue(wallets), + localeServiceChangeNotifierProvider + .overrideWithValue(mockLocaleService), + prefsChangeNotifierProvider.overrideWithValue(mockPrefs), + priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme(LightColors()), + ], + ), + home: mockingjay.MockNavigatorProvider( + navigator: navigator, + child: TransactionCard(transaction: tx, walletId: "wallet id")), + ), + ), + ); + + expect(find.byType(GestureDetector), findsOneWidget); + + await tester.tap(find.byType(GestureDetector)); + await tester.pump(); + + verify(mockLocaleService.addListener(any)).called(1); + + verify(mockPrefs.currency).called(2); + verify(mockLocaleService.locale).called(4); + verify(wallet.coin.ticker).called(1); + + verifyNoMoreInteractions(wallet); + verifyNoMoreInteractions(mockLocaleService); + + if (Util.isDesktop) { + expect(find.byType(TransactionDetailsView), findsOneWidget); + } else { + mockingjay + .verify(() => navigator.pushNamed("/transactionDetails", + arguments: Tuple3(tx, Coin.firo, "wallet id"))) + .called(1); + } + }); }