diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4638e84dd..01cd71173 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,6 +23,7 @@ jobs: run: | cargo install cargo-ndk rustup target add x86_64-unknown-linux-gnu + sudo apt clean sudo apt update sudo apt install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm sudo apt install -y debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev diff --git a/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart index c98d53580..dc9935a03 100644 --- a/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart +++ b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart @@ -18,16 +18,28 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -class BuyWarningPopup extends StatelessWidget { - BuyWarningPopup({ +class BuyWarningPopup extends StatefulWidget { + const BuyWarningPopup({ Key? key, required this.quote, this.order, }) : super(key: key); - final SimplexQuote quote; + final SimplexOrder? order; + @override + State createState() => _BuyWarningPopupState(); +} + +class _BuyWarningPopupState extends State { + late final bool isDesktop; SimplexOrder? order; + String get title => "Buy ${widget.quote.crypto.ticker}"; + String get message => + "This purchase is provided and fulfilled by Simplex by nuvei " + "(a third party). You will be taken to their website. Please follow " + "their instructions."; + Future> newOrder(SimplexQuote quote) async { final orderResponse = await SimplexAPI.instance.newOrder(quote); @@ -38,174 +50,241 @@ class BuyWarningPopup extends StatelessWidget { return SimplexAPI.instance.redirect(order); } - @override - Widget build(BuildContext context) { - final isDesktop = Util.isDesktop; - - Future _buyInvoice() async { - await showDialog( - context: context, - // useRootNavigator: isDesktop, - builder: (context) { - return isDesktop - ? DesktopDialog( - maxHeight: 700, - maxWidth: 580, - child: Column( + Future _buyInvoice() async { + await showDialog( + context: context, + // useRootNavigator: isDesktop, + builder: (context) { + return isDesktop + ? DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Order details", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - ), - child: Text( - "Order details", - style: STextStyles.desktopH3(context), + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension()! + .background, + child: BuyOrderDetailsView( + order: order as SimplexOrder, + ), ), ), - const DesktopDialogCloseButton(), ], ), + ), + ), + ], + ), + ) + : BuyOrderDetailsView( + order: order as SimplexOrder, + ); + }, + ); + } + + Future onContinue() async { + BuyResponse orderResponse = await newOrder(widget.quote); + if (orderResponse.exception == null) { + await redirect(orderResponse.value as SimplexOrder) + .then((_response) async { + order = orderResponse.value as SimplexOrder; + Navigator.of(context, rootNavigator: isDesktop).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); + await _buyInvoice(); + }); + } else { + await showDialog( + context: context, + barrierDismissible: true, + builder: (context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Simplex API error", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 24, + ), + Text( + "${orderResponse.exception?.errorMessage}", + style: STextStyles.smallMed14(context), + ), + const SizedBox( + height: 56, + ), + Row( + children: [ + const Spacer(), Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: Row( - children: [ - Expanded( - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(16), - borderColor: Theme.of(context) - .extension()! - .background, - child: BuyOrderDetailsView( - order: order as SimplexOrder, - ), - ), - ), - ], - ), + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); // weee + }, ), ), ], - ), - ) - : BuyOrderDetailsView( - order: order as SimplexOrder, - ); - }); - } - - return StackDialog( - title: "Buy ${quote.crypto.ticker}", - message: "This purchase is provided and fulfilled by Simplex by nuvei " - "(a third party). You will be taken to their website. Please follow " - "their instructions.", - leftButton: SecondaryButton( - label: "Cancel", - onPressed: Navigator.of(context, rootNavigator: isDesktop).pop, - ), - rightButton: PrimaryButton( - label: "Continue", - onPressed: () async { - BuyResponse orderResponse = await newOrder(quote); - if (orderResponse.exception == null) { - await redirect(orderResponse.value as SimplexOrder) - .then((_response) async { - this.order = orderResponse.value as SimplexOrder; - Navigator.of(context, rootNavigator: isDesktop).pop(); - Navigator.of(context, rootNavigator: isDesktop).pop(); - await _buyInvoice(); - }); + ) + ], + ), + ), + ); } else { - await showDialog( - context: context, - barrierDismissible: true, - builder: (context) { - if (isDesktop) { - return DesktopDialog( - maxWidth: 450, - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Simplex API error", - style: STextStyles.desktopH3(context), - ), - const SizedBox( - height: 24, - ), - Text( - "${orderResponse.exception?.errorMessage}", - style: STextStyles.smallMed14(context), - ), - const SizedBox( - height: 56, - ), - Row( - children: [ - const Spacer(), - Expanded( - child: PrimaryButton( - buttonHeight: ButtonHeight.l, - label: "Ok", - onPressed: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - Navigator.of(context).pop(); // weee - }, - ), - ), - ], - ) - ], - ), - ), - ); - } else { - return StackDialog( - title: "Simplex API error", - message: "${orderResponse.exception?.errorMessage}", - // "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}", - rightButton: TextButton( - style: Theme.of(context) + return StackDialog( + title: "Simplex API error", + message: "${orderResponse.exception?.errorMessage}", + // "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}", + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) .extension()! - .getSecondaryEnabledButtonStyle(context), - child: Text( - "Ok", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - onPressed: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - Navigator.of(context).pop(); // weee - }, - ), - ); - } - }, + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); // weee + }, + ), ); } }, - ), - icon: SizedBox( - width: 64, - height: 32, - child: SvgPicture.asset( - Assets.buy.simplexLogo(context), + ); + } + } + + @override + void initState() { + order = widget.order; + isDesktop = Util.isDesktop; + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 350, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: STextStyles.desktopH3(context), + ), + SizedBox( + width: 64, + height: 32, + child: SvgPicture.asset( + Assets.buy.simplexLogo(context), + ), + ), + ], + ), + const Spacer(), + Text( + message, + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: + Navigator.of(context, rootNavigator: isDesktop).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: onContinue, + ), + ), + ], + ) + ], + ), ), - ), - ); + ); + } else { + return StackDialog( + title: title, + message: message, + leftButton: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context, rootNavigator: isDesktop).pop, + ), + rightButton: PrimaryButton( + label: "Continue", + onPressed: onContinue, + ), + icon: SizedBox( + width: 64, + height: 32, + child: SvgPicture.asset( + Assets.buy.simplexLogo(context), + ), + ), + ); + } } } diff --git a/lib/pages/paynym/paynym_home_view.dart b/lib/pages/paynym/paynym_home_view.dart index 96baa5daf..9292b8acb 100644 --- a/lib/pages/paynym/paynym_home_view.dart +++ b/lib/pages/paynym/paynym_home_view.dart @@ -201,7 +201,7 @@ class _PaynymHomeViewState extends ConsumerState { height: 20, color: Theme.of(context) .extension()! - .textDark, + .accentColorDark, ), onPressed: () { Navigator.of(context).pushNamed( @@ -223,7 +223,7 @@ class _PaynymHomeViewState extends ConsumerState { height: 20, color: Theme.of(context) .extension()! - .textDark, + .accentColorDark, ), onPressed: () { // todo info ? @@ -322,7 +322,7 @@ class _PaynymHomeViewState extends ConsumerState { height: 12, color: Theme.of(context) .extension()! - .textDark, + .buttonTextSecondary, ), onPressed: () async { await Clipboard.setData( @@ -359,7 +359,7 @@ class _PaynymHomeViewState extends ConsumerState { height: 12, color: Theme.of(context) .extension()! - .textDark, + .buttonTextSecondary, ), onPressed: () async { Rect? sharePositionOrigin; @@ -396,7 +396,7 @@ class _PaynymHomeViewState extends ConsumerState { height: 12, color: Theme.of(context) .extension()! - .textDark, + .buttonTextSecondary, ), onPressed: () { showDialog( diff --git a/lib/pages/paynym/subwidgets/paynym_followers_list.dart b/lib/pages/paynym/subwidgets/paynym_followers_list.dart index dbec19692..d8583954d 100644 --- a/lib/pages/paynym/subwidgets/paynym_followers_list.dart +++ b/lib/pages/paynym/subwidgets/paynym_followers_list.dart @@ -3,11 +3,17 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/paynym/subwidgets/paynym_card_button.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; +import 'package:stackwallet/utilities/logger.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 PaynymFollowersList extends ConsumerStatefulWidget { @@ -54,61 +60,97 @@ class _PaynymFollowersListState extends ConsumerState { ref.watch(myPaynymAccountStateProvider.state).state?.followers; final count = followers?.length ?? 0; - return ListView.separated( - itemCount: max(count, 1), - separatorBuilder: (BuildContext context, int index) => Container( - height: 1.5, - color: Colors.transparent, - ), - itemBuilder: (BuildContext context, int index) { - if (count == 0) { - return RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Your PayNym followers will appear here", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ) - : STextStyles.label(context), - ), - ], - ), - ); - } else if (count == 1) { - return RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: PaynymCardButton( - walletId: widget.walletId, - accountLite: followers![0], - ), - ); - } else { - BorderRadius? borderRadius; - if (index == 0) { - borderRadius = _borderRadiusFirst; - } else if (index == count - 1) { - borderRadius = _borderRadiusLast; - } + return ConditionalParent( + condition: !isDesktop, + builder: (child) => RefreshIndicator( + child: child, + onRefresh: () async { + try { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId); - return Container( - key: Key("paynymCardKey_${followers![index].nymId}"), - decoration: BoxDecoration( - borderRadius: borderRadius, - color: Theme.of(context).extension()!.popupBG, - ), - child: PaynymCardButton( - walletId: widget.walletId, - accountLite: followers[index], - ), - ); - } - }, + // get wallet to access paynym calls + final wallet = manager.wallet as PaynymWalletInterface; + + // get payment code + final pCode = await wallet.getPaymentCode( + DerivePathTypeExt.primaryFor(manager.coin), + ); + + // get account from api + final account = + await ref.read(paynymAPIProvider).nym(pCode.toString()); + + // update my account + if (account.value != null) { + ref.read(myPaynymAccountStateProvider.state).state = + account.value!; + } + } catch (e) { + Logging.instance.log( + "Failed pull down refresh of paynym home page: $e", + level: LogLevel.Warning, + ); + } + }, + ), + child: ListView.separated( + itemCount: max(count, 1), + separatorBuilder: (BuildContext context, int index) => Container( + height: 1.5, + color: Colors.transparent, + ), + itemBuilder: (BuildContext context, int index) { + if (count == 0) { + return RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Your PayNym followers will appear here", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.label(context), + ), + ], + ), + ); + } else if (count == 1) { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: PaynymCardButton( + walletId: widget.walletId, + accountLite: followers![0], + ), + ); + } else { + BorderRadius? borderRadius; + if (index == 0) { + borderRadius = _borderRadiusFirst; + } else if (index == count - 1) { + borderRadius = _borderRadiusLast; + } + + return Container( + key: Key("paynymCardKey_${followers![index].nymId}"), + decoration: BoxDecoration( + borderRadius: borderRadius, + color: Theme.of(context).extension()!.popupBG, + ), + child: PaynymCardButton( + walletId: widget.walletId, + accountLite: followers[index], + ), + ); + } + }, + ), ); } } diff --git a/lib/pages/paynym/subwidgets/paynym_following_list.dart b/lib/pages/paynym/subwidgets/paynym_following_list.dart index 754fd6a45..4d9df16ac 100644 --- a/lib/pages/paynym/subwidgets/paynym_following_list.dart +++ b/lib/pages/paynym/subwidgets/paynym_following_list.dart @@ -3,11 +3,17 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/paynym/subwidgets/paynym_card_button.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; +import 'package:stackwallet/utilities/logger.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 PaynymFollowingList extends ConsumerStatefulWidget { @@ -54,61 +60,97 @@ class _PaynymFollowingListState extends ConsumerState { ref.watch(myPaynymAccountStateProvider.state).state?.following; final count = following?.length ?? 0; - return ListView.separated( - itemCount: max(count, 1), - separatorBuilder: (BuildContext context, int index) => Container( - height: 1.5, - color: Colors.transparent, - ), - itemBuilder: (BuildContext context, int index) { - if (count == 0) { - return RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Your PayNym contacts will appear here", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ) - : STextStyles.label(context), - ), - ], - ), - ); - } else if (count == 1) { - return RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: PaynymCardButton( - walletId: widget.walletId, - accountLite: following![0], - ), - ); - } else { - BorderRadius? borderRadius; - if (index == 0) { - borderRadius = _borderRadiusFirst; - } else if (index == count - 1) { - borderRadius = _borderRadiusLast; - } + return ConditionalParent( + condition: !isDesktop, + builder: (child) => RefreshIndicator( + child: child, + onRefresh: () async { + try { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId); - return Container( - key: Key("paynymCardKey_${following![index].nymId}"), - decoration: BoxDecoration( - borderRadius: borderRadius, - color: Theme.of(context).extension()!.popupBG, - ), - child: PaynymCardButton( - walletId: widget.walletId, - accountLite: following[index], - ), - ); - } - }, + // get wallet to access paynym calls + final wallet = manager.wallet as PaynymWalletInterface; + + // get payment code + final pCode = await wallet.getPaymentCode( + DerivePathTypeExt.primaryFor(manager.coin), + ); + + // get account from api + final account = + await ref.read(paynymAPIProvider).nym(pCode.toString()); + + // update my account + if (account.value != null) { + ref.read(myPaynymAccountStateProvider.state).state = + account.value!; + } + } catch (e) { + Logging.instance.log( + "Failed pull down refresh of paynym home page: $e", + level: LogLevel.Warning, + ); + } + }, + ), + child: ListView.separated( + itemCount: max(count, 1), + separatorBuilder: (BuildContext context, int index) => Container( + height: 1.5, + color: Colors.transparent, + ), + itemBuilder: (BuildContext context, int index) { + if (count == 0) { + return RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Your PayNym contacts will appear here", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.label(context), + ), + ], + ), + ); + } else if (count == 1) { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: PaynymCardButton( + walletId: widget.walletId, + accountLite: following![0], + ), + ); + } else { + BorderRadius? borderRadius; + if (index == 0) { + borderRadius = _borderRadiusFirst; + } else if (index == count - 1) { + borderRadius = _borderRadiusLast; + } + + return Container( + key: Key("paynymCardKey_${following![index].nymId}"), + decoration: BoxDecoration( + borderRadius: borderRadius, + color: Theme.of(context).extension()!.popupBG, + ), + child: PaynymCardButton( + walletId: widget.walletId, + accountLite: following[index], + ), + ); + } + }, + ), ); } } diff --git a/lib/pages/receive_view/addresses/address_card.dart b/lib/pages/receive_view/addresses/address_card.dart new file mode 100644 index 000000000..1a47c256c --- /dev/null +++ b/lib/pages/receive_view/addresses/address_card.dart @@ -0,0 +1,132 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_qr_popup.dart'; +import 'package:stackwallet/pages/receive_view/addresses/edit_address_label_view.dart'; +import 'package:stackwallet/utilities/clipboard_interface.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/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/copy_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:tuple/tuple.dart'; + +class AddressCard extends StatelessWidget { + const AddressCard({ + Key? key, + required this.address, + required this.walletId, + required this.coin, + this.clipboard = const ClipboardWrapper(), + }) : super(key: key); + + final Address address; + final String walletId; + final Coin coin; + final ClipboardInterface clipboard; + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "TODO: label", + style: STextStyles.itemSubtitle(context), + ), + CustomTextButton( + text: "Edit label", + textSize: 14, + onTap: () { + Navigator.of(context).pushNamed( + EditAddressLabelView.routeName, + arguments: Tuple2( + address, + walletId, + ), + ); + }, + ), + ], + ), + const SizedBox( + height: 8, + ), + Row( + children: [ + Expanded( + child: SelectableText( + address.value, + style: STextStyles.itemSubtitle12(context), + ), + ) + ], + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Copy address", + icon: CopyIcon( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: () async { + await clipboard.setData( + ClipboardData( + text: address.value, + ), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + ), + ); + }, + ), + ), + const SizedBox( + width: 12, + ), + Expanded( + child: SecondaryButton( + label: "Show QR Code", + icon: QrCodeIcon( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => AddressQrPopup( + address: address, + coin: coin, + clipboard: clipboard, + ), + ); + }, + ), + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/pages/receive_view/addresses/address_qr_popup.dart b/lib/pages/receive_view/addresses/address_qr_popup.dart new file mode 100644 index 000000000..75b3509de --- /dev/null +++ b/lib/pages/receive_view/addresses/address_qr_popup.dart @@ -0,0 +1,193 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:ui' as ui; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/clipboard_interface.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/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class AddressQrPopup extends StatefulWidget { + const AddressQrPopup({ + Key? key, + required this.address, + required this.coin, + this.clipboard = const ClipboardWrapper(), + }) : super(key: key); + + final Address address; + final Coin coin; + final ClipboardInterface clipboard; + + @override + State createState() => _AddressQrPopupState(); +} + +class _AddressQrPopupState extends State { + final _qrKey = GlobalKey(); + final isDesktop = Util.isDesktop; + + Future _capturePng(bool shouldSaveInsteadOfShare) async { + try { + RenderRepaintBoundary boundary = + _qrKey.currentContext?.findRenderObject() as RenderRepaintBoundary; + ui.Image image = await boundary.toImage(); + ByteData? byteData = + await image.toByteData(format: ui.ImageByteFormat.png); + Uint8List pngBytes = byteData!.buffer.asUint8List(); + + if (shouldSaveInsteadOfShare) { + if (isDesktop) { + final dir = Directory("${Platform.environment['HOME']}"); + if (!dir.existsSync()) { + throw Exception( + "Home dir not found while trying to open filepicker on QR image save"); + } + final path = await FilePicker.platform.saveFile( + fileName: "qrcode.png", + initialDirectory: dir.path, + ); + + if (path != null) { + final file = File(path); + if (file.existsSync()) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "$path already exists!", + context: context, + ), + ); + } else { + await file.writeAsBytes(pngBytes); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "$path saved!", + context: context, + ), + ); + } + } + } else { + // await DocumentFileSavePlus.saveFile( + // pngBytes, + // "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png", + // "image/png"); + } + } else { + final tempDir = await getTemporaryDirectory(); + final file = await File("${tempDir.path}/qrcode.png").create(); + await file.writeAsBytes(pngBytes); + + await Share.shareFiles(["${tempDir.path}/qrcode.png"], + text: "Receive URI QR Code"); + } + } catch (e) { + //todo: comeback to this + debugPrint(e.toString()); + } + } + + @override + Widget build(BuildContext context) { + return StackDialogBase( + child: Column( + children: [ + Text( + "todo: custom label", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 8, + ), + Text( + widget.address.value, + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 16, + ), + Center( + child: RepaintBoundary( + key: _qrKey, + child: QrImage( + data: AddressUtils.buildUriString( + widget.coin, + widget.address.value, + {}, + ), + size: 220, + backgroundColor: + Theme.of(context).extension()!.popupBG, + foregroundColor: + Theme.of(context).extension()!.accentColorDark, + ), + ), + ), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + width: 170, + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () async { + await _capturePng(false); + }, + label: "Share", + icon: SvgPicture.asset( + Assets.svg.share, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + width: 170, + onPressed: () async { + await _capturePng(true); + }, + label: "Save", + icon: SvgPicture.asset( + Assets.svg.arrowDown, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + ), + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/pages/receive_view/addresses/edit_address_label_view.dart b/lib/pages/receive_view/addresses/edit_address_label_view.dart new file mode 100644 index 000000000..d44b61686 --- /dev/null +++ b/lib/pages/receive_view/addresses/edit_address_label_view.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/models/address/address.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/background.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_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.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 EditAddressLabelView extends ConsumerStatefulWidget { + const EditAddressLabelView({ + Key? key, + required this.address, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/editAddressLabel"; + + final Address address; + final String walletId; + + @override + ConsumerState createState() => + _EditAddressLabelViewState(); +} + +class _EditAddressLabelViewState extends ConsumerState { + late final TextEditingController _labelFieldController; + final labelFieldFocusNode = FocusNode(); + + late final bool isDesktop; + + @override + void initState() { + isDesktop = Util.isDesktop; + _labelFieldController = TextEditingController(); + _labelFieldController.text = "todo: address.label"; + super.initState(); + } + + @override + void dispose() { + _labelFieldController.dispose(); + labelFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: child, + ), + child: Scaffold( + backgroundColor: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.background, + appBar: isDesktop + ? null + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Edit label", + style: STextStyles.navBarTitle(context), + ), + ), + body: ConditionalParent( + condition: !isDesktop, + builder: (child) => Padding( + padding: const EdgeInsets.all(12), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), + ), + ); + }, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 12, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Edit label", + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + ), + Padding( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 32, + ) + : const EdgeInsets.all(0), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _labelFieldController, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + focusNode: labelFieldFocusNode, + decoration: standardInputDecoration( + "Address label", + labelFieldFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + contentPadding: isDesktop + ? const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ) + : null, + suffixIcon: _labelFieldController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _labelFieldController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + // if (!isDesktop) + const Spacer(), + if (isDesktop) + Padding( + padding: const EdgeInsets.all(32), + child: PrimaryButton( + label: "Save", + onPressed: () async { + // todo: update address + // await ref + // .read(notesServiceChangeNotifierProvider( + // widget.walletId)) + // .editOrAddNote( + // txid: widget.txid, + // note: _labelFieldController.text, + // ); + // if (mounted) { + // Navigator.of(context).pop(); + // } + }, + ), + ), + if (!isDesktop) + TextButton( + onPressed: () async { + // todo: update address + // await ref + // .read( + // notesServiceChangeNotifierProvider(widget.walletId)) + // .editOrAddNote( + // txid: widget.txid, + // note: _labelFieldController.text, + // ); + // if (mounted) { + // Navigator.of(context).pop(); + // } + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + child: Text( + "Save", + style: STextStyles.button(context), + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/receive_view/receiving_addresses_view.dart b/lib/pages/receive_view/addresses/receiving_addresses_view.dart similarity index 84% rename from lib/pages/receive_view/receiving_addresses_view.dart rename to lib/pages/receive_view/addresses/receiving_addresses_view.dart index e5f1d88bf..927043373 100644 --- a/lib/pages/receive_view/receiving_addresses_view.dart +++ b/lib/pages/receive_view/addresses/receiving_addresses_view.dart @@ -3,6 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:isar/isar.dart'; import 'package:stackwallet/db/main_db.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_card.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -10,7 +12,6 @@ import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; -import 'package:stackwallet/widgets/rounded_white_container.dart'; class ReceivingAddressesView extends ConsumerWidget { const ReceivingAddressesView({ @@ -28,6 +29,8 @@ class ReceivingAddressesView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final coin = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).coin)); return ConditionalParent( condition: !isDesktop, builder: (child) => Background( @@ -73,7 +76,9 @@ class ReceivingAddressesView extends ConsumerWidget { height: 10, ), itemBuilder: (_, index) => AddressCard( + walletId: walletId, address: snapshot.data![index], + coin: coin, ), ); } else { @@ -89,28 +94,3 @@ class ReceivingAddressesView extends ConsumerWidget { ); } } - -class AddressCard extends StatelessWidget { - const AddressCard({ - Key? key, - required this.address, - }) : super(key: key); - - final Address address; - - @override - Widget build(BuildContext context) { - return RoundedWhiteContainer( - child: Row( - children: [ - Expanded( - child: Text( - address.value, - style: STextStyles.itemSubtitle12(context), - ), - ) - ], - ), - ); - } -} diff --git a/lib/pages/receive_view/receive_view.dart b/lib/pages/receive_view/receive_view.dart index 2d7d11b45..52283111c 100644 --- a/lib/pages/receive_view/receive_view.dart +++ b/lib/pages/receive_view/receive_view.dart @@ -6,8 +6,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/receive_view/addresses/receiving_addresses_view.dart'; import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart'; -import 'package:stackwallet/pages/receive_view/receiving_addresses_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -187,11 +187,21 @@ class _ReceiveViewState extends ConsumerState { ); }, child: RoundedWhiteContainer( + boxShadow: [ + Theme.of(context) + .extension()! + .standardBoxShadow, + ], child: Material( color: Colors.transparent, - child: Text( - "Address list", - style: STextStyles.baseXS(context), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + ), + child: Text( + "Address list", + style: STextStyles.field(context), + ), ), ), ), diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index c472bcc11..eab3d0638 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -1338,6 +1338,7 @@ class _SendViewState extends ConsumerState { Constants.decimalPlacesForCoin( coin)); } + _cryptoAmountChanged(); }, ), ], diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 970246043..d1ed6a4bd 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -6,6 +6,7 @@ import 'package:stackwallet/models/buy/response_objects/quote.dart'; import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/isar/models/address/address.dart'; import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart'; @@ -44,9 +45,10 @@ import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart'; import 'package:stackwallet/pages/paynym/paynym_claim_view.dart'; import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; +import 'package:stackwallet/pages/receive_view/addresses/edit_address_label_view.dart'; +import 'package:stackwallet/pages/receive_view/addresses/receiving_addresses_view.dart'; import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart'; import 'package:stackwallet/pages/receive_view/receive_view.dart'; -import 'package:stackwallet/pages/receive_view/receiving_addresses_view.dart'; import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; import 'package:stackwallet/pages/send_view/send_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart'; @@ -474,6 +476,21 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case EditAddressLabelView.routeName: + if (args is Tuple2) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => EditAddressLabelView( + address: args.item1, + walletId: args.item2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case EditTradeNoteView.routeName: if (args is Tuple2) { return getRoute( diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index 3bf151ccc..60ba85895 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -1267,46 +1267,31 @@ class BitcoinWallet extends CoinServiceAPI // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - // final priceData = - // await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - // Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - // final locale = - // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - // final String worthNow = Format.localizedStringAsFixed( - // value: - // ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2), - // decimalPlaces: 2, - // locale: locale!); - // - // final tx = models.Transaction( - // txid: txData["txid"] as String, - // confirmedStatus: false, - // timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - // txType: "Sent", - // amount: txData["recipientAmt"] as int, - // worthNow: worthNow, - // worthAtBlockTimestamp: worthNow, - // fees: txData["fee"] as int, - // inputSize: 0, - // outputSize: 0, - // inputs: [], - // outputs: [], - // address: txData["address"] as String, - // height: -1, - // confirmations: 0, - // ); - // - // if (cachedTxData == null) { - // final data = await _fetchTransactionData(); - // _transactionData = Future(() => data); - // } - // - // final transactions = cachedTxData!.getAllTransactions(); - // transactions[tx.txid] = tx; - // cachedTxData = models.TransactionData.fromMap(transactions); - // _transactionData = Future(() => cachedTxData!); + final transaction = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: txData["recipientAmt"] as int, + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple4(transaction, [], [], address), + ], + walletId, + ); } @override diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 4a4ff041b..1963c89bc 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1200,46 +1200,31 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB { // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - // final priceData = - // await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - // Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - // final locale = - // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - // final String worthNow = Format.localizedStringAsFixed( - // value: - // ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2), - // decimalPlaces: 2, - // locale: locale!); - // - // final tx = models.Transaction( - // txid: txData["txid"] as String, - // confirmedStatus: false, - // timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - // txType: "Sent", - // amount: txData["recipientAmt"] as int, - // worthNow: worthNow, - // worthAtBlockTimestamp: worthNow, - // fees: txData["fee"] as int, - // inputSize: 0, - // outputSize: 0, - // inputs: [], - // outputs: [], - // address: txData["address"] as String, - // height: -1, - // confirmations: 0, - // ); - // - // if (cachedTxData == null) { - // final data = await _fetchTransactionData(); - // _transactionData = Future(() => data); - // } - // - // final transactions = cachedTxData!.getAllTransactions(); - // transactions[tx.txid] = tx; - // cachedTxData = models.TransactionData.fromMap(transactions); - // _transactionData = Future(() => cachedTxData!); + final transaction = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: txData["recipientAmt"] as int, + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple4(transaction, [], [], address), + ], + walletId, + ); } bool validateCashAddr(String cashAddr) { diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 85a2abc81..b67054b4c 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -1059,46 +1059,31 @@ class DogecoinWallet extends CoinServiceAPI // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - // final priceData = - // await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - // Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - // final locale = - // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - // final String worthNow = Format.localizedStringAsFixed( - // value: - // ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2), - // decimalPlaces: 2, - // locale: locale!); - // - // final tx = models.Transaction( - // txid: txData["txid"] as String, - // confirmedStatus: false, - // timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - // txType: "Sent", - // amount: txData["recipientAmt"] as int, - // worthNow: worthNow, - // worthAtBlockTimestamp: worthNow, - // fees: txData["fee"] as int, - // inputSize: 0, - // outputSize: 0, - // inputs: [], - // outputs: [], - // address: txData["address"] as String, - // height: -1, - // confirmations: 0, - // ); - // - // if (cachedTxData == null) { - // final data = await _fetchTransactionData(); - // _transactionData = Future(() => data); - // } - // - // final transactions = cachedTxData!.getAllTransactions(); - // transactions[tx.txid] = tx; - // cachedTxData = models.TransactionData.fromMap(transactions); - // _transactionData = Future(() => cachedTxData!); + final transaction = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: txData["recipientAmt"] as int, + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple4(transaction, [], [], address), + ], + walletId, + ); } @override diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 03e788e54..9f767dbba 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -818,44 +818,31 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive { // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - // final currentPrice = await firoPrice; - // final locale = - // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - // final String worthNow = Format.localizedStringAsFixed( - // value: - // ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2), - // decimalPlaces: 2, - // locale: locale!); - // - // final tx = models.Transaction( - // txid: txData["txid"] as String, - // confirmedStatus: false, - // timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - // txType: "Sent", - // amount: txData["recipientAmt"] as int, - // worthNow: worthNow, - // worthAtBlockTimestamp: worthNow, - // fees: txData["fee"] as int, - // inputSize: 0, - // outputSize: 0, - // inputs: [], - // outputs: [], - // address: txData["address"] as String, - // height: -1, - // confirmations: 0, - // ); - // - // if (cachedTxData == null) { - // final data = await _fetchTransactionData(); - // _transactionData = Future(() => data); - // } - // - // final transactions = cachedTxData!.getAllTransactions(); - // transactions[tx.txid] = tx; - // cachedTxData = models.TransactionData.fromMap(transactions); - // _transactionData = Future(() => cachedTxData!); + final transaction = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: txData["recipientAmt"] as int, + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple4(transaction, [], [], address), + ], + walletId, + ); } /// Holds the max fee that can be sent diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index ecbc9cbc0..03b569511 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -1214,46 +1214,31 @@ class LitecoinWallet extends CoinServiceAPI // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - // final priceData = - // await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - // Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - // final locale = - // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - // final String worthNow = Format.localizedStringAsFixed( - // value: - // ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2), - // decimalPlaces: 2, - // locale: locale!); - // - // final tx = models.Transaction( - // txid: txData["txid"] as String, - // confirmedStatus: false, - // timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - // txType: "Sent", - // amount: txData["recipientAmt"] as int, - // worthNow: worthNow, - // worthAtBlockTimestamp: worthNow, - // fees: txData["fee"] as int, - // inputSize: 0, - // outputSize: 0, - // inputs: [], - // outputs: [], - // address: txData["address"] as String, - // height: -1, - // confirmations: 0, - // ); - // - // if (cachedTxData == null) { - // final data = await _refreshTransactions(); - // _transactionData = Future(() => data); - // } - // - // final transactions = cachedTxData!.getAllTransactions(); - // transactions[tx.txid] = tx; - // cachedTxData = models.TransactionData.fromMap(transactions); - // _transactionData = Future(() => cachedTxData!); + final transaction = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: txData["recipientAmt"] as int, + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple4(transaction, [], [], address), + ], + walletId, + ); } @override diff --git a/lib/services/coins/manager.dart b/lib/services/coins/manager.dart index 892766cf5..203c2d05f 100644 --- a/lib/services/coins/manager.dart +++ b/lib/services/coins/manager.dart @@ -110,8 +110,15 @@ class Manager with ChangeNotifier { try { final txid = await _currentWallet.confirmSend(txData: txData); - txData["txid"] = txid; - await _currentWallet.updateSentCachedTxData(txData); + try { + txData["txid"] = txid; + await _currentWallet.updateSentCachedTxData(txData); + } catch (e, s) { + // do not rethrow as that would get handled as a send failure further up + // also this is not critical code and transaction should show up on \ + // refresh regardless + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + } notifyListeners(); return txid; diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 1ee89d95f..10852044b 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -1203,46 +1203,31 @@ class NamecoinWallet extends CoinServiceAPI // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - // final priceData = - // await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - // Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - // final locale = - // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - // final String worthNow = Format.localizedStringAsFixed( - // value: - // ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2), - // decimalPlaces: 2, - // locale: locale!); - // - // final tx = models.Transaction( - // txid: txData["txid"] as String, - // confirmedStatus: false, - // timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - // txType: "Sent", - // amount: txData["recipientAmt"] as int, - // worthNow: worthNow, - // worthAtBlockTimestamp: worthNow, - // fees: txData["fee"] as int, - // inputSize: 0, - // outputSize: 0, - // inputs: [], - // outputs: [], - // address: txData["address"] as String, - // height: -1, - // confirmations: 0, - // ); - // - // if (cachedTxData == null) { - // final data = await _refreshTransactions(); - // _transactionData = Future(() => data); - // } - // - // final transactions = cachedTxData!.getAllTransactions(); - // transactions[tx.txid] = tx; - // cachedTxData = models.TransactionData.fromMap(transactions); - // _transactionData = Future(() => cachedTxData!); + final transaction = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: txData["recipientAmt"] as int, + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple4(transaction, [], [], address), + ], + walletId, + ); } @override @@ -1438,17 +1423,21 @@ class NamecoinWallet extends CoinServiceAPI Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { - final features = await electrumXClient.getServerFeatures(); - Logging.instance.log("features: $features", level: LogLevel.Info); - switch (coin) { - case Coin.namecoin: - if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - throw Exception("genesis hash does not match main net!"); - } - break; - default: - throw Exception( - "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); + try { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.namecoin: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + default: + throw Exception( + "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); + } + } catch (e, s) { + Logging.instance.log("$e/n$s", level: LogLevel.Info); } } diff --git a/lib/services/coins/particl/particl_wallet.dart b/lib/services/coins/particl/particl_wallet.dart index 625b4d384..1f6c7a2ea 100644 --- a/lib/services/coins/particl/particl_wallet.dart +++ b/lib/services/coins/particl/particl_wallet.dart @@ -1131,46 +1131,31 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB { // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - // final priceData = - // await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - // Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - // final locale = - // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - // final String worthNow = Format.localizedStringAsFixed( - // value: - // ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2), - // decimalPlaces: 2, - // locale: locale!); - // - // final tx = models.Transaction( - // txid: txData["txid"] as String, - // confirmedStatus: false, - // timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - // txType: "Sent", - // amount: txData["recipientAmt"] as int, - // worthNow: worthNow, - // worthAtBlockTimestamp: worthNow, - // fees: txData["fee"] as int, - // inputSize: 0, - // outputSize: 0, - // inputs: [], - // outputs: [], - // address: txData["address"] as String, - // height: -1, - // confirmations: 0, - // ); - // - // if (cachedTxData == null) { - // final data = await _refreshTransactions(); - // _transactionData = Future(() => data); - // } else { - // final transactions = cachedTxData!.getAllTransactions(); - // transactions[tx.txid] = tx; - // cachedTxData = models.TransactionData.fromMap(transactions); - // _transactionData = Future(() => cachedTxData!); - // } + final transaction = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: txData["recipientAmt"] as int, + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple4(transaction, [], [], address), + ], + walletId, + ); } @override @@ -1353,17 +1338,21 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { - final features = await electrumXClient.getServerFeatures(); - Logging.instance.log("features: $features", level: LogLevel.Info); - switch (coin) { - case Coin.particl: - if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - throw Exception("genesis hash does not match main net!"); - } - break; - default: - throw Exception( - "Attempted to generate a ParticlWallet using a non particl coin type: ${coin.name}"); + try { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.particl: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + default: + throw Exception( + "Attempted to generate a ParticlWallet using a non particl coin type: ${coin.name}"); + } + } catch (e, s) { + Logging.instance.log("$e/n$s", level: LogLevel.Info); } } diff --git a/lib/utilities/theme/ocean_breeze_colors.dart b/lib/utilities/theme/ocean_breeze_colors.dart index 1ba64843d..f8bcaf0c1 100644 --- a/lib/utilities/theme/ocean_breeze_colors.dart +++ b/lib/utilities/theme/ocean_breeze_colors.dart @@ -94,7 +94,7 @@ class OceanBreezeColors extends StackColorTheme { @override Color get buttonTextPrimary => const Color(0xFFFFFFFF); @override - Color get buttonTextSecondary => const Color(0xFF232323); + Color get buttonTextSecondary => accentColorDark; @override Color get buttonTextPrimaryDisabled => const Color(0xFFFFFFFF); @override diff --git a/lib/widgets/rounded_container.dart b/lib/widgets/rounded_container.dart index 9f9bfbd8d..d689d9ca8 100644 --- a/lib/widgets/rounded_container.dart +++ b/lib/widgets/rounded_container.dart @@ -11,6 +11,7 @@ class RoundedContainer extends StatelessWidget { this.width, this.height, this.borderColor, + this.boxShadow, }) : super(key: key); final Widget? child; @@ -20,6 +21,7 @@ class RoundedContainer extends StatelessWidget { final double? width; final double? height; final Color? borderColor; + final List? boxShadow; @override Widget build(BuildContext context) { @@ -32,6 +34,7 @@ class RoundedContainer extends StatelessWidget { Constants.size.circularBorderRadius * radiusMultiplier, ), border: borderColor == null ? null : Border.all(color: borderColor!), + boxShadow: boxShadow, ), child: Padding( padding: padding, diff --git a/lib/widgets/rounded_white_container.dart b/lib/widgets/rounded_white_container.dart index 1173e95b1..2ade9b729 100644 --- a/lib/widgets/rounded_white_container.dart +++ b/lib/widgets/rounded_white_container.dart @@ -11,6 +11,7 @@ class RoundedWhiteContainer extends StatelessWidget { this.width, this.height, this.borderColor, + this.boxShadow, }) : super(key: key); final Widget? child; @@ -19,6 +20,7 @@ class RoundedWhiteContainer extends StatelessWidget { final double? width; final double? height; final Color? borderColor; + final List? boxShadow; @override Widget build(BuildContext context) { @@ -29,6 +31,7 @@ class RoundedWhiteContainer extends StatelessWidget { width: width, height: height, borderColor: borderColor, + boxShadow: boxShadow, child: child, ); }