diff --git a/assets/images/usdterc.png b/assets/images/usdterc.png new file mode 100644 index 000000000..212b301b5 Binary files /dev/null and b/assets/images/usdterc.png differ diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 19810c636..e043e3b1e 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -38,6 +38,8 @@ class AddressValidator extends TextValidator { return '[0-9a-zA-Z]'; case CryptoCurrency.usdt: return '[0-9a-zA-Z]'; + case CryptoCurrency.usdterc20: + return '[0-9a-zA-Z]'; case CryptoCurrency.xlm: return '[0-9a-zA-Z]'; case CryptoCurrency.xrp: @@ -75,6 +77,8 @@ class AddressValidator extends TextValidator { return [34]; case CryptoCurrency.usdt: return [42]; + case CryptoCurrency.usdterc20: + return [42]; case CryptoCurrency.xlm: return [56]; case CryptoCurrency.xrp: diff --git a/lib/entities/crypto_currency.dart b/lib/entities/crypto_currency.dart index b836393bd..d5f23a0fa 100644 --- a/lib/entities/crypto_currency.dart +++ b/lib/entities/crypto_currency.dart @@ -22,6 +22,7 @@ class CryptoCurrency extends EnumerableItem with Serializable { CryptoCurrency.nano, CryptoCurrency.trx, CryptoCurrency.usdt, + CryptoCurrency.usdterc20, CryptoCurrency.xlm, CryptoCurrency.xrp ]; @@ -38,8 +39,9 @@ class CryptoCurrency extends EnumerableItem with Serializable { static const nano = CryptoCurrency(title: 'NANO', raw: 10); static const trx = CryptoCurrency(title: 'TRX', raw: 11); static const usdt = CryptoCurrency(title: 'USDT', raw: 12); - static const xlm = CryptoCurrency(title: 'XLM', raw: 13); - static const xrp = CryptoCurrency(title: 'XRP', raw: 14); + static const usdterc20 = CryptoCurrency(title: 'USDTERC20', raw: 13); + static const xlm = CryptoCurrency(title: 'XLM', raw: 14); + static const xrp = CryptoCurrency(title: 'XRP', raw: 15); static CryptoCurrency deserialize({int raw}) { switch (raw) { @@ -70,8 +72,10 @@ class CryptoCurrency extends EnumerableItem with Serializable { case 12: return CryptoCurrency.usdt; case 13: - return CryptoCurrency.xlm; + return CryptoCurrency.usdterc20; case 14: + return CryptoCurrency.xlm; + case 15: return CryptoCurrency.xrp; default: return null; @@ -106,6 +110,8 @@ class CryptoCurrency extends EnumerableItem with Serializable { return CryptoCurrency.trx; case 'usdt': return CryptoCurrency.usdt; + case 'usdterc20': + return CryptoCurrency.usdterc20; case 'xlm': return CryptoCurrency.xlm; case 'xrp': diff --git a/lib/exchange/changenow/changenow_exchange_provider.dart b/lib/exchange/changenow/changenow_exchange_provider.dart index 94f8ea6ac..2a9b38c64 100644 --- a/lib/exchange/changenow/changenow_exchange_provider.dart +++ b/lib/exchange/changenow/changenow_exchange_provider.dart @@ -146,7 +146,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider { @override Future calculateAmount( - {CryptoCurrency from, CryptoCurrency to, double amount}) async { + {CryptoCurrency from, CryptoCurrency to, double amount, + bool isReceiveAmount}) async { final url = apiUri + _exchangeAmountUriSufix + amount.toString() + diff --git a/lib/exchange/exchange_provider.dart b/lib/exchange/exchange_provider.dart index 418d20b8c..65f7ad8ba 100644 --- a/lib/exchange/exchange_provider.dart +++ b/lib/exchange/exchange_provider.dart @@ -20,5 +20,5 @@ abstract class ExchangeProvider { Future createTrade({TradeRequest request}); Future findTradeById({@required String id}); Future calculateAmount( - {CryptoCurrency from, CryptoCurrency to, double amount}); + {CryptoCurrency from, CryptoCurrency to, double amount, bool isReceiveAmount}); } diff --git a/lib/exchange/morphtoken/morphtoken_exchange_provider.dart b/lib/exchange/morphtoken/morphtoken_exchange_provider.dart index 6c4aade37..195f72ae5 100644 --- a/lib/exchange/morphtoken/morphtoken_exchange_provider.dart +++ b/lib/exchange/morphtoken/morphtoken_exchange_provider.dart @@ -185,7 +185,8 @@ class MorphTokenExchangeProvider extends ExchangeProvider { @override Future calculateAmount( - {CryptoCurrency from, CryptoCurrency to, double amount}) async { + {CryptoCurrency from, CryptoCurrency to, double amount, + bool isReceiveAmount}) async { final url = apiUri + _ratesURISuffix; final response = await get(url); final responseJSON = json.decode(response.body) as Map; diff --git a/lib/exchange/xmrto/xmrto_exchange_provider.dart b/lib/exchange/xmrto/xmrto_exchange_provider.dart index ca1253310..25232db65 100644 --- a/lib/exchange/xmrto/xmrto_exchange_provider.dart +++ b/lib/exchange/xmrto/xmrto_exchange_provider.dart @@ -21,8 +21,8 @@ class XMRTOExchangeProvider extends ExchangeProvider { ]); static const userAgent = 'CakeWallet/XMR iOS'; - static const originalApiUri = 'https://xmr.to/api/v2/xmr2btc'; - static const proxyApiUri = 'https://xmrproxy.net/api/v2/xmr2btc'; + static const originalApiUri = 'https://xmr.to/api/v3/xmr2btc'; + static const proxyApiUri = 'https://xmrproxy.net/api/v3/xmr2btc'; static const _orderParameterUriSufix = '/order_parameter_query'; static const _orderStatusUriSufix = '/order_status_query/'; static const _orderCreateUriSufix = '/order_create/'; @@ -61,9 +61,9 @@ class XMRTOExchangeProvider extends ExchangeProvider { } final responseJSON = json.decode(response.body) as Map; - double min = responseJSON['lower_limit'] as double; - double max = responseJSON['upper_limit'] as double; - final price = responseJSON['price'] as double; + double min = double.parse(responseJSON['lower_limit'] as String); + double max = double.parse(responseJSON['upper_limit'] as String); + final price = double.parse(responseJSON['price'] as String); if (price > 0) { try { @@ -88,7 +88,12 @@ class XMRTOExchangeProvider extends ExchangeProvider { final _request = request as XMRTOTradeRequest; final url = await getApiUri() + _orderCreateUriSufix; final body = { - 'xmr_amount': _request.amount, + 'amount': _request.isBTCRequest + ? _request.receiveAmount + : _request.amount, + 'amount_currency': _request.isBTCRequest + ? _request.to.toString() + : _request.from.toString(), 'btc_dest_address': _request.address }; final response = await post(url, @@ -165,7 +170,8 @@ class XMRTOExchangeProvider extends ExchangeProvider { @override Future calculateAmount( - {CryptoCurrency from, CryptoCurrency to, double amount}) async { + {CryptoCurrency from, CryptoCurrency to, double amount, + bool isReceiveAmount}) async { if (from != CryptoCurrency.xmr && to != CryptoCurrency.btc) { return 0; } @@ -174,7 +180,9 @@ class XMRTOExchangeProvider extends ExchangeProvider { _rate = await _fetchRates(); } - final double result = _rate * amount; + final double result = isReceiveAmount + ? _rate == 0 ? 0 : amount / _rate + : _rate * amount; return double.parse(result.toStringAsFixed(12)); } @@ -185,7 +193,7 @@ class XMRTOExchangeProvider extends ExchangeProvider { final response = await get(url, headers: {'Content-Type': 'application/json'}); final responseJSON = json.decode(response.body) as Map; - final price = responseJSON['price'] as double; + final price = double.parse(responseJSON['price'] as String); return price; } catch (e) { diff --git a/lib/exchange/xmrto/xmrto_trade_request.dart b/lib/exchange/xmrto/xmrto_trade_request.dart index a456b572a..e88db176d 100644 --- a/lib/exchange/xmrto/xmrto_trade_request.dart +++ b/lib/exchange/xmrto/xmrto_trade_request.dart @@ -7,12 +7,16 @@ class XMRTOTradeRequest extends TradeRequest { {@required this.from, @required this.to, @required this.amount, + @required this.receiveAmount, @required this.address, - @required this.refundAddress}); + @required this.refundAddress, + @required this.isBTCRequest}); final CryptoCurrency from; final CryptoCurrency to; final String amount; + final String receiveAmount; final String address; final String refundAddress; + final bool isBTCRequest; } diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index dee016ee8..c66f5f2c4 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -220,6 +220,9 @@ class ContactListPage extends BasePage { case CryptoCurrency.usdt: image = Image.asset('assets/images/usdt.png', height: 24, width: 24); break; + case CryptoCurrency.usdterc20: + image = Image.asset('assets/images/usdterc.png', height: 24, width: 24); + break; case CryptoCurrency.xlm: image = Image.asset('assets/images/xlm.png', height: 24, width: 24); break; diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index c734ef5a0..694f74129 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -1,4 +1,5 @@ import 'dart:ui'; +import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -207,7 +208,9 @@ class ExchangePage extends BasePage { exchangeViewModel.wallet.currency ? exchangeViewModel.wallet.address : exchangeViewModel.receiveAddress, - initialIsAmountEditable: false, + initialIsAmountEditable: + exchangeViewModel.provider is + XMRTOExchangeProvider ? true : false, initialIsAddressEditable: exchangeViewModel .isReceiveAddressEnabled, @@ -520,6 +523,12 @@ class ExchangePage extends BasePage { receiveKey.currentState.isAddressEditable(isEditable: isEnabled); }); + reaction((_) => exchangeViewModel.provider, (ExchangeProvider provider) { + provider is XMRTOExchangeProvider + ? receiveKey.currentState.isAmountEditable(isEditable: true) + : receiveKey.currentState.isAmountEditable(isEditable: false); + }); + // FIXME: FIXME // reaction((_) => exchangeViewModel.tradeState, (ExchangeTradeState state) { @@ -573,6 +582,7 @@ class ExchangePage extends BasePage { if (depositAmountController.text != exchangeViewModel.depositAmount) { exchangeViewModel.changeDepositAmount( amount: depositAmountController.text); + exchangeViewModel.isReceiveAmountEntered = false; } }); @@ -583,6 +593,7 @@ class ExchangePage extends BasePage { if (receiveAmountController.text != exchangeViewModel.receiveAmount) { exchangeViewModel.changeReceiveAmount( amount: receiveAmountController.text); + exchangeViewModel.isReceiveAmountEntered = true; } }); diff --git a/lib/src/screens/exchange/widgets/currency_picker.dart b/lib/src/screens/exchange/widgets/currency_picker.dart index 668f99e77..8a5c2794f 100644 --- a/lib/src/screens/exchange/widgets/currency_picker.dart +++ b/lib/src/screens/exchange/widgets/currency_picker.dart @@ -5,8 +5,9 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart'; +import 'package:cake_wallet/src/widgets/cake_scrollbar.dart'; -class CurrencyPicker extends StatelessWidget { +class CurrencyPicker extends StatefulWidget { CurrencyPicker({ @required this.selectedAtIndex, @required this.items, @@ -18,12 +19,47 @@ class CurrencyPicker extends StatelessWidget { final List items; final String title; final Function(CryptoCurrency) onItemSelected; + + @override + CurrencyPickerState createState() => CurrencyPickerState( + selectedAtIndex, + items, + title, + onItemSelected + ); +} + +class CurrencyPickerState extends State { + CurrencyPickerState( + this.selectedAtIndex, + this.items, + this.title, + this.onItemSelected): itemsCount = items.length; + + final int selectedAtIndex; + final List items; + final String title; + final Function(CryptoCurrency) onItemSelected; + final closeButton = Image.asset('assets/images/close.png', color: Palette.darkBlueCraiola, ); + final int crossAxisCount = 3; + final int itemsCount; + final double backgroundHeight = 280; + final double thumbHeight = 72; + ScrollController controller = ScrollController(); + double fromTop = 0; @override Widget build(BuildContext context) { + controller.addListener(() { + fromTop = controller.hasClients + ? (controller.offset / controller.position.maxScrollExtent * (backgroundHeight - thumbHeight)) + : 0; + setState(() {}); + }); + return AlertBackground( child: Stack( alignment: Alignment.center, @@ -52,54 +88,72 @@ class CurrencyPicker extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(14)), child: Container( - height: 400, - width: 300, - color: Theme.of(context).accentTextTheme.title.backgroundColor, - child: GridView.count( - shrinkWrap: true, - crossAxisCount: 3, - childAspectRatio: 1.25, - physics: const NeverScrollableScrollPhysics(), - crossAxisSpacing: 1, - mainAxisSpacing: 1, - children: List.generate(items.length, (index) { + height: 320, + width: 300, + color: Theme.of(context).accentTextTheme.title.backgroundColor, + child: Stack( + alignment: Alignment.center, + children: [ + GridView.count( + controller: controller, + crossAxisCount: crossAxisCount, + childAspectRatio: 1.25, + crossAxisSpacing: 1, + mainAxisSpacing: 1, + children: List.generate( + itemsCount + + getExtraEmptyTilesCount(crossAxisCount, itemsCount), + (index) { - final item = items[index]; - final isItemSelected = index == selectedAtIndex; + if (index >= itemsCount) { + return Container( + color: Theme.of(context).accentTextTheme.title.color, + ); + } - final color = isItemSelected - ? Theme.of(context).textTheme.body2.color - : Theme.of(context).accentTextTheme.title.color; - final textColor = isItemSelected - ? Palette.blueCraiola - : Theme.of(context).primaryTextTheme.title.color; + final item = items[index]; + final isItemSelected = index == selectedAtIndex; - return GestureDetector( - onTap: () { - if (onItemSelected == null) { - return; - } - Navigator.of(context).pop(); - onItemSelected(item); - }, - child: Container( - color: color, - child: Center( - child: Text( - item.toString(), - style: TextStyle( - fontSize: 18, - fontFamily: 'Poppins', - fontWeight: FontWeight.w600, - decoration: TextDecoration.none, - color: textColor - ), - ), - ), - ), - ); - }) - ), + final color = isItemSelected + ? Theme.of(context).textTheme.body2.color + : Theme.of(context).accentTextTheme.title.color; + final textColor = isItemSelected + ? Palette.blueCraiola + : Theme.of(context).primaryTextTheme.title.color; + + return GestureDetector( + onTap: () { + if (onItemSelected == null) { + return; + } + Navigator.of(context).pop(); + onItemSelected(item); + }, + child: Container( + color: color, + child: Center( + child: Text( + item.toString(), + style: TextStyle( + fontSize: 15, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + color: textColor + ), + ), + ), + ), + ); + }) + ), + CakeScrollbar( + backgroundHeight: backgroundHeight, + thumbHeight: thumbHeight, + fromTop: fromTop + ) + ], + ) ), ), ), @@ -111,4 +165,9 @@ class CurrencyPicker extends StatelessWidget { ) ); } + + int getExtraEmptyTilesCount(int crossAxisCount, int itemsCount) { + final int tilesInNewRowCount = itemsCount % crossAxisCount; + return tilesInNewRowCount == 0 ? 0 : crossAxisCount - tilesInNewRowCount; + } } \ No newline at end of file diff --git a/lib/utils/show_pop_up.dart b/lib/utils/show_pop_up.dart index a97fb3762..280bcff3c 100644 --- a/lib/utils/show_pop_up.dart +++ b/lib/utils/show_pop_up.dart @@ -14,8 +14,8 @@ Future showPopUp({ context: context, builder: builder, barrierDismissible: barrierDismissible, - barrierColor: barrierColor, - useSafeArea: useSafeArea, + //barrierColor: barrierColor, + //useSafeArea: useSafeArea, useRootNavigator: useRootNavigator, routeSettings: routeSettings, child: child); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index d1a29c731..caca84b83 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -48,6 +48,7 @@ abstract class ExchangeViewModelBase with Store { tradeState = ExchangeTradeStateInitial(); _cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12; provider = providersForCurrentPair().first; + isReceiveAmountEntered = false; loadLimits(); } @@ -92,6 +93,8 @@ abstract class ExchangeViewModelBase with Store { @observable bool isReceiveAddressEnabled; + bool isReceiveAmountEntered; + Limits limits; NumberFormat _cryptoNumberFormat; @@ -138,7 +141,8 @@ abstract class ExchangeViewModelBase with Store { provider .calculateAmount( - from: depositCurrency, to: receiveCurrency, amount: _amount) + from: depositCurrency, to: receiveCurrency, amount: _amount, + isReceiveAmount: true) .then((amount) => _cryptoNumberFormat .format(amount) .toString() @@ -159,7 +163,8 @@ abstract class ExchangeViewModelBase with Store { final _amount = double.parse(amount); provider .calculateAmount( - from: depositCurrency, to: receiveCurrency, amount: _amount) + from: depositCurrency, to: receiveCurrency, amount: _amount, + isReceiveAmount: false) .then((amount) => _cryptoNumberFormat .format(amount) .toString() @@ -191,8 +196,10 @@ abstract class ExchangeViewModelBase with Store { from: depositCurrency, to: receiveCurrency, amount: depositAmount, + receiveAmount: receiveAmount, address: receiveAddress, - refundAddress: depositAddress); + refundAddress: depositAddress, + isBTCRequest: isReceiveAmountEntered); amount = depositAmount; currency = depositCurrency; } diff --git a/pubspec.lock b/pubspec.lock index e1d6a52a9..9b56163ba 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.2" + version: "2.4.1" auto_size_text: dependency: "direct main" description: @@ -210,7 +210,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.13" + version: "1.14.12" connectivity: dependency: "direct main" description: @@ -252,7 +252,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.4" csslib: dependency: transitive description: @@ -330,13 +330,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" ffi: dependency: transitive description: @@ -512,7 +505,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.18" + version: "2.1.12" intl: dependency: "direct main" description: @@ -568,7 +561,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.8" + version: "0.12.6" meta: dependency: transitive description: @@ -638,7 +631,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.6.4" path_drawing: dependency: transitive description: @@ -701,7 +694,7 @@ packages: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "3.0.4" + version: "2.4.0" platform: dependency: transitive description: @@ -881,7 +874,7 @@ packages: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.5" + version: "1.9.3" stream_channel: dependency: transitive description: @@ -916,7 +909,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.17" + version: "0.2.15" time: dependency: transitive description: @@ -937,7 +930,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.1.6" url_launcher: dependency: "direct main" description: @@ -1028,7 +1021,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "4.5.1" + version: "3.6.1" yaml: dependency: "direct main" description: @@ -1037,5 +1030,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=2.9.0-14.0.dev <3.0.0" + dart: ">=2.7.0 <3.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0"