2023-05-26 21:21:16 +00:00
/ *
* This file is part of Stack Wallet .
*
* Copyright ( c ) 2023 Cypher Stack
* All Rights Reserved .
* The code is distributed under GPLv3 license , see LICENSE file for details .
* Generated by Cypher Stack on 2023 - 05 - 26
*
* /
2023-01-15 23:26:05 +00:00
import ' dart:async ' ;
2023-01-16 00:09:11 +00:00
import ' package:decimal/decimal.dart ' ;
2023-01-10 21:25:20 +00:00
import ' package:flutter/material.dart ' ;
2023-01-15 23:26:05 +00:00
import ' package:flutter/services.dart ' ;
2023-01-10 21:25:20 +00:00
import ' package:flutter_riverpod/flutter_riverpod.dart ' ;
2023-01-13 19:14:56 +00:00
import ' package:flutter_svg/svg.dart ' ;
2023-01-21 01:34:54 +00:00
import ' package:intl/intl.dart ' ;
2024-05-23 17:26:58 +00:00
2024-05-23 00:37:06 +00:00
import ' ../../app_config.dart ' ;
import ' ../../models/buy/response_objects/crypto.dart ' ;
import ' ../../models/buy/response_objects/fiat.dart ' ;
import ' ../../models/buy/response_objects/quote.dart ' ;
import ' ../../models/contact_address_entry.dart ' ;
import ' ../../models/isar/models/ethereum/eth_contract.dart ' ;
import ' ../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart ' ;
import ' ../../providers/providers.dart ' ;
import ' ../../services/buy/buy_response.dart ' ;
import ' ../../services/buy/simplex/simplex_api.dart ' ;
import ' ../../themes/stack_colors.dart ' ;
import ' ../../utilities/address_utils.dart ' ;
import ' ../../utilities/assets.dart ' ;
import ' ../../utilities/barcode_scanner_interface.dart ' ;
import ' ../../utilities/clipboard_interface.dart ' ;
import ' ../../utilities/constants.dart ' ;
import ' ../../utilities/logger.dart ' ;
import ' ../../utilities/text_styles.dart ' ;
import ' ../../utilities/util.dart ' ;
import ' ../../wallets/crypto_currency/crypto_currency.dart ' ;
import ' ../../widgets/conditional_parent.dart ' ;
import ' ../../widgets/custom_buttons/blue_text_button.dart ' ;
import ' ../../widgets/custom_loading_overlay.dart ' ;
import ' ../../widgets/desktop/desktop_dialog.dart ' ;
import ' ../../widgets/desktop/desktop_dialog_close_button.dart ' ;
import ' ../../widgets/desktop/primary_button.dart ' ;
import ' ../../widgets/icon_widgets/addressbook_icon.dart ' ;
import ' ../../widgets/icon_widgets/clipboard_icon.dart ' ;
import ' ../../widgets/icon_widgets/qrcode_icon.dart ' ;
import ' ../../widgets/icon_widgets/x_icon.dart ' ;
import ' ../../widgets/rounded_container.dart ' ;
import ' ../../widgets/rounded_white_container.dart ' ;
import ' ../../widgets/stack_dialog.dart ' ;
import ' ../../widgets/stack_text_field.dart ' ;
import ' ../../widgets/textfield_icon_button.dart ' ;
2024-05-23 17:26:58 +00:00
import ' ../address_book_views/address_book_view.dart ' ;
import ' ../exchange_view/choose_from_stack_view.dart ' ;
import ' buy_quote_preview.dart ' ;
import ' sub_widgets/crypto_selection_view.dart ' ;
import ' sub_widgets/fiat_selection_view.dart ' ;
2023-01-10 21:25:20 +00:00
class BuyForm extends ConsumerStatefulWidget {
const BuyForm ( {
2024-05-15 21:20:45 +00:00
super . key ,
2023-01-27 19:47:41 +00:00
this . coin ,
2023-03-29 14:29:40 +00:00
this . tokenContract ,
2023-01-13 22:45:35 +00:00
this . clipboard = const ClipboardWrapper ( ) ,
this . scanner = const BarcodeScannerWrapper ( ) ,
2024-05-15 21:20:45 +00:00
} ) ;
2023-01-10 21:25:20 +00:00
2024-05-15 21:20:45 +00:00
final CryptoCurrency ? coin ;
2023-01-27 19:47:41 +00:00
2023-01-13 22:45:35 +00:00
final ClipboardInterface clipboard ;
final BarcodeScannerInterface scanner ;
2023-03-29 14:29:40 +00:00
final EthContract ? tokenContract ;
2023-01-10 21:25:20 +00:00
@ override
ConsumerState < BuyForm > createState ( ) = > _BuyFormState ( ) ;
}
class _BuyFormState extends ConsumerState < BuyForm > {
2024-05-15 21:20:45 +00:00
late final CryptoCurrency ? coin ;
2023-01-27 19:47:41 +00:00
2023-01-13 22:45:35 +00:00
late final ClipboardInterface clipboard ;
late final BarcodeScannerInterface scanner ;
2023-01-10 21:25:20 +00:00
2023-01-13 22:45:35 +00:00
late final TextEditingController _receiveAddressController ;
2023-01-14 00:07:27 +00:00
late final TextEditingController _buyAmountController ;
2023-01-13 22:45:35 +00:00
final FocusNode _receiveAddressFocusNode = FocusNode ( ) ;
2023-01-11 15:54:39 +00:00
final FocusNode _fiatFocusNode = FocusNode ( ) ;
final FocusNode _cryptoFocusNode = FocusNode ( ) ;
2023-01-14 00:07:27 +00:00
final FocusNode _buyAmountFocusNode = FocusNode ( ) ;
2023-01-11 15:54:39 +00:00
2023-01-13 22:45:35 +00:00
final isDesktop = Util . isDesktop ;
2023-01-14 00:07:27 +00:00
List < Crypto > ? coins ;
List < Fiat > ? fiats ;
2023-01-13 22:45:35 +00:00
String ? _address ;
2023-01-26 21:39:57 +00:00
static Fiat ? selectedFiat ;
static Crypto ? selectedCrypto ;
2023-01-16 23:38:42 +00:00
SimplexQuote quote = SimplexQuote (
2023-01-25 19:48:28 +00:00
crypto: Crypto . fromJson ( { ' ticker ' : ' BTC ' , ' name ' : ' Bitcoin ' } ) ,
fiat: Fiat . fromJson ( { ' ticker ' : ' USD ' , ' name ' : ' United States Dollar ' } ) ,
2023-01-16 23:38:42 +00:00
youPayFiatPrice: Decimal . parse ( " 100 " ) ,
youReceiveCryptoAmount: Decimal . parse ( " 1.0238917 " ) ,
2023-01-19 23:47:27 +00:00
id: " someID " ,
2023-01-16 23:38:42 +00:00
receivingAddress: ' ' ,
2023-01-19 19:36:05 +00:00
buyWithFiat: true ,
2023-01-16 23:38:42 +00:00
) ; // TODO enum this or something
2023-01-15 23:26:05 +00:00
2023-01-26 21:39:57 +00:00
static bool buyWithFiat = true ;
2023-01-13 22:45:35 +00:00
bool _addressToggleFlag = false ;
2023-01-13 19:14:56 +00:00
bool _hovering1 = false ;
bool _hovering2 = false ;
2023-01-11 22:17:46 +00:00
2023-01-27 22:18:13 +00:00
// TODO actually check USD min and max, these could get updated by Simplex
2023-01-26 21:39:57 +00:00
static Decimal minFiat = Decimal . fromInt ( 50 ) ;
static Decimal maxFiat = Decimal . fromInt ( 20000 ) ;
2023-01-26 16:32:47 +00:00
2023-03-29 14:30:56 +00:00
// // We can't get crypto min and max without asking for a quote
// static Decimal minCrypto = Decimal.parse((0.00000001)
// .toString()); // lol how to go from double->Decimal more easily?
// static Decimal maxCrypto = Decimal.parse((10000.00000000).toString());
// static String boundedCryptoTicker = '';
2023-01-26 21:03:54 +00:00
2023-01-30 19:08:44 +00:00
String _amountOutOfRangeErrorString = " " ;
void validateAmount ( ) {
if ( _buyAmountController . text . isEmpty ) {
setState ( ( ) {
_amountOutOfRangeErrorString = " " ;
} ) ;
return ;
}
final value = Decimal . tryParse ( _buyAmountController . text ) ;
if ( value = = null ) {
setState ( ( ) {
_amountOutOfRangeErrorString = " Invalid amount " ;
} ) ;
2023-01-30 19:40:40 +00:00
} else if ( value > maxFiat & & buyWithFiat ) {
2023-01-30 19:08:44 +00:00
setState ( ( ) {
_amountOutOfRangeErrorString =
" Maximum amount: ${ maxFiat . toStringAsFixed ( 2 ) } " ;
} ) ;
2023-01-30 19:40:40 +00:00
} else if ( value < minFiat & & buyWithFiat ) {
2023-01-30 19:08:44 +00:00
setState ( ( ) {
_amountOutOfRangeErrorString =
" Minimum amount: ${ minFiat . toStringAsFixed ( 2 ) } " ;
} ) ;
} else {
setState ( ( ) {
_amountOutOfRangeErrorString = " " ;
} ) ;
}
}
2023-01-11 17:49:59 +00:00
void selectCrypto ( ) async {
2023-01-16 21:07:45 +00:00
if ( ref . read ( simplexProvider ) . supportedCryptos . isEmpty ) {
2023-01-15 23:26:05 +00:00
bool shouldPop = false ;
unawaited (
showDialog (
context: context ,
builder: ( context ) = > WillPopScope (
child: const CustomLoadingOverlay (
message: " Loading currency data " ,
eventBus: null ,
) ,
onWillPop: ( ) async = > shouldPop ,
) ,
) ,
) ;
2023-01-20 21:30:35 +00:00
await _loadSimplexCryptos ( ) ;
2023-01-15 23:26:05 +00:00
shouldPop = true ;
if ( mounted ) {
2023-01-19 20:20:27 +00:00
Navigator . of ( context , rootNavigator: isDesktop ) . pop ( ) ;
2023-01-15 23:26:05 +00:00
}
}
2023-01-11 17:19:19 +00:00
await _showFloatingCryptoSelectionSheet (
2023-01-16 21:07:45 +00:00
coins: ref . read ( simplexProvider ) . supportedCryptos ,
2023-01-15 23:26:05 +00:00
onSelected: ( crypto ) {
setState ( ( ) {
2023-03-29 14:30:56 +00:00
// if (selectedCrypto?.ticker != _BuyFormState.boundedCryptoTicker) {
// // Reset crypto mins and maxes ... we don't know these bounds until we request a quote
// _BuyFormState.minCrypto = Decimal.parse((0.00000001)
// .toString()); // lol how to go from double->Decimal more easily?
// _BuyFormState.maxCrypto =
// Decimal.parse((10000.00000000).toString());
// }
2023-01-15 23:26:05 +00:00
selectedCrypto = crypto ;
} ) ;
} ,
) ;
2023-01-11 17:19:19 +00:00
}
Future < void > _showFloatingCryptoSelectionSheet ( {
required List < Crypto > coins ,
required void Function ( Crypto ) onSelected ,
} ) async {
_fiatFocusNode . unfocus ( ) ;
_cryptoFocusNode . unfocus ( ) ;
final result = isDesktop
? await showDialog < Crypto ? > (
context: context ,
builder: ( context ) {
return DesktopDialog (
maxHeight: 700 ,
maxWidth: 580 ,
child: Column (
children: [
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Padding (
padding: const EdgeInsets . only (
left: 32 ,
) ,
child: Text (
2023-01-14 00:07:27 +00:00
" Choose a crypto to buy " ,
2023-01-11 17:19:19 +00:00
style: STextStyles . desktopH3 ( context ) ,
) ,
) ,
const DesktopDialogCloseButton ( ) ,
] ,
) ,
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 < StackColors > ( ) !
. background ,
2023-01-14 00:07:27 +00:00
child: CryptoSelectionView (
coins: coins ,
2023-01-11 17:19:19 +00:00
) ,
) ,
) ,
] ,
) ,
) ,
) ,
] ,
) ,
) ;
2024-05-15 21:20:45 +00:00
} ,
)
2023-01-11 17:19:19 +00:00
: await Navigator . of ( context ) . push (
MaterialPageRoute < dynamic > (
2023-01-14 00:07:27 +00:00
builder: ( _ ) = > CryptoSelectionView (
coins: coins ,
2023-01-11 17:19:19 +00:00
) ,
) ,
) ;
2023-01-14 00:07:27 +00:00
if ( mounted & & result is Crypto ) {
2023-01-11 17:19:19 +00:00
onSelected ( result ) ;
}
}
2023-01-15 23:26:05 +00:00
Future < void > selectFiat ( ) async {
2023-01-16 21:07:45 +00:00
if ( ref . read ( simplexProvider ) . supportedFiats . isEmpty ) {
2023-01-15 23:26:05 +00:00
bool shouldPop = false ;
unawaited (
showDialog (
context: context ,
builder: ( context ) = > WillPopScope (
child: const CustomLoadingOverlay (
message: " Loading currency data " ,
eventBus: null ,
) ,
onWillPop: ( ) async = > shouldPop ,
) ,
) ,
) ;
2023-01-20 21:30:35 +00:00
await _loadSimplexFiats ( ) ;
2023-01-15 23:26:05 +00:00
shouldPop = true ;
if ( mounted ) {
2023-01-19 20:20:27 +00:00
Navigator . of ( context , rootNavigator: isDesktop ) . pop ( ) ;
2023-01-15 23:26:05 +00:00
}
}
2023-01-11 17:49:59 +00:00
2023-01-13 22:45:35 +00:00
await _showFloatingFiatSelectionSheet (
2023-01-16 21:07:45 +00:00
fiats: ref . read ( simplexProvider ) . supportedFiats ,
2023-01-15 23:26:05 +00:00
onSelected: ( fiat ) {
setState ( ( ) {
selectedFiat = fiat ;
2023-01-26 16:32:47 +00:00
minFiat = fiat . minAmount ! = minFiat ? fiat . minAmount : minFiat ;
maxFiat = fiat . maxAmount ! = maxFiat ? fiat . maxAmount : maxFiat ;
2023-01-15 23:26:05 +00:00
} ) ;
2023-01-30 19:40:40 +00:00
validateAmount ( ) ;
2023-01-15 23:26:05 +00:00
} ,
) ;
}
2023-01-20 21:30:35 +00:00
Future < void > _loadSimplexCryptos ( ) async {
final response = await SimplexAPI . instance . getSupportedCryptos ( ) ;
if ( response . value ! = null ) {
2023-01-23 20:24:06 +00:00
ref
. read ( simplexProvider )
. updateSupportedCryptos ( response . value ! ) ; // TODO validate
2023-01-20 21:30:35 +00:00
} else {
Logging . instance . log (
" _loadSimplexCurrencies: $ response " ,
level: LogLevel . Warning ,
) ;
}
}
Future < void > _loadSimplexFiats ( ) async {
final response = await SimplexAPI . instance . getSupportedFiats ( ) ;
2023-01-15 23:26:05 +00:00
if ( response . value ! = null ) {
2023-01-23 20:24:06 +00:00
ref
. read ( simplexProvider )
. updateSupportedFiats ( response . value ! ) ; // TODO validate
2023-01-15 23:26:05 +00:00
} else {
Logging . instance . log (
" _loadSimplexCurrencies: $ response " ,
level: LogLevel . Warning ,
) ;
}
2023-01-11 17:49:59 +00:00
}
Future < void > _showFloatingFiatSelectionSheet ( {
required List < Fiat > fiats ,
required void Function ( Fiat ) onSelected ,
} ) async {
_fiatFocusNode . unfocus ( ) ;
_cryptoFocusNode . unfocus ( ) ;
final result = isDesktop
2023-01-13 22:50:49 +00:00
? await showDialog < Fiat ? > (
2023-01-11 17:49:59 +00:00
context: context ,
builder: ( context ) {
return DesktopDialog (
maxHeight: 700 ,
maxWidth: 580 ,
child: Column (
children: [
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Padding (
padding: const EdgeInsets . only (
left: 32 ,
) ,
child: Text (
" Choose a fiat with which to pay " ,
style: STextStyles . desktopH3 ( context ) ,
) ,
) ,
const DesktopDialogCloseButton ( ) ,
] ,
) ,
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 < StackColors > ( ) !
. background ,
child: FiatSelectionView (
2023-01-13 22:45:35 +00:00
fiats: fiats ,
2023-01-11 17:49:59 +00:00
) ,
) ,
) ,
] ,
) ,
) ,
) ,
] ,
) ,
) ;
2024-05-15 21:20:45 +00:00
} ,
)
2023-01-11 17:49:59 +00:00
: await Navigator . of ( context ) . push (
MaterialPageRoute < dynamic > (
builder: ( _ ) = > FiatSelectionView (
2023-01-13 22:45:35 +00:00
fiats: fiats ,
2023-01-11 17:49:59 +00:00
) ,
) ,
) ;
if ( mounted & & result is Fiat ) {
onSelected ( result ) ;
}
}
2023-01-30 19:08:44 +00:00
// String? _fetchIconUrlFromTicker(String? ticker) {
// if (ticker == null) return null;
//
// return null;
// }
2023-01-11 17:19:19 +00:00
2023-01-16 23:38:42 +00:00
Future < void > previewQuote ( SimplexQuote quote ) async {
bool shouldPop = false ;
unawaited (
showDialog (
context: context ,
builder: ( context ) = > WillPopScope (
child: const CustomLoadingOverlay (
message: " Loading quote data " ,
eventBus: null ,
) ,
onWillPop: ( ) async = > shouldPop ,
) ,
) ,
) ;
quote = SimplexQuote (
crypto: selectedCrypto ! ,
fiat: selectedFiat ! ,
2023-01-17 00:08:37 +00:00
youPayFiatPrice: buyWithFiat
? Decimal . parse ( _buyAmountController . text )
: Decimal . parse ( " 100 " ) , // dummy value
youReceiveCryptoAmount: buyWithFiat
? Decimal . parse ( " 0.000420282 " ) // dummy value
: Decimal . parse ( _buyAmountController . text ) , // Ternary for this
2023-01-19 23:47:27 +00:00
id: " id " , // anything; we get an ID back
2023-01-16 23:38:42 +00:00
receivingAddress: _receiveAddressController . text ,
2023-01-19 19:36:05 +00:00
buyWithFiat: buyWithFiat ,
2023-01-16 23:38:42 +00:00
) ;
2024-05-27 23:56:22 +00:00
final BuyResponse < SimplexQuote > quoteResponse = await _loadQuote ( quote ) ;
2023-01-16 23:38:42 +00:00
shouldPop = true ;
if ( mounted ) {
2023-01-19 20:20:27 +00:00
Navigator . of ( context , rootNavigator: isDesktop ) . pop ( ) ;
2023-01-16 23:38:42 +00:00
}
2023-01-26 20:20:45 +00:00
if ( quoteResponse . exception = = null ) {
quote = quoteResponse . value as SimplexQuote ;
if ( quote . id ! = ' id ' & & quote . id ! = ' someID ' ) {
// TODO detect default quote better
await _showFloatingBuyQuotePreviewSheet (
quote: ref . read ( simplexProvider ) . quote ,
onSelected: ( quote ) {
// TODO launch URL
} ,
) ;
2023-03-29 14:29:40 +00:00
} else if ( mounted ) {
2023-01-26 20:20:45 +00:00
await showDialog < dynamic > (
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 unresponsive " ,
style: STextStyles . desktopH3 ( context ) ,
) ,
const SizedBox (
height: 24 ,
) ,
Text (
" Simplex API unresponsive, please try again later " ,
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 ,
) ,
) ,
] ,
2024-05-15 21:20:45 +00:00
) ,
2023-01-26 20:20:45 +00:00
] ,
) ,
) ,
) ;
} else {
return StackDialog (
title: " Simplex API error " ,
2023-01-27 21:10:59 +00:00
message: " ${ quoteResponse . exception ? . errorMessage } " ,
2023-01-26 20:20:45 +00:00
rightButton: TextButton (
style: Theme . of ( context )
. extension < StackColors > ( ) !
. getSecondaryEnabledButtonStyle ( context ) ,
child: Text (
" Ok " ,
style: STextStyles . button ( context ) . copyWith (
2024-05-15 21:20:45 +00:00
color: Theme . of ( context )
. extension < StackColors > ( ) !
. accentColorDark ,
) ,
2023-01-26 20:20:45 +00:00
) ,
onPressed: ( ) {
Navigator . of ( context ) . pop ( ) ;
} ,
) ,
) ;
}
} ,
) ;
}
2023-03-29 14:29:40 +00:00
} else if ( mounted ) {
2023-01-26 21:03:54 +00:00
// Error; probably amount out of bounds
2023-01-30 20:08:21 +00:00
// String errorMessage = "${quoteResponse.exception?.errorMessage}";
// if (errorMessage.contains('must be between')) {
// errorMessage = errorMessage.substring(
// errorMessage.indexOf('getQuote exception: ') + 20,
// errorMessage.indexOf(", value: null"));
// _BuyFormState.boundedCryptoTicker = errorMessage.substring(
// errorMessage.indexOf('The ') + 4,
// errorMessage.indexOf(' amount must be between'));
// _BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
// errorMessage.indexOf('must be between ') + 16,
// errorMessage.indexOf(' and ')));
// _BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
// errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
// errorMessage.length));
// if (Decimal.parse(_buyAmountController.text) >
// _BuyFormState.maxCrypto) {
// _buyAmountController.text = _BuyFormState.maxCrypto.toString();
// }
// }
2023-01-26 15:56:48 +00:00
await showDialog < dynamic > (
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 (
2023-01-26 20:20:45 +00:00
" Simplex API error " ,
2023-01-26 15:56:48 +00:00
style: STextStyles . desktopH3 ( context ) ,
) ,
const SizedBox (
height: 24 ,
) ,
Text (
2023-01-30 20:08:21 +00:00
quoteResponse . exception ! . errorMessage ,
2023-01-26 15:56:48 +00:00
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 ,
) ,
) ,
] ,
2024-05-15 21:20:45 +00:00
) ,
2023-01-26 15:56:48 +00:00
] ,
) ,
) ,
) ;
} else {
return StackDialog (
2023-01-26 20:20:45 +00:00
title: " Simplex API error " ,
2023-01-27 21:10:59 +00:00
message: " ${ quoteResponse . exception ? . errorMessage } " ,
// "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}",
2023-01-26 15:56:48 +00:00
rightButton: TextButton (
style: Theme . of ( context )
. extension < StackColors > ( ) !
. getSecondaryEnabledButtonStyle ( context ) ,
child: Text (
" Ok " ,
style: STextStyles . button ( context ) . copyWith (
2024-05-15 21:20:45 +00:00
color: Theme . of ( context )
. extension < StackColors > ( ) !
. accentColorDark ,
) ,
2023-01-26 15:56:48 +00:00
) ,
onPressed: ( ) {
Navigator . of ( context ) . pop ( ) ;
} ,
) ,
) ;
}
} ,
) ;
}
2023-01-16 21:31:53 +00:00
}
2023-01-26 20:20:45 +00:00
Future < BuyResponse < SimplexQuote > > _loadQuote ( SimplexQuote quote ) async {
2023-01-16 23:38:42 +00:00
final response = await SimplexAPI . instance . getQuote ( quote ) ;
if ( response . value ! = null ) {
2023-01-26 20:20:45 +00:00
// TODO check for error key
2023-01-16 23:38:42 +00:00
ref . read ( simplexProvider ) . updateQuote ( response . value ! ) ;
2023-01-26 20:20:45 +00:00
return BuyResponse ( value: response . value ! ) ;
2023-01-16 23:38:42 +00:00
} else {
Logging . instance . log (
" _loadQuote: $ response " ,
level: LogLevel . Warning ,
) ;
2023-01-26 20:20:45 +00:00
return BuyResponse (
2023-01-30 20:08:21 +00:00
exception: response . exception ? ?
BuyException (
response . toString ( ) ,
BuyExceptionType . generic ,
) ,
2023-01-26 20:20:45 +00:00
) ;
2023-01-16 23:38:42 +00:00
}
}
2023-01-16 21:31:53 +00:00
Future < void > _showFloatingBuyQuotePreviewSheet ( {
required SimplexQuote quote ,
required void Function ( SimplexQuote ) onSelected ,
} ) async {
_fiatFocusNode . unfocus ( ) ;
_cryptoFocusNode . unfocus ( ) ;
final result = isDesktop
? await showDialog < Fiat ? > (
context: context ,
builder: ( context ) {
return DesktopDialog (
maxHeight: 700 ,
maxWidth: 580 ,
child: Column (
children: [
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Padding (
padding: const EdgeInsets . only (
left: 32 ,
) ,
child: Text (
2023-01-16 22:12:37 +00:00
" Preview quote " ,
2023-01-16 21:31:53 +00:00
style: STextStyles . desktopH3 ( context ) ,
) ,
) ,
const DesktopDialogCloseButton ( ) ,
] ,
) ,
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 < StackColors > ( ) !
. background ,
child: BuyQuotePreviewView (
quote: quote ,
) ,
) ,
) ,
] ,
) ,
) ,
) ,
] ,
) ,
) ;
2024-05-15 21:20:45 +00:00
} ,
)
2023-01-16 21:31:53 +00:00
: await Navigator . of ( context ) . push (
MaterialPageRoute < dynamic > (
builder: ( _ ) = > BuyQuotePreviewView (
quote: quote ,
) ,
) ,
) ;
if ( mounted & & result is SimplexQuote ) {
onSelected ( result ) ;
}
}
2023-01-11 15:54:39 +00:00
@ override
void initState ( ) {
2023-01-13 22:45:35 +00:00
_receiveAddressController = TextEditingController ( ) ;
2023-01-14 00:07:27 +00:00
_buyAmountController = TextEditingController ( ) ;
2023-01-11 15:54:39 +00:00
2023-01-13 22:45:35 +00:00
clipboard = widget . clipboard ;
scanner = widget . scanner ;
2023-01-16 21:07:45 +00:00
coins = ref . read ( simplexProvider ) . supportedCryptos ;
fiats = ref . read ( simplexProvider ) . supportedFiats ;
2023-01-16 23:38:42 +00:00
// quote = ref.read(simplexProvider).quote;
quote = SimplexQuote (
2023-01-26 21:03:54 +00:00
crypto: Crypto . fromJson ( { ' ticker ' : ' BTC ' , ' name ' : ' Bitcoin ' } ) ,
fiat: Fiat . fromJson ( { ' ticker ' : ' USD ' , ' name ' : ' United States Dollar ' } ) ,
2023-01-16 23:38:42 +00:00
youPayFiatPrice: Decimal . parse ( " 100 " ) ,
youReceiveCryptoAmount: Decimal . parse ( " 1.0238917 " ) ,
2023-01-19 23:47:27 +00:00
id: " someID " ,
2023-01-16 23:38:42 +00:00
receivingAddress: ' ' ,
2023-01-19 19:36:05 +00:00
buyWithFiat: true ,
2023-01-16 23:38:42 +00:00
) ; // TODO enum this or something
2023-01-14 00:07:27 +00:00
2023-01-16 17:05:50 +00:00
// TODO set defaults better; should probably explicitly enumerate the coins & fiats used and pull the specific ones we need rather than generating them as defaults here
2023-01-26 21:03:54 +00:00
selectedFiat =
Fiat . fromJson ( { ' ticker ' : ' USD ' , ' name ' : ' United States Dollar ' } ) ;
2023-01-27 19:47:41 +00:00
selectedCrypto = Crypto . fromJson ( {
' ticker ' : widget . coin ? . ticker ? ? ' BTC ' ,
2024-05-15 21:20:45 +00:00
' name ' : widget . coin ? . prettyName ? ? ' Bitcoin ' ,
2023-01-27 19:47:41 +00:00
} ) ;
2023-01-16 17:05:50 +00:00
2023-03-29 14:29:40 +00:00
// THIS IS BAD. No way to be certain the simplex ticker points to the same
// contract as the ticker symbol of this contract
// if (widget.tokenContract != null &&
// DefaultTokens.list
// .where((e) => e.address == widget.tokenContract!.address)
// .isNotEmpty) {
// selectedCrypto = Crypto.fromJson({
// 'ticker': widget.tokenContract!.symbol,
// 'name': widget.tokenContract!.name,
// });
// }
2023-01-13 22:45:35 +00:00
// TODO set initial crypto to open wallet if a wallet is open
2023-01-11 15:54:39 +00:00
super . initState ( ) ;
}
2023-01-10 21:25:20 +00:00
@ override
void dispose ( ) {
super . dispose ( ) ;
}
@ override
Widget build ( BuildContext context ) {
debugPrint ( " BUILD: $ runtimeType " ) ;
2024-05-27 23:56:22 +00:00
final Locale locale = Localizations . localeOf ( context ) ;
final format = NumberFormat . simpleCurrency ( locale: locale . toString ( ) ) ;
2023-01-21 01:34:54 +00:00
// See https://stackoverflow.com/a/67055685
2023-01-15 23:30:07 +00:00
return ConditionalParent (
condition: isDesktop ,
builder: ( child ) = > SizedBox (
width: 458 ,
child: child ,
) ,
child: ConditionalParent (
condition: ! isDesktop ,
builder: ( child ) = > LayoutBuilder (
builder: ( context , constraints ) = > SingleChildScrollView (
child: ConstrainedBox (
constraints: BoxConstraints (
minHeight: constraints . maxHeight ,
) ,
child: IntrinsicHeight (
child: child ,
) ,
2023-01-13 22:45:35 +00:00
) ,
2023-01-14 16:41:02 +00:00
) ,
2023-01-15 23:30:07 +00:00
) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
Text (
" I want to buy " ,
style: STextStyles . itemSubtitle ( context ) . copyWith (
color: Theme . of ( context ) . extension < StackColors > ( ) ! . textDark3 ,
) ,
) ,
SizedBox (
height: isDesktop ? 10 : 4 ,
) ,
MouseRegion (
cursor: SystemMouseCursors . click ,
onEnter: ( _ ) = > setState ( ( ) = > _hovering1 = true ) ,
onExit: ( _ ) = > setState ( ( ) = > _hovering1 = false ) ,
child: GestureDetector (
onTap: ( ) {
selectCrypto ( ) ;
} ,
child: RoundedContainer (
padding:
2023-01-24 20:17:27 +00:00
const EdgeInsets . symmetric ( vertical: 6 , horizontal: 2 ) ,
2023-01-15 23:30:07 +00:00
color: _hovering1
? Theme . of ( context )
2023-01-14 16:41:02 +00:00
. extension < StackColors > ( ) !
2023-01-27 17:43:41 +00:00
. currencyListItemBG
2023-01-15 23:30:07 +00:00
. withOpacity ( _hovering1 ? 0.3 : 0 )
: Theme . of ( context )
. extension < StackColors > ( ) !
. textFieldDefaultBG ,
child: Padding (
padding: const EdgeInsets . all ( 12 ) ,
2023-01-16 00:24:47 +00:00
child: Row (
children: < Widget > [
2023-05-09 15:21:04 +00:00
CoinIconForTicker (
ticker: selectedCrypto ? . ticker ? ? " BTC " ,
size: 20 ,
) ,
2023-01-16 00:24:47 +00:00
const SizedBox (
width: 10 ,
) ,
Expanded (
2023-01-15 23:30:07 +00:00
child: Text (
2023-01-16 00:24:47 +00:00
selectedCrypto ? . ticker ? ? " ERR " ,
style: STextStyles . largeMedium14 ( context ) ,
) ,
) ,
SvgPicture . asset (
Assets . svg . chevronDown ,
color: Theme . of ( context )
. extension < StackColors > ( ) !
. buttonTextSecondaryDisabled ,
width: 10 ,
height: 5 ,
) ,
] ,
) ,
2023-01-15 23:30:07 +00:00
) ,
2023-01-13 22:45:35 +00:00
) ,
) ,
) ,
2023-01-15 23:30:07 +00:00
SizedBox (
height: isDesktop ? 20 : 12 ,
) ,
Row (
crossAxisAlignment: CrossAxisAlignment . end ,
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Text (
" I want to pay with " ,
style: STextStyles . itemSubtitle ( context ) . copyWith (
color:
Theme . of ( context ) . extension < StackColors > ( ) ! . textDark3 ,
) ,
2023-01-13 22:45:35 +00:00
) ,
2023-01-15 23:30:07 +00:00
] ,
) ,
SizedBox (
height: isDesktop ? 10 : 4 ,
) ,
MouseRegion (
cursor: SystemMouseCursors . click ,
onEnter: ( _ ) = > setState ( ( ) = > _hovering2 = true ) ,
onExit: ( _ ) = > setState ( ( ) = > _hovering2 = false ) ,
child: GestureDetector (
onTap: ( ) {
selectFiat ( ) ;
} ,
child: RoundedContainer (
padding:
2023-01-24 20:17:27 +00:00
const EdgeInsets . symmetric ( vertical: 3 , horizontal: 2 ) ,
2023-01-15 23:30:07 +00:00
color: _hovering2
? Theme . of ( context )
2023-01-14 16:41:02 +00:00
. extension < StackColors > ( ) !
2023-01-27 17:43:41 +00:00
. currencyListItemBG
2023-01-15 23:30:07 +00:00
. withOpacity ( _hovering2 ? 0.3 : 0 )
: Theme . of ( context )
. extension < StackColors > ( ) !
. textFieldDefaultBG ,
child: Padding (
2023-01-24 17:01:01 +00:00
padding: const EdgeInsets . only (
2023-01-30 19:08:44 +00:00
left: 12.0 ,
top: 12.0 ,
right: 12.0 ,
bottom: 12.0 ,
) ,
2023-01-16 00:24:47 +00:00
child: Row (
children: < Widget > [
2023-01-24 20:17:27 +00:00
Container (
padding: const EdgeInsets . symmetric (
2024-05-15 21:20:45 +00:00
vertical: 3 ,
horizontal: 6 ,
) ,
2023-01-24 20:17:27 +00:00
decoration: BoxDecoration (
color: Theme . of ( context )
. extension < StackColors > ( ) !
2023-01-27 17:43:41 +00:00
. currencyListItemBG ,
2023-01-24 20:17:27 +00:00
borderRadius: BorderRadius . circular ( 4 ) ,
) ,
child: Text (
format . simpleCurrencySymbol (
2024-05-15 21:20:45 +00:00
selectedFiat ? . ticker ? ? " ERR " . toUpperCase ( ) ,
) ,
2023-01-24 20:17:27 +00:00
textAlign: TextAlign . center ,
style: STextStyles . smallMed12 ( context ) . copyWith (
2024-05-15 21:20:45 +00:00
color: Theme . of ( context )
. extension < StackColors > ( ) !
. accentColorDark ,
) ,
2023-01-24 20:17:27 +00:00
) ,
2023-01-15 23:30:07 +00:00
) ,
2023-01-16 00:24:47 +00:00
const SizedBox (
2023-01-24 20:21:47 +00:00
width: 8 ,
2023-01-16 00:24:47 +00:00
) ,
2023-01-24 17:23:51 +00:00
Text (
2023-01-30 19:08:44 +00:00
selectedFiat ? . ticker ? ? ' ERR ' ,
2023-01-24 17:23:51 +00:00
style: STextStyles . largeMedium14 ( context ) ,
) ,
const SizedBox (
width: 12 ,
) ,
2023-01-16 00:24:47 +00:00
Expanded (
2023-01-15 23:30:07 +00:00
child: Text (
2023-01-30 19:08:44 +00:00
selectedFiat ? . name ? ? ' Error ' ,
2023-01-16 00:24:47 +00:00
style: STextStyles . largeMedium14 ( context ) ,
) ,
) ,
SvgPicture . asset (
Assets . svg . chevronDown ,
color: Theme . of ( context )
. extension < StackColors > ( ) !
. buttonTextSecondaryDisabled ,
width: 10 ,
height: 5 ,
) ,
] ,
) ,
2023-01-15 23:30:07 +00:00
) ,
2023-01-13 22:45:35 +00:00
) ,
2023-01-13 19:14:56 +00:00
) ,
) ,
2023-01-15 23:30:07 +00:00
SizedBox (
height: isDesktop ? 10 : 4 ,
2023-01-13 22:45:35 +00:00
) ,
2023-01-15 23:30:07 +00:00
Row (
crossAxisAlignment: CrossAxisAlignment . end ,
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Text (
buyWithFiat ? " Enter amount " : " Enter crypto amount " ,
style: STextStyles . itemSubtitle ( context ) . copyWith (
color:
Theme . of ( context ) . extension < StackColors > ( ) ! . textDark3 ,
2023-01-14 16:41:02 +00:00
) ,
2023-01-15 23:30:07 +00:00
) ,
2023-01-30 21:34:21 +00:00
CustomTextButton (
2023-01-16 00:24:47 +00:00
text: buyWithFiat ? " Use crypto amount " : " Use fiat amount " ,
onTap: ( ) {
setState ( ( ) {
buyWithFiat = ! buyWithFiat ;
} ) ;
2023-01-30 19:42:19 +00:00
validateAmount ( ) ;
2023-01-16 00:24:47 +00:00
} ,
2024-05-15 21:20:45 +00:00
) ,
2023-01-15 23:30:07 +00:00
] ,
) ,
SizedBox (
height: isDesktop ? 10 : 4 ,
) ,
TextField (
autocorrect: Util . isDesktop ? false : true ,
enableSuggestions: Util . isDesktop ? false : true ,
style: STextStyles . smallMed14 ( context ) . copyWith (
color: Theme . of ( context ) . extension < StackColors > ( ) ! . textDark ,
2023-01-14 16:41:02 +00:00
) ,
2023-01-26 16:46:53 +00:00
key: const Key ( " buyAmountInputFieldTextFieldKey " ) ,
2023-01-30 19:08:44 +00:00
controller: _buyAmountController ,
// note: setting the text value here will set it every time this widget rebuilds
// ..text = _BuyFormState.buyWithFiat
// ? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00'
// : _BuyFormState.minCrypto.toStringAsFixed(8),
2023-01-15 23:30:07 +00:00
focusNode: _buyAmountFocusNode ,
keyboardType: Util . isDesktop
? null
: const TextInputType . numberWithOptions (
signed: false ,
decimal: true ,
) ,
2023-01-16 00:47:28 +00:00
textAlign: TextAlign . left ,
2023-01-30 19:08:44 +00:00
// inputFormatters: [NumericalRangeFormatter()],
onChanged: ( _ ) {
validateAmount ( ) ;
} ,
2023-01-15 23:30:07 +00:00
decoration: InputDecoration (
contentPadding: const EdgeInsets . only (
2023-01-16 00:47:28 +00:00
// top: 22,
// right: 12,
// bottom: 22,
left: 0 ,
top: 8 ,
bottom: 10 ,
right: 5 ,
2023-01-15 23:30:07 +00:00
) ,
hintText: " 0 " ,
hintStyle: STextStyles . desktopTextExtraSmall ( context ) . copyWith (
color: Theme . of ( context )
. extension < StackColors > ( ) !
. textFieldDefaultText ,
) ,
prefixIcon: FittedBox (
fit: BoxFit . scaleDown ,
child: Padding (
padding: const EdgeInsets . all ( 12 ) ,
2024-05-15 21:20:45 +00:00
child: Row (
children: [
const SizedBox ( width: 2 ) ,
buyWithFiat
? Container (
padding: const EdgeInsets . symmetric (
vertical: 3 ,
horizontal: 6 ,
) ,
decoration: BoxDecoration (
color: Theme . of ( context )
. extension < StackColors > ( ) !
. currencyListItemBG ,
borderRadius: BorderRadius . circular ( 4 ) ,
) ,
child: Text (
format . simpleCurrencySymbol (
selectedFiat ? . ticker . toUpperCase ( ) ? ? " ERR " ,
) ,
textAlign: TextAlign . center ,
style:
STextStyles . smallMed12 ( context ) . copyWith (
2023-01-24 20:17:27 +00:00
color: Theme . of ( context )
. extension < StackColors > ( ) !
2024-05-15 21:20:45 +00:00
. accentColorDark ,
) ,
) ,
)
: CoinIconForTicker (
ticker: selectedCrypto ? . ticker ? ? " BTC " ,
size: 20 ,
2023-01-24 20:17:27 +00:00
) ,
2024-05-15 21:20:45 +00:00
SizedBox (
width: buyWithFiat ? 8 : 10 ,
) , // maybe make isDesktop-aware?
Text (
buyWithFiat
? selectedFiat ? . ticker ? ? " ERR "
: selectedCrypto ? . ticker ? ? " ERR " ,
style: STextStyles . smallMed14 ( context ) . copyWith (
2023-01-21 01:58:45 +00:00
color: Theme . of ( context )
. extension < StackColors > ( ) !
2024-05-15 21:20:45 +00:00
. accentColorDark ,
) ,
) ,
] ,
) ,
2023-01-14 00:07:27 +00:00
) ,
) ,
2023-01-16 00:47:28 +00:00
suffixIcon: Padding (
padding: const EdgeInsets . all ( 0 ) ,
child: UnconstrainedBox (
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceAround ,
children: [
_buyAmountController . text . isNotEmpty
? TextFieldIconButton (
key: const Key (
2024-05-15 21:20:45 +00:00
" buyViewClearAmountFieldButtonKey " ,
) ,
2023-01-16 00:47:28 +00:00
onTap: ( ) {
2023-01-30 19:08:44 +00:00
// if (_BuyFormState.buyWithFiat) {
// _buyAmountController.text = _BuyFormState
// .minFiat
// .toStringAsFixed(2);
// } else {
// if (selectedCrypto?.ticker ==
// _BuyFormState.boundedCryptoTicker) {
// _buyAmountController.text = _BuyFormState
// .minCrypto
// .toStringAsFixed(8);
// }
// }
_buyAmountController . text = " " ;
validateAmount ( ) ;
2023-01-16 00:47:28 +00:00
} ,
child: const XIcon ( ) ,
)
: TextFieldIconButton (
key: const Key (
2024-05-15 21:20:45 +00:00
" buyViewPasteAddressFieldButtonKey " ,
) ,
2023-01-16 00:47:28 +00:00
onTap: ( ) async {
final ClipboardData ? data = await clipboard
. getData ( Clipboard . kTextPlain ) ;
final amountString =
Decimal . tryParse ( data ? . text ? ? " " ) ;
if ( amountString ! = null ) {
_buyAmountController . text =
amountString . toString ( ) ;
2023-01-30 19:08:44 +00:00
validateAmount ( ) ;
2023-01-16 00:47:28 +00:00
}
} ,
child: _buyAmountController . text . isEmpty
? const ClipboardIcon ( )
: const XIcon ( ) ,
) ,
] ,
) ,
) ,
) ,
2023-01-14 00:07:27 +00:00
) ,
2023-01-11 15:54:39 +00:00
) ,
2023-01-30 19:08:44 +00:00
SizedBox (
height: isDesktop ? 10 : 4 ,
) ,
if ( _amountOutOfRangeErrorString . isNotEmpty )
Text (
_amountOutOfRangeErrorString ,
style: STextStyles . errorSmall ( context ) ,
) ,
2023-01-15 23:30:07 +00:00
SizedBox (
height: isDesktop ? 20 : 12 ,
) ,
Row (
crossAxisAlignment: CrossAxisAlignment . end ,
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Text (
" Enter receiving address " ,
style: STextStyles . itemSubtitle ( context ) . copyWith (
color:
Theme . of ( context ) . extension < StackColors > ( ) ! . textDark3 ,
) ,
2023-01-15 23:26:05 +00:00
) ,
2024-05-23 17:26:58 +00:00
if ( AppConfig . isStackCoin ( selectedCrypto ? . ticker ) )
2023-01-30 21:34:21 +00:00
CustomTextButton (
2023-01-31 14:41:40 +00:00
text: " Choose from Stack " ,
2023-01-15 23:30:07 +00:00
onTap: ( ) {
try {
2024-05-22 19:38:49 +00:00
final coin = AppConfig . getCryptoCurrencyForTicker (
2023-01-15 23:30:07 +00:00
selectedCrypto ! . ticker ,
) ;
Navigator . of ( context )
. pushNamed (
ChooseFromStackView . routeName ,
arguments: coin ,
)
. then ( ( value ) async {
if ( value is String ) {
2023-11-03 19:46:55 +00:00
final wallet = ref . read ( pWallets ) . getWallet ( value ) ;
2023-01-15 23:30:07 +00:00
// _toController.text = manager.walletName;
// model.recipientAddress =
// await manager.currentReceivingAddress;
_receiveAddressController . text =
2023-11-03 19:46:55 +00:00
( await wallet . getCurrentReceivingAddress ( ) ) !
. value ;
2023-01-15 23:30:07 +00:00
2023-01-31 14:41:40 +00:00
setState ( ( ) {
_addressToggleFlag =
_receiveAddressController . text . isNotEmpty ;
} ) ;
validateAmount ( ) ;
2023-01-15 23:30:07 +00:00
}
} ) ;
} catch ( e , s ) {
Logging . instance . log ( " $ e \n $ s " , level: LogLevel . Info ) ;
}
} ,
) ,
] ,
2023-01-13 22:45:35 +00:00
) ,
2023-01-15 23:30:07 +00:00
SizedBox (
height: isDesktop ? 10 : 4 ,
) ,
ClipRRect (
borderRadius: BorderRadius . circular (
Constants . size . circularBorderRadius ,
2023-01-13 22:45:35 +00:00
) ,
2023-01-15 23:30:07 +00:00
child: TextField (
key: const Key ( " buyViewReceiveAddressFieldKey " ) ,
controller: _receiveAddressController ,
readOnly: false ,
autocorrect: false ,
enableSuggestions: false ,
// inputFormatters: <TextInputFormatter>[
// FilteringTextInputFormatter.allow(
// RegExp("[a-zA-Z0-9]{34}")),
// ],
toolbarOptions: const ToolbarOptions (
copy: false ,
cut: false ,
paste: true ,
selectAll: false ,
2023-01-13 22:45:35 +00:00
) ,
2023-01-15 23:30:07 +00:00
onChanged: ( newValue ) {
_address = newValue ;
setState ( ( ) {
_addressToggleFlag = newValue . isNotEmpty ;
} ) ;
} ,
focusNode: _receiveAddressFocusNode ,
style: STextStyles . field ( context ) ,
decoration: standardInputDecoration (
2023-01-16 00:47:28 +00:00
" Enter ${ selectedCrypto ? . ticker } address " ,
2023-01-15 23:30:07 +00:00
_receiveAddressFocusNode ,
context ,
) . copyWith (
contentPadding: const EdgeInsets . only (
2023-01-24 20:17:27 +00:00
left: 13 ,
2023-01-15 23:30:07 +00:00
top: 6 ,
bottom: 8 ,
right: 5 ,
) ,
suffixIcon: Padding (
padding: _receiveAddressController . text . isEmpty
? const EdgeInsets . only ( right: 8 )
: const EdgeInsets . only ( right: 0 ) ,
child: UnconstrainedBox (
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceAround ,
children: [
_addressToggleFlag
? TextFieldIconButton (
key: const Key (
2024-05-15 21:20:45 +00:00
" buyViewClearAddressFieldButtonKey " ,
) ,
2023-01-15 23:30:07 +00:00
onTap: ( ) {
_receiveAddressController . text = " " ;
2023-01-23 20:29:18 +00:00
_address = " " ;
2023-01-15 23:30:07 +00:00
setState ( ( ) {
2023-01-30 19:08:44 +00:00
_addressToggleFlag = false ;
2023-01-15 23:30:07 +00:00
} ) ;
} ,
child: const XIcon ( ) ,
)
: TextFieldIconButton (
key: const Key (
2024-05-15 21:20:45 +00:00
" buyViewPasteAddressFieldButtonKey " ,
) ,
2023-01-15 23:30:07 +00:00
onTap: ( ) async {
final ClipboardData ? data = await clipboard
. getData ( Clipboard . kTextPlain ) ;
if ( data ? . text ! = null & &
data ! . text ! . isNotEmpty ) {
String content = data . text ! . trim ( ) ;
if ( content . contains ( " \n " ) ) {
content = content . substring (
2024-05-15 21:20:45 +00:00
0 ,
content . indexOf ( " \n " ) ,
) ;
2023-01-15 23:30:07 +00:00
}
_receiveAddressController . text = content ;
_address = content ;
setState ( ( ) {
_addressToggleFlag =
_receiveAddressController
. text . isNotEmpty ;
} ) ;
2023-01-15 23:26:05 +00:00
}
2023-01-15 23:30:07 +00:00
} ,
child: _receiveAddressController . text . isEmpty
? const ClipboardIcon ( )
: const XIcon ( ) ,
) ,
2023-01-21 00:26:43 +00:00
if ( _receiveAddressController . text . isEmpty & &
2024-05-23 17:26:58 +00:00
AppConfig . isStackCoin ( selectedCrypto ? . ticker ) & &
2023-01-23 20:20:58 +00:00
isDesktop )
TextFieldIconButton (
key: const Key ( " buyViewAddressBookButtonKey " ) ,
onTap: ( ) async {
final entry =
await showDialog < ContactAddressEntry ? > (
context: context ,
builder: ( context ) = > DesktopDialog (
maxWidth: 696 ,
maxHeight: 600 ,
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 (
2024-05-15 21:20:45 +00:00
context ,
) ,
2023-01-23 20:20:58 +00:00
) ,
) ,
const DesktopDialogCloseButton ( ) ,
] ,
) ,
Expanded (
child: AddressBookAddressChooser (
2024-05-22 19:38:49 +00:00
coin: AppConfig . coins . firstWhere (
2024-05-15 21:20:45 +00:00
( e ) = >
e . ticker . toLowerCase ( ) = =
selectedCrypto ! . ticker
. toString ( )
. toLowerCase ( ) ,
) ,
2023-01-23 20:20:58 +00:00
) ,
) ,
] ,
) ,
) ,
) ;
if ( entry ! = null ) {
_receiveAddressController . text =
entry . address ;
_address = entry . address ;
setState ( ( ) {
_addressToggleFlag = true ;
} ) ;
}
} ,
child: const AddressBookIcon ( ) ,
) ,
if ( _receiveAddressController . text . isEmpty & &
2024-05-23 17:26:58 +00:00
AppConfig . isStackCoin ( selectedCrypto ? . ticker ) & &
2023-01-23 20:20:58 +00:00
! isDesktop )
2023-01-15 23:30:07 +00:00
TextFieldIconButton (
key: const Key ( " buyViewAddressBookButtonKey " ) ,
onTap: ( ) {
2023-01-23 20:20:58 +00:00
Navigator . of ( context , rootNavigator: isDesktop )
. pushNamed (
2023-01-15 23:30:07 +00:00
AddressBookView . routeName ,
) ;
} ,
child: const AddressBookIcon ( ) ,
) ,
if ( _receiveAddressController . text . isEmpty & &
! isDesktop )
TextFieldIconButton (
key: const Key ( " buyViewScanQrButtonKey " ) ,
onTap: ( ) async {
try {
if ( FocusScope . of ( context ) . hasFocus ) {
FocusScope . of ( context ) . unfocus ( ) ;
await Future < void > . delayed (
2024-05-15 21:20:45 +00:00
const Duration ( milliseconds: 75 ) ,
) ;
2023-01-15 23:30:07 +00:00
}
final qrResult = await scanner . scan ( ) ;
Logging . instance . log (
2024-05-15 21:20:45 +00:00
" qrResult content: ${ qrResult . rawContent } " ,
level: LogLevel . Info ,
) ;
2023-01-15 23:30:07 +00:00
final results = AddressUtils . parseUri (
2024-05-15 21:20:45 +00:00
qrResult . rawContent ,
) ;
2023-01-15 23:30:07 +00:00
Logging . instance . log (
2024-05-15 21:20:45 +00:00
" qrResult parsed: $ results " ,
level: LogLevel . Info ,
) ;
2023-01-15 23:26:05 +00:00
2023-01-15 23:30:07 +00:00
if ( results . isNotEmpty ) {
// auto fill address
_address = results [ " address " ] ? ? " " ;
_receiveAddressController . text = _address ! ;
2023-01-15 23:26:05 +00:00
setState ( ( ) {
_addressToggleFlag =
_receiveAddressController
. text . isNotEmpty ;
} ) ;
2023-01-14 16:41:02 +00:00
2023-01-15 23:30:07 +00:00
// now check for non standard encoded basic address
} else {
_address = qrResult . rawContent ;
_receiveAddressController . text =
_address ? ? " " ;
setState ( ( ) {
_addressToggleFlag =
_receiveAddressController
. text . isNotEmpty ;
} ) ;
}
} on PlatformException catch ( e , s ) {
// here we ignore the exception caused by not giving permission
// to use the camera to scan a qr code
Logging . instance . log (
" Failed to get camera permissions while trying to scan qr code in SendView: $ e \n $ s " ,
level: LogLevel . Warning ,
) ;
2023-01-15 23:26:05 +00:00
}
2023-01-15 23:30:07 +00:00
} ,
child: const QrCodeIcon ( ) ,
) ,
] ,
) ,
2023-01-13 22:45:35 +00:00
) ,
) ,
) ,
2023-01-11 15:54:39 +00:00
) ,
) ,
2023-01-15 23:30:07 +00:00
SizedBox (
height: isDesktop ? 20 : 12 ,
) ,
2023-01-30 19:08:44 +00:00
PrimaryButton (
buttonHeight: isDesktop ? ButtonHeight . l : null ,
enabled: _addressToggleFlag & &
_amountOutOfRangeErrorString . isEmpty & &
_buyAmountController . text . isNotEmpty ,
onPressed: ( ) {
previewQuote ( quote ) ;
} ,
label: " Preview quote " ,
2023-01-16 21:31:53 +00:00
) ,
2023-01-15 23:30:07 +00:00
] ,
) ,
2023-01-14 16:41:02 +00:00
) ,
) ;
2023-01-10 21:25:20 +00:00
}
}
2023-01-26 16:46:53 +00:00
2023-01-30 19:08:44 +00:00
// might need this again in the future
// // See https://stackoverflow.com/a/68072967
// class NumericalRangeFormatter extends TextInputFormatter {
// NumericalRangeFormatter();
//
// @override
// TextEditingValue formatEditUpdate(
// TextEditingValue oldValue,
// TextEditingValue newValue,
// ) {
// TextSelection newSelection = newValue.selection;
// String newVal = _BuyFormState.buyWithFiat
// ? Decimal.parse(newValue.text).toStringAsFixed(2)
// : Decimal.parse(newValue.text).toStringAsFixed(8);
// if (newValue.text == '') {
// return newValue;
// } else {
// if (_BuyFormState.buyWithFiat) {
// if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) {
// newVal = _BuyFormState.minFiat.toStringAsFixed(2);
// // _BuyFormState._buyAmountController.selection =
// // TextSelection.collapsed(
// // offset: _BuyFormState.buyWithFiat
// // ? _BuyFormState._buyAmountController.text.length - 2
// // : _BuyFormState._buyAmountController.text.length - 8);
// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) {
// newVal = _BuyFormState.maxFiat.toStringAsFixed(2);
// }
// } else if (!_BuyFormState.buyWithFiat &&
// _BuyFormState.selectedCrypto?.ticker ==
// _BuyFormState.boundedCryptoTicker) {
// if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) {
// newVal = _BuyFormState.minCrypto.toStringAsFixed(8);
// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) {
// newVal = _BuyFormState.maxCrypto.toStringAsFixed(8);
// }
// }
// }
//
// final regexString = _BuyFormState.buyWithFiat
// ? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$'
// : r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
//
// // return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// return RegExp(regexString).hasMatch(newVal)
// ? TextEditingValue(text: newVal, selection: newSelection)
// : oldValue;
// }
// }