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 ' ;
2023-01-11 17:19:19 +00:00
import ' package:stackwallet/models/buy/response_objects/crypto.dart ' ;
2023-01-11 17:49:59 +00:00
import ' package:stackwallet/models/buy/response_objects/fiat.dart ' ;
2023-01-16 00:09:11 +00:00
import ' package:stackwallet/models/buy/response_objects/quote.dart ' ;
2023-01-23 20:20:58 +00:00
import ' package:stackwallet/models/contact_address_entry.dart ' ;
2023-01-15 23:26:05 +00:00
import ' package:stackwallet/pages/address_book_views/address_book_view.dart ' ;
2023-01-16 00:09:11 +00:00
import ' package:stackwallet/pages/buy_view/buy_quote_preview.dart ' ;
2023-01-14 00:07:27 +00:00
import ' package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart ' ;
2023-01-11 17:49:59 +00:00
import ' package:stackwallet/pages/buy_view/sub_widgets/fiat_selection_view.dart ' ;
2023-01-15 23:26:05 +00:00
import ' package:stackwallet/pages/exchange_view/choose_from_stack_view.dart ' ;
2023-01-23 20:20:58 +00:00
import ' package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart ' ;
2023-01-11 15:54:39 +00:00
import ' package:stackwallet/providers/providers.dart ' ;
2023-01-15 23:26:05 +00:00
import ' package:stackwallet/services/buy/simplex/simplex_api.dart ' ;
2023-01-13 22:45:35 +00:00
import ' package:stackwallet/utilities/address_utils.dart ' ;
2023-01-13 19:14:56 +00:00
import ' package:stackwallet/utilities/assets.dart ' ;
2023-01-13 22:45:35 +00:00
import ' package:stackwallet/utilities/barcode_scanner_interface.dart ' ;
import ' package:stackwallet/utilities/clipboard_interface.dart ' ;
2023-01-11 15:54:39 +00:00
import ' package:stackwallet/utilities/constants.dart ' ;
2023-01-10 21:25:20 +00:00
import ' package:stackwallet/utilities/enums/coin_enum.dart ' ;
2023-01-13 22:45:35 +00:00
import ' package:stackwallet/utilities/logger.dart ' ;
2023-01-10 21:25:20 +00:00
import ' package:stackwallet/utilities/text_styles.dart ' ;
import ' package:stackwallet/utilities/theme/stack_colors.dart ' ;
import ' package:stackwallet/utilities/util.dart ' ;
2023-01-15 23:30:07 +00:00
import ' package:stackwallet/widgets/conditional_parent.dart ' ;
2023-01-15 23:26:05 +00:00
import ' package:stackwallet/widgets/custom_buttons/blue_text_button.dart ' ;
import ' package:stackwallet/widgets/custom_loading_overlay.dart ' ;
2023-01-11 17:19:19 +00:00
import ' package:stackwallet/widgets/desktop/desktop_dialog.dart ' ;
import ' package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart ' ;
2023-01-14 01:08:12 +00:00
import ' package:stackwallet/widgets/desktop/primary_button.dart ' ;
2023-01-13 22:45:35 +00:00
import ' package:stackwallet/widgets/icon_widgets/addressbook_icon.dart ' ;
import ' package:stackwallet/widgets/icon_widgets/clipboard_icon.dart ' ;
import ' package:stackwallet/widgets/icon_widgets/qrcode_icon.dart ' ;
import ' package:stackwallet/widgets/icon_widgets/x_icon.dart ' ;
2023-01-13 19:14:56 +00:00
import ' package:stackwallet/widgets/rounded_container.dart ' ;
2023-01-11 17:19:19 +00:00
import ' package:stackwallet/widgets/rounded_white_container.dart ' ;
2023-01-13 22:45:35 +00:00
import ' package:stackwallet/widgets/stack_text_field.dart ' ;
import ' package:stackwallet/widgets/textfield_icon_button.dart ' ;
2023-01-10 21:25:20 +00:00
class BuyForm extends ConsumerStatefulWidget {
const BuyForm ( {
Key ? key ,
2023-01-13 22:45:35 +00:00
this . clipboard = const ClipboardWrapper ( ) ,
this . scanner = const BarcodeScannerWrapper ( ) ,
2023-01-10 21:25:20 +00:00
} ) : super ( key: key ) ;
2023-01-13 22:45:35 +00:00
final ClipboardInterface clipboard ;
final BarcodeScannerInterface scanner ;
2023-01-10 21:25:20 +00:00
@ override
ConsumerState < BuyForm > createState ( ) = > _BuyFormState ( ) ;
}
class _BuyFormState extends ConsumerState < BuyForm > {
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-15 23:26:05 +00:00
Fiat ? selectedFiat ;
Crypto ? selectedCrypto ;
2023-01-16 23:38:42 +00:00
SimplexQuote quote = SimplexQuote (
crypto: Crypto . fromJson ( { ' ticker ' : ' BTC ' , ' name ' : ' Bitcoin ' , ' image ' : ' ' } ) ,
fiat: Fiat . fromJson (
{ ' ticker ' : ' USD ' , ' name ' : ' United States Dollar ' , ' image ' : ' ' } ) ,
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-11 22:17:46 +00:00
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-13 22:45:35 +00:00
void fiatFieldOnChanged ( String value ) async { }
2023-01-11 15:54:39 +00:00
2023-01-13 22:45:35 +00:00
void cryptoFieldOnChanged ( String value ) async { }
2023-01-11 17:19:19 +00:00
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 ( ( ) {
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
) ,
) ,
) ,
] ,
) ,
) ,
) ,
] ,
) ,
) ;
} )
: 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-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
) ,
) ,
) ,
] ,
) ,
) ,
) ,
] ,
) ,
) ;
} )
: 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-11 17:19:19 +00:00
String ? _fetchIconUrlFromTicker ( String ? ticker ) {
if ( ticker = = null ) return null ;
return null ;
}
2023-01-15 23:26:05 +00:00
bool isStackCoin ( String ? ticker ) {
if ( ticker = = null ) return false ;
try {
coinFromTickerCaseInsensitive ( ticker ) ;
return true ;
} on ArgumentError catch ( _ ) {
return false ;
}
}
2023-01-21 02:05:32 +00:00
Widget ? getIconForTicker ( String ticker ) {
String ? iconAsset = isStackCoin ( ticker )
? Assets . svg . iconFor ( coin: coinFromTickerCaseInsensitive ( ticker ) )
: Assets . svg . buyIconFor ( ticker ) ;
return ( iconAsset ! = null )
? SvgPicture . asset ( iconAsset , height: 20 , width: 20 )
: null ;
}
2023-01-16 23:38:42 +00:00
Future < void > previewQuote ( SimplexQuote quote ) async {
2023-01-19 23:47:27 +00:00
// if (ref.read(simplexProvider).quote.id == "someID") {
2023-01-16 23:38:42 +00:00
// // TODO make a better way of detecting a default SimplexQuote
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
) ;
await _loadQuote ( quote ) ;
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-16 21:31:53 +00:00
// }
await _showFloatingBuyQuotePreviewSheet (
quote: ref . read ( simplexProvider ) . quote ,
onSelected: ( quote ) {
// setState(() {
// selectedFiat = fiat;
// });
// TODO launch URL
} ,
) ;
}
2023-01-16 23:38:42 +00:00
Future < void > _loadQuote ( SimplexQuote quote ) async {
final response = await SimplexAPI . instance . getQuote ( quote ) ;
if ( response . value ! = null ) {
ref . read ( simplexProvider ) . updateQuote ( response . value ! ) ;
} else {
Logging . instance . log (
" _loadQuote: $ response " ,
level: LogLevel . Warning ,
) ;
}
}
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 ,
) ,
) ,
) ,
] ,
) ,
) ,
) ,
] ,
) ,
) ;
} )
: 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 (
crypto:
Crypto . fromJson ( { ' ticker ' : ' BTC ' , ' name ' : ' Bitcoin ' , ' image ' : ' ' } ) ,
fiat: Fiat . fromJson (
{ ' ticker ' : ' USD ' , ' name ' : ' United States Dollar ' , ' image ' : ' ' } ) ,
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
selectedFiat = Fiat . fromJson ( { ' ticker ' : ' USD ' , ' name ' : ' USD ' , ' image ' : ' ' } ) ;
selectedCrypto =
Crypto . fromJson ( { ' ticker ' : ' BTC ' , ' name ' : ' Bitcoin ' , ' image ' : ' ' } ) ;
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 " ) ;
2023-01-21 01:34:54 +00:00
Locale locale = Localizations . localeOf ( context ) ;
var format = NumberFormat . simpleCurrency ( locale: locale . toString ( ) ) ;
// 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:
const EdgeInsets . symmetric ( vertical: 6 , horizontal: 6 ) ,
color: _hovering1
? Theme . of ( context )
2023-01-14 16:41:02 +00:00
. extension < StackColors > ( ) !
2023-01-15 23:30:07 +00:00
. highlight
. 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-01-21 02:05:32 +00:00
getIconForTicker ( selectedCrypto ? . ticker ? ? " BTC " )
as Widget ,
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:
const EdgeInsets . symmetric ( vertical: 3 , horizontal: 6 ) ,
color: _hovering2
? Theme . of ( context )
2023-01-14 16:41:02 +00:00
. extension < StackColors > ( ) !
2023-01-15 23:30:07 +00:00
. highlight
. withOpacity ( _hovering2 ? 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-01-21 01:34:54 +00:00
Text (
format . simpleCurrencySymbol (
selectedFiat ? . ticker ? ? " ERR " . toUpperCase ( ) ) ,
style: STextStyles . currencyTicker ( context ) . apply (
fontSizeFactor: ( 1 /
format
. simpleCurrencySymbol (
selectedFiat ? . ticker ? ? " ERR " )
. length * // Couldn't get pow() working here
format
. simpleCurrencySymbol (
selectedFiat ? . ticker ? ? " ERR " )
. length ) ) ,
textAlign: TextAlign . center ,
2023-01-15 23:30:07 +00:00
) ,
2023-01-16 00:24:47 +00:00
// SvgPicture.asset(
// Assets.svg.iconFor(
// coin: coinFromTickerCaseInsensitive("BTC"),
// ),
// height: 18,
// width: 18,
// ),
const SizedBox (
width: 10 ,
) ,
Expanded (
2023-01-15 23:30:07 +00:00
child: Text (
2023-01-16 00:24:47 +00:00
selectedFiat ? . 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-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-16 00:24:47 +00:00
BlueTextButton (
text: buyWithFiat ? " Use crypto amount " : " Use fiat amount " ,
onTap: ( ) {
setState ( ( ) {
buyWithFiat = ! buyWithFiat ;
} ) ;
} ,
)
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-15 23:30:07 +00:00
key: const Key ( " amountInputFieldCryptoTextFieldKey " ) ,
controller: _buyAmountController ,
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-15 23:30:07 +00:00
inputFormatters: [
2023-01-16 00:24:47 +00:00
// regex to validate a crypto amount with 8 decimal places or
// 2 if fiat
TextInputFormatter . withFunction (
( oldValue , newValue ) {
final regexString = 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 ( newValue . text )
? newValue
: oldValue ;
} ,
) ,
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 ) ,
2023-01-21 01:58:45 +00:00
child: Row ( children: [
2023-01-16 00:24:47 +00:00
buyWithFiat
2023-01-21 01:58:45 +00:00
? Text (
format . simpleCurrencySymbol (
selectedFiat ? . ticker ? ? " ERR " . toUpperCase ( ) ) ,
style: STextStyles . currencyTicker ( context ) . apply (
fontSizeFactor: ( 1 /
format
. simpleCurrencySymbol (
selectedFiat ? . ticker ? ? " ERR " )
. length * // Couldn't get pow() working here
format
. simpleCurrencySymbol (
selectedFiat ? . ticker ? ? " ERR " )
. length ) ) ,
textAlign: TextAlign . center ,
)
2023-01-21 02:05:32 +00:00
: getIconForTicker ( selectedCrypto ? . ticker ? ? " BTC " )
as Widget ,
2023-01-21 01:58:45 +00:00
const SizedBox ( width: 10 ) , // maybe make isDesktop-aware?
Text (
buyWithFiat
? selectedFiat ? . ticker ? ? " ERR "
: selectedCrypto ? . ticker ? ? " ERR " ,
style: STextStyles . smallMed14 ( context ) . copyWith (
color: Theme . of ( context )
. extension < StackColors > ( ) !
. 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 (
" buyViewClearAddressFieldButtonKey " ) ,
onTap: ( ) {
_buyAmountController . text = " " ;
// _receiveAddress = "";
setState ( ( ) { } ) ;
} ,
child: const XIcon ( ) ,
)
: TextFieldIconButton (
key: const Key (
" buyViewPasteAddressFieldButtonKey " ) ,
onTap: ( ) async {
final ClipboardData ? data = await clipboard
. getData ( Clipboard . kTextPlain ) ;
final amountString =
Decimal . tryParse ( data ? . text ? ? " " ) ;
if ( amountString ! = null ) {
_buyAmountController . text =
amountString . toString ( ) ;
setState ( ( ) { } ) ;
}
} ,
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-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
) ,
2023-01-15 23:30:07 +00:00
if ( isStackCoin ( selectedCrypto ? . ticker ) )
BlueTextButton (
text: " Choose from stack " ,
onTap: ( ) {
try {
final coin = coinFromTickerCaseInsensitive (
selectedCrypto ! . ticker ,
) ;
Navigator . of ( context )
. pushNamed (
ChooseFromStackView . routeName ,
arguments: coin ,
)
. then ( ( value ) async {
if ( value is String ) {
final manager = ref
. read ( walletsChangeNotifierProvider )
. getManager ( value ) ;
// _toController.text = manager.walletName;
// model.recipientAddress =
// await manager.currentReceivingAddress;
_receiveAddressController . text =
await manager . currentReceivingAddress ;
setState ( ( ) { } ) ;
}
} ) ;
} 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 (
left: 16 ,
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 (
" buyViewClearAddressFieldButtonKey " ) ,
onTap: ( ) {
_receiveAddressController . text = " " ;
2023-01-23 20:29:18 +00:00
_address = " " ;
2023-01-15 23:30:07 +00:00
setState ( ( ) {
2023-01-21 02:15:04 +00:00
_addressToggleFlag = true ;
2023-01-15 23:30:07 +00:00
} ) ;
} ,
child: const XIcon ( ) ,
)
: TextFieldIconButton (
key: const Key (
" buyViewPasteAddressFieldButtonKey " ) ,
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 (
0 , content . indexOf ( " \n " ) ) ;
}
_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 & &
2023-01-23 20:20:58 +00:00
isStackCoin ( selectedCrypto ? . ticker ) & &
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 (
context ) ,
) ,
) ,
const DesktopDialogCloseButton ( ) ,
] ,
) ,
Expanded (
child: AddressBookAddressChooser (
coin: coinFromTickerCaseInsensitive (
selectedCrypto ! . ticker
. toString ( ) ) ,
) ,
) ,
] ,
) ,
) ,
) ;
if ( entry ! = null ) {
_receiveAddressController . text =
entry . address ;
_address = entry . address ;
setState ( ( ) {
_addressToggleFlag = true ;
} ) ;
}
} ,
child: const AddressBookIcon ( ) ,
) ,
if ( _receiveAddressController . text . isEmpty & &
isStackCoin ( selectedCrypto ? . ticker ) & &
! 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 (
const Duration ( milliseconds: 75 ) ) ;
}
final qrResult = await scanner . scan ( ) ;
Logging . instance . log (
" qrResult content: ${ qrResult . rawContent } " ,
level: LogLevel . Info ) ;
final results = AddressUtils . parseUri (
qrResult . rawContent ) ;
Logging . instance . log (
" 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-16 21:31:53 +00:00
MouseRegion (
cursor: SystemMouseCursors . click ,
onEnter: ( _ ) = > setState ( ( ) = > _hovering1 = true ) ,
onExit: ( _ ) = > setState ( ( ) = > _hovering1 = false ) ,
child: GestureDetector (
onTap: ( ) {
2023-01-21 02:09:42 +00:00
if ( _receiveAddressController . text . isNotEmpty & &
_buyAmountController . text . isNotEmpty ) {
previewQuote ( quote ) ;
}
2023-01-16 21:31:53 +00:00
} ,
child: PrimaryButton (
buttonHeight: isDesktop ? ButtonHeight . l : null ,
enabled: _receiveAddressController . text . isNotEmpty & &
_buyAmountController . text . isNotEmpty ,
onPressed: ( ) {
2023-01-21 02:09:42 +00:00
if ( _receiveAddressController . text . isNotEmpty & &
_buyAmountController . text . isNotEmpty ) {
previewQuote ( quote ) ;
}
2023-01-16 21:31:53 +00:00
} ,
label: " Preview quote " ,
) ) ,
) ,
2023-01-15 23:30:07 +00:00
] ,
) ,
2023-01-14 16:41:02 +00:00
) ,
) ;
2023-01-10 21:25:20 +00:00
}
}