import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:provider/provider.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart'; import 'package:cake_wallet/src/stores/exchange/limits_state.dart'; import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; import 'package:cake_wallet/src/stores/exchange/exchange_store.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; class ExchangePage extends BasePage { @override String get title => S.current.exchange; @override bool get isModalBackButton => true; final Image arrowBottomPurple = Image.asset('assets/images/arrow_bottom_purple_icon.png', height: 8); @override Widget middle(BuildContext context) { final exchangeStore = Provider.of(context); return FlatButton( onPressed: () => _presentProviderPicker(context), highlightColor: Colors.transparent, splashColor: Colors.transparent, child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(S.of(context).exchange, style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.w400, color: Theme.of(context).primaryTextTheme.title.color)), SizedBox(width: 5), arrowBottomPurple ]), Observer( builder: (_) => Text('${exchangeStore.provider.title}', style: TextStyle( fontSize: 10.0, fontWeight: FontWeight.w400, color: Theme.of(context).primaryTextTheme.headline.color))) ], ), ); } @override Widget trailing(BuildContext context) { final exchangeStore = Provider.of(context); return ButtonTheme( minWidth: double.minPositive, child: FlatButton( padding: EdgeInsets.all(0), child: Text( S.of(context).clear, style: TextStyle( color: Theme.of(context).accentTextTheme.caption.color, fontWeight: FontWeight.w500, fontSize: 16), ), onPressed: () => exchangeStore.reset()), ); } @override Widget body(BuildContext context) => ExchangeForm(); void _presentProviderPicker(BuildContext context) { final exchangeStore = Provider.of(context); final items = exchangeStore.providersForCurrentPair(); final selectedItem = items.indexOf(exchangeStore.provider); showDialog( builder: (_) => Picker( items: items, selectedAtIndex: selectedItem, title: S.of(context).change_exchange_provider, onItemSelected: (ExchangeProvider provider) => exchangeStore.changeProvider(provider: provider)), context: context); } } class ExchangeForm extends StatefulWidget { @override State createState() => ExchangeFormState(); } class ExchangeFormState extends State { final depositKey = GlobalKey(); final receiveKey = GlobalKey(); final _formKey = GlobalKey(); var _isReactionsSet = false; final Image arrowBottomPurple = Image.asset( 'assets/images/arrow_bottom_purple_icon.png', height: 8, ); final Image arrowBottomCakeGreen = Image.asset( 'assets/images/arrow_bottom_cake_green.png', height: 8, ); @override Widget build(BuildContext context) { final exchangeStore = Provider.of(context); final walletStore = Provider.of(context); final depositWalletName = exchangeStore.depositCurrency == CryptoCurrency.xmr ? walletStore.name : null; final receiveWalletName = exchangeStore.receiveCurrency == CryptoCurrency.xmr ? walletStore.name : null; WidgetsBinding.instance.addPostFrameCallback( (_) => _setReactions(context, exchangeStore, walletStore)); return Form( key: _formKey, child: ScrollableWithBottomSection( contentPadding: EdgeInsets.only(left: 20, right: 20), content: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Padding( padding: EdgeInsets.only(top: 10, bottom: 20), child: Text( S.of(context).you_will_send, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, height: 1.1, color: Theme.of(context).primaryTextTheme.title.color), ), ), ExchangeCard( key: depositKey, initialCurrency: exchangeStore.depositCurrency, initialWalletName: depositWalletName, initialAddress: exchangeStore.depositCurrency == walletStore.type ? walletStore.address : null, initialIsAmountEditable: true, initialIsAddressEditable: true, isAmountEstimated: false, currencies: CryptoCurrency.all, onCurrencySelected: (currency) => exchangeStore.changeDepositCurrency(currency: currency), imageArrow: arrowBottomPurple, currencyValueValidator: (value) { exchangeStore.validateCryptoCurrency(value); return exchangeStore.errorMessage; }, addressTextFieldValidator: (value) { exchangeStore.validateAddress(value, cryptoCurrency: exchangeStore.depositCurrency); return exchangeStore.errorMessage; }, ), SizedBox(height: 35), Padding( padding: EdgeInsets.only(bottom: 20), child: Text( S.of(context).you_will_get, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, height: 1.1, color: Theme.of(context).primaryTextTheme.title.color), )), Observer( builder: (_) => ExchangeCard( key: receiveKey, initialCurrency: exchangeStore.receiveCurrency, initialWalletName: receiveWalletName, initialAddress: exchangeStore.receiveCurrency == walletStore.type ? walletStore.address : null, initialIsAmountEditable: false, initialIsAddressEditable: true, isAmountEstimated: true, currencies: CryptoCurrency.all, onCurrencySelected: (currency) => exchangeStore .changeReceiveCurrency(currency: currency), imageArrow: arrowBottomCakeGreen, currencyValueValidator: (value) { exchangeStore.validateCryptoCurrency(value); return exchangeStore.errorMessage; }, addressTextFieldValidator: (value) { exchangeStore.validateAddress(value, cryptoCurrency: exchangeStore.receiveCurrency); return exchangeStore.errorMessage; }, )), ], ), ), bottomSectionPadding: EdgeInsets.only(top: 35, left: 20, right: 20), bottomSection: Column(children: [ Padding( padding: EdgeInsets.only(bottom: 15), child: Observer(builder: (_) { final description = exchangeStore.provider is XMRTOExchangeProvider ? S.of(context).amount_is_guaranteed : S.of(context).amount_is_estimate; return Center( child: Text( description, style: TextStyle(color: Palette.blueGrey, fontSize: 12), ), ); }), ), Observer( builder: (_) => LoadingPrimaryButton( text: S.of(context).exchange, onPressed: () { if (_formKey.currentState.validate()) { exchangeStore.createTrade(); } }, color: Theme.of(context) .primaryTextTheme .button .backgroundColor, borderColor: Theme.of(context) .primaryTextTheme .button .decorationColor, isLoading: exchangeStore.tradeState is TradeIsCreating, )), Observer(builder: (_) { final title = exchangeStore.provider.description.title; var imageSrc = ''; switch (exchangeStore.provider.description) { case ExchangeProviderDescription.xmrto: imageSrc = 'assets/images/xmr_btc.png'; break; case ExchangeProviderDescription.changeNow: imageSrc = 'assets/images/change_now.png'; break; case ExchangeProviderDescription.morphToken: imageSrc = 'assets/images/morph_icon.png'; break; } return Padding( padding: EdgeInsets.only(top: 20, bottom: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset(imageSrc), SizedBox(width: 10), Text( S.of(context).powered_by(title), style: TextStyle(fontSize: 14, color: Palette.powered), ) ], ), ); }) ]), )); } void _setReactions( BuildContext context, ExchangeStore store, WalletStore walletStore) { if (_isReactionsSet) { return; } final depositAddressController = depositKey.currentState.addressController; final depositAmountController = depositKey.currentState.amountController; final receiveAddressController = receiveKey.currentState.addressController; final receiveAmountController = receiveKey.currentState.amountController; final limitsState = store.limitsState; if (limitsState is LimitsLoadedSuccessfully) { final min = limitsState.limits.min != null ? limitsState.limits.min.toString() : null; final max = limitsState.limits.max != null ? limitsState.limits.max.toString() : null; final key = depositKey; key.currentState.changeLimits(min: min, max: max); } _onCurrencyChange(store.receiveCurrency, walletStore, receiveKey); _onCurrencyChange(store.depositCurrency, walletStore, depositKey); reaction( (_) => walletStore.name, (String _) => _onWalletNameChange( walletStore, store.receiveCurrency, receiveKey)); reaction( (_) => walletStore.name, (String _) => _onWalletNameChange( walletStore, store.depositCurrency, depositKey)); reaction( (_) => store.receiveCurrency, (CryptoCurrency currency) => _onCurrencyChange(currency, walletStore, receiveKey)); reaction( (_) => store.depositCurrency, (CryptoCurrency currency) => _onCurrencyChange(currency, walletStore, depositKey)); reaction((_) => store.depositAmount, (String amount) { if (depositKey.currentState.amountController.text != amount) { depositKey.currentState.amountController.text = amount; } }); reaction((_) => store.receiveAmount, (String amount) { if (receiveKey.currentState.amountController.text != store.receiveAmount) { receiveKey.currentState.amountController.text = amount; } }); reaction((_) => store.provider, (ExchangeProvider provider) { receiveKey.currentState.isAddressEditable(isEditable: true); receiveKey.currentState.isAmountEditable(isEditable: false); depositKey.currentState.isAddressEditable(isEditable: true); depositKey.currentState.isAmountEditable(isEditable: true); receiveKey.currentState.changeIsAmountEstimated(true); }); reaction((_) => store.tradeState, (ExchangeTradeState state) { if (state is TradeIsCreatedFailure) { WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text(S.of(context).error), content: Text(state.error), actions: [ FlatButton( child: Text(S.of(context).ok), onPressed: () => Navigator.of(context).pop()) ], ); }); }); } if (state is TradeIsCreatedSuccessfully) { Navigator.of(context) .pushNamed(Routes.exchangeConfirm, arguments: state.trade); } }); reaction((_) => store.limitsState, (LimitsState state) { String min; String max; if (state is LimitsLoadedSuccessfully) { min = state.limits.min != null ? state.limits.min.toString() : null; max = state.limits.max != null ? state.limits.max.toString() : null; } if (state is LimitsLoadedFailure) { min = '0'; max = '0'; } if (state is LimitsIsLoading) { min = '...'; max = '...'; } depositKey.currentState.changeLimits(min: min, max: max); receiveKey.currentState.changeLimits(min: null, max: null); }); depositAddressController.addListener( () => store.depositAddress = depositAddressController.text); depositAmountController.addListener(() { if (depositAmountController.text != store.depositAmount) { store.changeDepositAmount(amount: depositAmountController.text); } }); receiveAddressController.addListener( () => store.receiveAddress = receiveAddressController.text); receiveAmountController.addListener(() { if (receiveAmountController.text != store.receiveAmount) { store.changeReceiveAmount(amount: receiveAmountController.text); } }); reaction((_) => walletStore.address, (String address) { if (store.depositCurrency == CryptoCurrency.xmr) { depositKey.currentState.changeAddress(address: address); } if (store.receiveCurrency == CryptoCurrency.xmr) { receiveKey.currentState.changeAddress(address: address); } }); _isReactionsSet = true; } void _onCurrencyChange(CryptoCurrency currency, WalletStore walletStore, GlobalKey key) { final isCurrentTypeWallet = currency == walletStore.type; key.currentState.changeSelectedCurrency(currency); key.currentState .changeWalletName(isCurrentTypeWallet ? walletStore.name : null); key.currentState .changeAddress(address: isCurrentTypeWallet ? walletStore.address : ''); key.currentState.changeAmount(amount: ''); } void _onWalletNameChange(WalletStore walletStore, CryptoCurrency currency, GlobalKey key) { final isCurrentTypeWallet = currency == walletStore.type; if (isCurrentTypeWallet) { key.currentState.changeWalletName(walletStore.name); key.currentState.addressController.text = walletStore.address; } else if (key.currentState.addressController.text == walletStore.address) { key.currentState.changeWalletName(null); key.currentState.addressController.text = null; } } }