From a4c279b5381ce81b3608ea3d4bca1666cb9de914 Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Fri, 9 Jun 2023 14:46:00 +0300 Subject: [PATCH 1/3] CW-411 Update sideshift api (#959) * Update sideshift api * Fix sideShift api v2 rate * Fix rate calculation on sideshift * Additional fixes * Minor: Add additional unsupported * Add network to quote * Add amount and affiliateID to quote * Refactor tag/network assigning * Fix syntax errors with rate qpi * Add network for fetching rate * Remove amount from rate endpoint * Fix transaction depositAmount * Fix issues from code review * Fix issues from code review * Remove affiliate ID --------- Co-authored-by: Justin Ehrenhofer --- .github/workflows/pr_test_build.yml | 1 - .../sideshift_exchange_provider.dart | 192 ++++++++++-------- .../exchange/exchange_trade_view_model.dart | 9 +- .../exchange/exchange_view_model.dart | 4 +- tool/utils/secret_key.dart | 1 - 5 files changed, 118 insertions(+), 89 deletions(-) diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 076fc2ea1..4ca762c12 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -106,7 +106,6 @@ jobs: echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const sideShiftApiKey = '${{ secrets.SIDE_SHIFT_API_KEY }}';" >> lib/.secrets.g.dart echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart diff --git a/lib/exchange/sideshift/sideshift_exchange_provider.dart b/lib/exchange/sideshift/sideshift_exchange_provider.dart index a5b0c990f..26575f2c1 100644 --- a/lib/exchange/sideshift/sideshift_exchange_provider.dart +++ b/lib/exchange/sideshift/sideshift_exchange_provider.dart @@ -19,10 +19,10 @@ class SideShiftExchangeProvider extends ExchangeProvider { static const affiliateId = secrets.sideShiftAffiliateId; static const apiBaseUrl = 'https://sideshift.ai/api'; - static const rangePath = '/v1/pairs'; - static const orderPath = '/v1/orders'; - static const quotePath = '/v1/quotes'; - static const permissionPath = '/v1/permissions'; + static const rangePath = '/v2/pair'; + static const orderPath = '/v2/shifts'; + static const quotePath = '/v2/quotes'; + static const permissionPath = '/v2/permissions'; static const List _notSupported = [ CryptoCurrency.xhv, @@ -36,23 +36,22 @@ class SideShiftExchangeProvider extends ExchangeProvider { CryptoCurrency.scrt, CryptoCurrency.stx, CryptoCurrency.bttc, + CryptoCurrency.usdt, + CryptoCurrency.eos, ]; static List _supportedPairs() { - final supportedCurrencies = CryptoCurrency.all - .where((element) => !_notSupported.contains(element)) - .toList(); + final supportedCurrencies = + CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList(); return supportedCurrencies - .map((i) => supportedCurrencies - .map((k) => ExchangePair(from: i, to: k, reverse: true))) + .map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true))) .expand((i) => i) .toList(); } @override - ExchangeProviderDescription get description => - ExchangeProviderDescription.sideShift; + ExchangeProviderDescription get description => ExchangeProviderDescription.sideShift; @override Future fetchRate( @@ -65,17 +64,18 @@ class SideShiftExchangeProvider extends ExchangeProvider { if (amount == 0) { return 0.0; } - final fromCurrency = _normalizeCryptoCurrency(from); - final toCurrency = _normalizeCryptoCurrency(to); - final url = - apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency; + + final fromCurrency = from.title.toLowerCase(); + final toCurrency = to.title.toLowerCase(); + final depositNetwork = _networkFor(from); + final settleNetwork = _networkFor(to); + + final url = "$apiBaseUrl$rangePath/$fromCurrency-$depositNetwork/$toCurrency-$settleNetwork"; + final uri = Uri.parse(url); final response = await get(uri); final responseJSON = json.decode(response.body) as Map; final rate = double.parse(responseJSON['rate'] as String); - final max = double.parse(responseJSON['max'] as String); - - if (amount > max) return 0.00; return rate; } catch (_) { @@ -101,25 +101,38 @@ class SideShiftExchangeProvider extends ExchangeProvider { } final responseJSON = json.decode(response.body) as Map; - final canCreateOrder = responseJSON['createOrder'] as bool; - final canCreateQuote = responseJSON['createQuote'] as bool; - return canCreateOrder && canCreateQuote; + final cancreateShift = responseJSON['createShift'] as bool; + return cancreateShift; } @override - Future createTrade( - {required TradeRequest request, required bool isFixedRateMode}) async { + Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { final _request = request as SideShiftRequest; - final quoteId = await _createQuote(_request); - final url = apiBaseUrl + orderPath; - final headers = {'Content-Type': 'application/json'}; + String url = ''; + final depositCoin = request.depositMethod.title.toLowerCase(); + final settleCoin = request.settleMethod.title.toLowerCase(); final body = { - 'type': 'fixed', - 'quoteId': quoteId, 'affiliateId': affiliateId, 'settleAddress': _request.settleAddress, - 'refundAddress': _request.refundAddress + 'refundAddress': _request.refundAddress, }; + + if (isFixedRateMode) { + final quoteId = await _createQuote(_request); + body['quoteId'] = quoteId; + + url = apiBaseUrl + orderPath + '/fixed'; + } else { + url = apiBaseUrl + orderPath + '/variable'; + final depositNetwork = _networkFor(request.depositMethod); + final settleNetwork = _networkFor(request.settleMethod); + body["depositCoin"] = depositCoin; + body["settleCoin"] = settleCoin; + body["settleNetwork"] = settleNetwork; + body["depositNetwork"] = depositNetwork; + } + final headers = {'Content-Type': 'application/json'}; + final uri = Uri.parse(url); final response = await post(uri, headers: headers, body: json.encode(body)); @@ -136,8 +149,9 @@ class SideShiftExchangeProvider extends ExchangeProvider { final responseJSON = json.decode(response.body) as Map; final id = responseJSON['id'] as String; - final inputAddress = responseJSON['depositAddress']['address'] as String; - final settleAddress = responseJSON['settleAddress']['address'] as String; + final inputAddress = responseJSON['depositAddress'] as String; + final settleAddress = responseJSON['settleAddress'] as String; + final depositAmount = responseJSON['depositAmount'] as String?; return Trade( id: id, @@ -147,7 +161,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { inputAddress: inputAddress, refundAddress: settleAddress, state: TradeState.created, - amount: _request.depositAmount, + amount: depositAmount ?? _request.depositAmount, payoutAddress: settleAddress, createdAt: DateTime.now(), ); @@ -156,13 +170,17 @@ class SideShiftExchangeProvider extends ExchangeProvider { Future _createQuote(SideShiftRequest request) async { final url = apiBaseUrl + quotePath; final headers = {'Content-Type': 'application/json'}; - final depositMethod = _normalizeCryptoCurrency(request.depositMethod); - final settleMethod = _normalizeCryptoCurrency(request.settleMethod); + final depositMethod = request.depositMethod.title.toLowerCase(); + final settleMethod = request.settleMethod.title.toLowerCase(); + final depositNetwork = _networkFor(request.depositMethod); + final settleNetwork = _networkFor(request.settleMethod); final body = { - 'depositMethod': depositMethod, - 'settleMethod': settleMethod, + 'depositCoin': depositMethod, + 'settleCoin': settleMethod, 'affiliateId': affiliateId, - 'depositAmount': request.depositAmount, + 'settleAmount': request.depositAmount, + 'settleNetwork': settleNetwork, + 'depositNetwork': depositNetwork, }; final uri = Uri.parse(url); final response = await post(uri, headers: headers, body: json.encode(body)); @@ -189,9 +207,15 @@ class SideShiftExchangeProvider extends ExchangeProvider { {required CryptoCurrency from, required CryptoCurrency to, required bool isFixedRateMode}) async { - final fromCurrency = _normalizeCryptoCurrency(from); - final toCurrency = _normalizeCryptoCurrency(to); - final url = apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency; + final fromCurrency = isFixedRateMode ? to : from; + final toCurrency = isFixedRateMode ? from : to; + + final fromNetwork = _networkFor(fromCurrency); + final toNetwork = _networkFor(toCurrency); + + final url = + "$apiBaseUrl$rangePath/${fromCurrency.title.toLowerCase()}-$fromNetwork/${toCurrency.title.toLowerCase()}-$toNetwork"; + final uri = Uri.parse(url); final response = await get(uri); @@ -210,6 +234,14 @@ class SideShiftExchangeProvider extends ExchangeProvider { final min = double.tryParse(responseJSON['min'] as String? ?? ''); final max = double.tryParse(responseJSON['max'] as String? ?? ''); + if (isFixedRateMode) { + final currentRate = double.parse(responseJSON['rate'] as String); + return Limits( + min: min != null ? (min * currentRate) : null, + max: max != null ? (max * currentRate) : null, + ); + } + return Limits(min: min, max: max); } @@ -227,8 +259,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { final responseJSON = json.decode(response.body) as Map; final error = responseJSON['error']['message'] as String; - throw TradeNotFoundException(id, - provider: description, description: error); + throw TradeNotFoundException(id, provider: description, description: error); } if (response.statusCode != 200) { @@ -236,36 +267,32 @@ class SideShiftExchangeProvider extends ExchangeProvider { } final responseJSON = json.decode(response.body) as Map; - final fromCurrency = responseJSON['depositMethodId'] as String; + final fromCurrency = responseJSON['depositCoin'] as String; final from = CryptoCurrency.fromString(fromCurrency); - final toCurrency = responseJSON['settleMethodId'] as String; + final toCurrency = responseJSON['settleCoin'] as String; final to = CryptoCurrency.fromString(toCurrency); - final inputAddress = responseJSON['depositAddress']['address'] as String; - final expectedSendAmount = responseJSON['depositAmount'].toString(); - final deposits = responseJSON['deposits'] as List?; - final settleAddress = responseJSON['settleAddress']['address'] as String; + final inputAddress = responseJSON['depositAddress'] as String; + final expectedSendAmount = responseJSON['depositAmount'] as String?; + final status = responseJSON['status'] as String?; + final settleAddress = responseJSON['settleAddress'] as String; TradeState? state; - String? status; - if (deposits?.isNotEmpty ?? false) { - status = deposits![0]['status'] as String?; - } state = TradeState.deserialize(raw: status ?? 'created'); + final isVariable = (responseJSON['type'] as String) == 'variable'; - final expiredAtRaw = responseJSON['expiresAtISO'] as String; - final expiredAt = DateTime.tryParse(expiredAtRaw)?.toLocal(); + final expiredAtRaw = responseJSON['expiresAt'] as String; + final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal(); return Trade( - id: id, - from: from, - to: to, - provider: description, - inputAddress: inputAddress, - amount: expectedSendAmount, - state: state, - expiredAt: expiredAt, - payoutAddress: settleAddress - ); + id: id, + from: from, + to: to, + provider: description, + inputAddress: inputAddress, + amount: expectedSendAmount ?? '', + state: state, + expiredAt: expiredAt, + payoutAddress: settleAddress); } @override @@ -280,28 +307,25 @@ class SideShiftExchangeProvider extends ExchangeProvider { @override String get title => 'SideShift'; - static String _normalizeCryptoCurrency(CryptoCurrency currency) { - switch (currency) { - case CryptoCurrency.zaddr: - return 'zaddr'; - case CryptoCurrency.zec: - return 'zec'; - case CryptoCurrency.bnb: - return currency.tag!.toLowerCase(); - case CryptoCurrency.usdterc20: - return 'usdtErc20'; - case CryptoCurrency.usdttrc20: - return 'usdtTrc20'; - case CryptoCurrency.usdcpoly: - return 'usdcpolygon'; - case CryptoCurrency.usdcsol: - return 'usdcsol'; - case CryptoCurrency.maticpoly: + String _networkFor(CryptoCurrency currency) => + currency.tag != null ? _normalizeTag(currency.tag!) : 'mainnet'; + + String _normalizeTag(String tag) { + switch (tag) { + case 'ETH': + return 'ethereum'; + case 'TRX': + return 'tron'; + case 'LN': + return 'lightning'; + case 'POLY': return 'polygon'; - case CryptoCurrency.btcln: - return 'ln'; + case 'ZEC': + return 'zcash'; + case 'AVAXC': + return 'avax'; default: - return currency.title.toLowerCase(); + return tag.toLowerCase(); } } } diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 1d9f4f582..d5aeaa4fc 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -47,7 +47,7 @@ abstract class ExchangeTradeViewModelBase with Store { case ExchangeProviderDescription.simpleSwap: _provider = SimpleSwapExchangeProvider(); break; - case ExchangeProviderDescription.trocador: + case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; } @@ -114,6 +114,10 @@ abstract class ExchangeTradeViewModelBase with Store { updatedTrade.createdAt = trade.createdAt; } + if (updatedTrade.amount.isEmpty) { + updatedTrade.amount = trade.amount; + } + trade = updatedTrade; _updateItems(); @@ -123,7 +127,8 @@ abstract class ExchangeTradeViewModelBase with Store { } void _updateItems() { - final tagFrom = tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : ''; + final tagFrom = + tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : ''; final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : ''; items.clear(); items.add(ExchangeTradeItem( diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 77fb9a3d4..f823001a4 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -443,7 +443,9 @@ abstract class ExchangeViewModelBase with Store { request = SideShiftRequest( depositMethod: depositCurrency, settleMethod: receiveCurrency, - depositAmount: depositAmount.replaceAll(',', '.'), + depositAmount: isFixedRateMode + ? receiveAmount.replaceAll(',', '.') + : depositAmount.replaceAll(',', '.'), settleAddress: receiveAddress, refundAddress: depositAddress, ); diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 621ab1cfc..b5b4de6d6 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -20,7 +20,6 @@ class SecretKey { SecretKey('moonPayApiKey', () => ''), SecretKey('moonPaySecretKey', () => ''), SecretKey('sideShiftAffiliateId', () => ''), - SecretKey('sideShiftApiKey', () => ''), SecretKey('simpleSwapApiKey', () => ''), SecretKey('simpleSwapApiKeyDesktop', () => ''), SecretKey('anypayToken', () => ''), From b16cfaaff5ac7e318dba9557b3eba6c5af6d30d7 Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Fri, 9 Jun 2023 19:29:34 -0300 Subject: [PATCH 2/3] Cw 231 display balance in send and transaction screens when long pressing and when show balance is disabled (#926) * feat: allow reversing displayMode by long pressing on transactions page * feat: allow reversing displayMode by long pressing on send page * revert: revert transaction_list_item.dart but keep BalanceDisplayMode get displayMode => balanceViewModel.displayMode --- .../dashboard/widgets/transactions_page.dart | 201 ++++--- lib/src/screens/send/send_page.dart | 534 ++++++++++-------- .../dashboard/transaction_list_item.dart | 3 +- 3 files changed, 396 insertions(+), 342 deletions(-) diff --git a/lib/src/screens/dashboard/widgets/transactions_page.dart b/lib/src/screens/dashboard/widgets/transactions_page.dart index c80edc8d9..61ab84265 100644 --- a/lib/src/screens/dashboard/widgets/transactions_page.dart +++ b/lib/src/screens/dashboard/widgets/transactions_page.dart @@ -25,107 +25,126 @@ class TransactionsPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - color: ResponsiveLayoutUtil.instance.isMobile(context) - ? null - : Theme.of(context).colorScheme.background, - padding: EdgeInsets.only(top: 24, bottom: 24), - child: Column( - children: [ - HeaderRow(dashboardViewModel: dashboardViewModel), - Expanded(child: Observer(builder: (_) { - final items = dashboardViewModel.items; + return GestureDetector( + onLongPress: () => dashboardViewModel.balanceViewModel.isReversing = + !dashboardViewModel.balanceViewModel.isReversing, + onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing = + !dashboardViewModel.balanceViewModel.isReversing, + child: Container( + color: ResponsiveLayoutUtil.instance.isMobile(context) + ? null + : Theme.of(context).colorScheme.background, + padding: EdgeInsets.only(top: 24, bottom: 24), + child: Column( + children: [ + HeaderRow(dashboardViewModel: dashboardViewModel), + Expanded(child: Observer(builder: (_) { + final items = dashboardViewModel.items; - return items.isNotEmpty - ? ListView.builder( - itemCount: items.length, - itemBuilder: (context, index) { - final item = items[index]; + return items.isNotEmpty + ? ListView.builder( + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; - if (item is DateSectionItem) { - return DateSectionRaw(date: item.date); - } + if (item is DateSectionItem) { + return DateSectionRaw(date: item.date); + } - if (item is TransactionListItem) { - final transaction = item.transaction; + if (item is TransactionListItem) { + final transaction = item.transaction; - return Observer( - builder: (_) => TransactionRow( - onTap: () => Navigator.of(context) - .pushNamed(Routes.transactionDetails, arguments: transaction), - direction: transaction.direction, - formattedDate: DateFormat('HH:mm').format(transaction.date), - formattedAmount: item.formattedCryptoAmount, - formattedFiatAmount: - dashboardViewModel.balanceViewModel.isFiatDisabled - ? '' - : item.formattedFiatAmount, - isPending: transaction.isPending, - title: item.formattedTitle + item.formattedStatus)); - } + return Observer( + builder: (_) => TransactionRow( + onTap: () => Navigator.of(context).pushNamed( + Routes.transactionDetails, + arguments: transaction), + direction: transaction.direction, + formattedDate: DateFormat('HH:mm') + .format(transaction.date), + formattedAmount: item.formattedCryptoAmount, + formattedFiatAmount: dashboardViewModel + .balanceViewModel.isFiatDisabled + ? '' + : item.formattedFiatAmount, + isPending: transaction.isPending, + title: item.formattedTitle + + item.formattedStatus)); + } - if (item is AnonpayTransactionListItem) { - final transactionInfo = item.transaction; + if (item is AnonpayTransactionListItem) { + final transactionInfo = item.transaction; - return AnonpayTransactionRow( - onTap: () => Navigator.of(context) - .pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo), - currency: transactionInfo.fiatAmount != null - ? transactionInfo.fiatEquiv ?? '' - : CryptoCurrency.fromFullName(transactionInfo.coinTo) - .name - .toUpperCase(), - provider: transactionInfo.provider, - amount: transactionInfo.fiatAmount?.toString() ?? - (transactionInfo.amountTo?.toString() ?? ''), - createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt), - ); - } + return AnonpayTransactionRow( + onTap: () => Navigator.of(context).pushNamed( + Routes.anonPayDetailsPage, + arguments: transactionInfo), + currency: transactionInfo.fiatAmount != null + ? transactionInfo.fiatEquiv ?? '' + : CryptoCurrency.fromFullName( + transactionInfo.coinTo) + .name + .toUpperCase(), + provider: transactionInfo.provider, + amount: transactionInfo.fiatAmount?.toString() ?? + (transactionInfo.amountTo?.toString() ?? ''), + createdAt: DateFormat('HH:mm') + .format(transactionInfo.createdAt), + ); + } - if (item is TradeListItem) { - final trade = item.trade; + if (item is TradeListItem) { + final trade = item.trade; - return Observer( - builder: (_) => TradeRow( - onTap: () => Navigator.of(context) - .pushNamed(Routes.tradeDetails, arguments: trade), - provider: trade.provider, - from: trade.from, - to: trade.to, - createdAtFormattedDate: trade.createdAt != null - ? DateFormat('HH:mm').format(trade.createdAt!) - : null, - formattedAmount: item.tradeFormattedAmount)); - } - - if (item is OrderListItem) { - final order = item.order; - - return Observer( - builder: (_) => OrderRow( - onTap: () => Navigator.of(context) - .pushNamed(Routes.orderDetails, arguments: order), - provider: order.provider, - from: order.from!, - to: order.to!, + return Observer( + builder: (_) => TradeRow( + onTap: () => Navigator.of(context).pushNamed( + Routes.tradeDetails, + arguments: trade), + provider: trade.provider, + from: trade.from, + to: trade.to, createdAtFormattedDate: - DateFormat('HH:mm').format(order.createdAt), - formattedAmount: item.orderFormattedAmount, - )); - } + trade.createdAt != null + ? DateFormat('HH:mm') + .format(trade.createdAt!) + : null, + formattedAmount: item.tradeFormattedAmount)); + } - return Container(color: Colors.transparent, height: 1); - }) - : Center( - child: Text( - S.of(context).placeholder_transactions, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).primaryTextTheme!.labelSmall!.decorationColor!), - ), - ); - })) - ], + if (item is OrderListItem) { + final order = item.order; + + return Observer( + builder: (_) => OrderRow( + onTap: () => Navigator.of(context) + .pushNamed(Routes.orderDetails, + arguments: order), + provider: order.provider, + from: order.from!, + to: order.to!, + createdAtFormattedDate: DateFormat('HH:mm') + .format(order.createdAt), + formattedAmount: item.orderFormattedAmount, + )); + } + + return Container(color: Colors.transparent, height: 1); + }) + : Center( + child: Text( + S.of(context).placeholder_transactions, + style: TextStyle( + fontSize: 14, + color: Theme.of(context) + .primaryTextTheme + .labelSmall! + .decorationColor!), + ), + ); + })) + ], + ), ), ); } diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 9e648d094..6fffffa42 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -145,216 +145,247 @@ class SendPage extends BasePage { Widget body(BuildContext context) { _setEffects(context); - return Form( - key: _formKey, - child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(bottom: 24), - content: FocusTraversalGroup( - policy: OrderedTraversalPolicy(), - child: Column( - children: [ - Container( - height: _sendCardHeight(context), - child: Observer( - builder: (_) { - return PageView.builder( - scrollDirection: Axis.horizontal, - controller: controller, - itemCount: sendViewModel.outputs.length, - itemBuilder: (context, index) { - final output = sendViewModel.outputs[index]; - - return SendCard( - key: output.key, - output: output, - sendViewModel: sendViewModel, - initialPaymentRequest: initialPaymentRequest, - ); - }); - }, - )), - Padding( - padding: - EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10), - child: Container( - height: 10, - child: Observer( - builder: (_) { - final count = sendViewModel.outputs.length; - - return count > 1 - ? SmoothPageIndicator( - controller: controller, - count: count, - effect: ScrollingDotsEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context) - .primaryTextTheme!.displaySmall! - .backgroundColor!, - activeDotColor: Theme.of(context) - .primaryTextTheme!.displayMedium! - .backgroundColor!), - ) - : Offstage(); - }, + return GestureDetector( + onLongPress: () => sendViewModel.balanceViewModel.isReversing = + !sendViewModel.balanceViewModel.isReversing, + onLongPressUp: () => sendViewModel.balanceViewModel.isReversing = + !sendViewModel.balanceViewModel.isReversing, + child: Form( + key: _formKey, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(bottom: 24), + content: FocusTraversalGroup( + policy: OrderedTraversalPolicy(), + child: Column( + children: [ + Container( + height: _sendCardHeight(context), + child: Observer( + builder: (_) { + return PageView.builder( + scrollDirection: Axis.horizontal, + controller: controller, + itemCount: sendViewModel.outputs.length, + itemBuilder: (context, index) { + final output = sendViewModel.outputs[index]; + + return SendCard( + key: output.key, + output: output, + sendViewModel: sendViewModel, + initialPaymentRequest: initialPaymentRequest, + ); + }); + }, + )), + Padding( + padding: EdgeInsets.only( + top: 10, left: 24, right: 24, bottom: 10), + child: Container( + height: 10, + child: Observer( + builder: (_) { + final count = sendViewModel.outputs.length; + + return count > 1 + ? SmoothPageIndicator( + controller: controller, + count: count, + effect: ScrollingDotsEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context) + .primaryTextTheme + !.displaySmall! + .backgroundColor!, + activeDotColor: Theme.of(context) + .primaryTextTheme + !.displayMedium! + .backgroundColor!), + ) + : Offstage(); + }, + ), ), ), - ), - if (sendViewModel.hasMultiRecipient) - Container( - height: 40, - width: double.infinity, - padding: EdgeInsets.only(left: 24), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Observer( - builder: (_) { - final templates = sendViewModel.templates; - final itemCount = templates.length; - - return Row( - children: [ - AddTemplateButton( - onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate), - currentTemplatesLength: templates.length, - ), - ListView.builder( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: itemCount, - itemBuilder: (context, index) { - final template = templates[index]; - return TemplateTile( - key: UniqueKey(), - to: template.name, - amount: template.isCurrencySelected ? template.amount : template.amountFiat, - from: template.isCurrencySelected ? template.cryptoCurrency : template.fiatCurrency, - onTap: () async { - final fiatFromTemplate = FiatCurrency.all.singleWhere((element) => element.title == template.fiatCurrency); - final output = _defineCurrentOutput(); - output.address = template.address; - if(template.isCurrencySelected){ - output.setCryptoAmount(template.amount); - }else{ - sendViewModel.setFiatCurrency(fiatFromTemplate); - output.setFiatAmount(template.amountFiat); - } - output.resetParsedAddress(); - await output.fetchParsedAddress(context); - }, - onRemove: () { - showPopUp( - context: context, - builder: (dialogContext) { - return AlertWithTwoActions( - alertTitle: S.of(context).template, - alertContent: S - .of(context) - .confirm_delete_template, - rightButtonText: S.of(context).delete, - leftButtonText: S.of(context).cancel, - actionRightButton: () { - Navigator.of(dialogContext).pop(); - sendViewModel.sendTemplateViewModel - .removeTemplate( - template: template); - }, - actionLeftButton: () => - Navigator.of(dialogContext) - .pop()); + if (sendViewModel.hasMultiRecipient) + Container( + height: 40, + width: double.infinity, + padding: EdgeInsets.only(left: 24), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Observer( + builder: (_) { + final templates = sendViewModel.templates; + final itemCount = templates.length; + + return Row( + children: [ + AddTemplateButton( + onTap: () => Navigator.of(context) + .pushNamed(Routes.sendTemplate), + currentTemplatesLength: templates.length, + ), + ListView.builder( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: itemCount, + itemBuilder: (context, index) { + final template = templates[index]; + return TemplateTile( + key: UniqueKey(), + to: template.name, + amount: template.isCurrencySelected + ? template.amount + : template.amountFiat, + from: template.isCurrencySelected + ? template.cryptoCurrency + : template.fiatCurrency, + onTap: () async { + final fiatFromTemplate = FiatCurrency + .all + .singleWhere((element) => + element.title == + template.fiatCurrency); + final output = _defineCurrentOutput(); + output.address = template.address; + if (template.isCurrencySelected) { + output + .setCryptoAmount(template.amount); + } else { + sendViewModel.setFiatCurrency( + fiatFromTemplate); + output.setFiatAmount( + template.amountFiat); + } + output.resetParsedAddress(); + await output + .fetchParsedAddress(context); + }, + onRemove: () { + showPopUp( + context: context, + builder: (dialogContext) { + return AlertWithTwoActions( + alertTitle: + S.of(context).template, + alertContent: S + .of(context) + .confirm_delete_template, + rightButtonText: + S.of(context).delete, + leftButtonText: + S.of(context).cancel, + actionRightButton: () { + Navigator.of(dialogContext) + .pop(); + sendViewModel + .sendTemplateViewModel + .removeTemplate( + template: template); + }, + actionLeftButton: () => + Navigator.of(dialogContext) + .pop()); + }, + ); }, ); }, - ); - }, - ), - ], - ); - }, - ), - ), - ) - ], - ), - ), - bottomSectionPadding: - EdgeInsets.only(left: 24, right: 24, bottom: 24), - bottomSection: Column( - children: [ - if (sendViewModel.hasCurrecyChanger) - Observer(builder: (_) => - Padding( - padding: EdgeInsets.only(bottom: 12), - child: PrimaryButton( - onPressed: () => presentCurrencyPicker(context), - text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})', - color: Colors.transparent, - textColor: Theme.of(context) - .accentTextTheme!.displaySmall! - .decorationColor!, + ), + ], + ); + }, + ), + ), ) - ) - ), - if (sendViewModel.hasMultiRecipient) - Padding( - padding: EdgeInsets.only(bottom: 12), - child: PrimaryButton( - onPressed: () { - sendViewModel.addOutput(); - Future.delayed(const Duration(milliseconds: 250), () { - controller.jumpToPage(sendViewModel.outputs.length - 1); - }); - }, - text: S.of(context).add_receiver, - color: Colors.transparent, - textColor: Theme.of(context) - .accentTextTheme!.displaySmall! - .decorationColor!, - isDottedBorder: true, - borderColor: Theme.of(context) - .primaryTextTheme!.displaySmall! - .decorationColor!, - )), - Observer( - builder: (_) { - return LoadingPrimaryButton( - onPressed: () async { - if (_formKey.currentState != null && !_formKey.currentState!.validate()) { - if (sendViewModel.outputs.length > 1) { - showErrorValidationAlert(context); + ], + ), + ), + bottomSectionPadding: + EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSection: Column( + children: [ + if (sendViewModel.hasCurrecyChanger) + Observer( + builder: (_) => Padding( + padding: EdgeInsets.only(bottom: 12), + child: PrimaryButton( + onPressed: () => presentCurrencyPicker(context), + text: + 'Change your asset (${sendViewModel.selectedCryptoCurrency})', + color: Colors.transparent, + textColor: Theme.of(context) + .accentTextTheme + !.displaySmall! + .decorationColor!, + ))), + if (sendViewModel.hasMultiRecipient) + Padding( + padding: EdgeInsets.only(bottom: 12), + child: PrimaryButton( + onPressed: () { + sendViewModel.addOutput(); + Future.delayed(const Duration(milliseconds: 250), () { + controller + .jumpToPage(sendViewModel.outputs.length - 1); + }); + }, + text: S.of(context).add_receiver, + color: Colors.transparent, + textColor: Theme.of(context) + .accentTextTheme + !.displaySmall! + .decorationColor!, + isDottedBorder: true, + borderColor: Theme.of(context) + .primaryTextTheme + !.displaySmall! + .decorationColor!, + )), + Observer( + builder: (_) { + return LoadingPrimaryButton( + onPressed: () async { + if (_formKey.currentState != null && + !_formKey.currentState!.validate()) { + if (sendViewModel.outputs.length > 1) { + showErrorValidationAlert(context); + } + + return; } - return; - } + final notValidItems = sendViewModel.outputs + .where((item) => + item.address.isEmpty || + item.cryptoAmount.isEmpty) + .toList(); - final notValidItems = sendViewModel.outputs - .where((item) => - item.address.isEmpty || item.cryptoAmount.isEmpty) - .toList(); + if (notValidItems.isNotEmpty ?? false) { + showErrorValidationAlert(context); + return; + } - if (notValidItems.isNotEmpty ?? false) { - showErrorValidationAlert(context); - return; - } - - await sendViewModel.createTransaction(); - - }, - text: S.of(context).send, - color: Theme.of(context).accentTextTheme!.bodyLarge!.color!, - textColor: Colors.white, - isLoading: sendViewModel.state is IsExecutingState || - sendViewModel.state is TransactionCommitting, - isDisabled: !sendViewModel.isReadyForSend, - ); - }, - ) - ], - )), + await sendViewModel.createTransaction(); + }, + text: S.of(context).send, + color: + Theme.of(context).accentTextTheme!.bodyLarge!.color!, + textColor: Colors.white, + isLoading: sendViewModel.state is IsExecutingState || + sendViewModel.state is TransactionCommitting, + isDisabled: !sendViewModel.isReadyForSend, + ); + }, + ) + ], + )), + ), ); } @@ -382,51 +413,54 @@ class SendPage extends BasePage { WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { showPopUp( - context: context, - builder: (BuildContext context) { - return ConfirmSendingAlert( - alertTitle: S.of(context).confirm_sending, - amount: S.of(context).send_amount, - amountValue: - sendViewModel.pendingTransaction!.amountFormatted, - fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted, - fee: S.of(context).send_fee, - feeValue: sendViewModel.pendingTransaction!.feeFormatted, - feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted, - outputs: sendViewModel.outputs, - rightButtonText: S.of(context).ok, - leftButtonText: S.of(context).cancel, - actionRightButton: () { - Navigator.of(context).pop(); - sendViewModel.commitTransaction(); - showPopUp( - context: context, - builder: (BuildContext context) { - return Observer(builder: (_) { - final state = sendViewModel.state; + context: context, + builder: (BuildContext context) { + return ConfirmSendingAlert( + alertTitle: S.of(context).confirm_sending, + amount: S.of(context).send_amount, + amountValue: + sendViewModel.pendingTransaction!.amountFormatted, + fiatAmountValue: + sendViewModel.pendingTransactionFiatAmountFormatted, + fee: S.of(context).send_fee, + feeValue: sendViewModel.pendingTransaction!.feeFormatted, + feeFiatAmount: sendViewModel + .pendingTransactionFeeFiatAmountFormatted, + outputs: sendViewModel.outputs, + rightButtonText: S.of(context).ok, + leftButtonText: S.of(context).cancel, + actionRightButton: () { + Navigator.of(context).pop(); + sendViewModel.commitTransaction(); + showPopUp( + context: context, + builder: (BuildContext context) { + return Observer(builder: (_) { + final state = sendViewModel.state; - if (state is FailureState) { - Navigator.of(context).pop(); - } - - if (state is TransactionCommitted) { - return AlertWithOneAction( - alertTitle: '', - alertContent: S.of(context).send_success( - sendViewModel.selectedCryptoCurrency.toString()), - buttonText: S.of(context).ok, - buttonAction: () { - Navigator.of(context).pop(); - RequestReviewHandler.requestReview(); - }); + if (state is FailureState) { + Navigator.of(context).pop(); } - return Offstage(); + if (state is TransactionCommitted) { + return AlertWithOneAction( + alertTitle: '', + alertContent: S.of(context).send_success( + sendViewModel.selectedCryptoCurrency + .toString()), + buttonText: S.of(context).ok, + buttonAction: () { + Navigator.of(context).pop(); + RequestReviewHandler.requestReview(); + }); + } + + return Offstage(); + }); }); - }); - }, - actionLeftButton: () => Navigator.of(context).pop()); - }); + }, + actionLeftButton: () => Navigator.of(context).pop()); + }); } }); } @@ -461,16 +495,18 @@ class SendPage extends BasePage { }); } - void presentCurrencyPicker(BuildContext context) async { + void presentCurrencyPicker(BuildContext context) async { await showPopUp( builder: (_) => Picker( - items: sendViewModel.currencies, - displayItem: (Object item) => item.toString(), - selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), - title: S.of(context).please_select, - mainAxisAlignment: MainAxisAlignment.center, - onItemSelected: (CryptoCurrency cur) => sendViewModel.selectedCryptoCurrency = cur, - ), + items: sendViewModel.currencies, + displayItem: (Object item) => item.toString(), + selectedAtIndex: sendViewModel.currencies + .indexOf(sendViewModel.selectedCryptoCurrency), + title: S.of(context).please_select, + mainAxisAlignment: MainAxisAlignment.center, + onItemSelected: (CryptoCurrency cur) => + sendViewModel.selectedCryptoCurrency = cur, + ), context: context); } } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 0f16bdfe8..c8c6f5175 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -13,7 +13,6 @@ import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cw_core/keyable.dart'; import 'package:cw_core/wallet_type.dart'; - class TransactionListItem extends ActionListItem with Keyable { TransactionListItem( {required this.transaction, @@ -28,7 +27,7 @@ class TransactionListItem extends ActionListItem with Keyable { FiatCurrency get fiatCurrency => settingsStore.fiatCurrency; - BalanceDisplayMode get displayMode => settingsStore.balanceDisplayMode; + BalanceDisplayMode get displayMode => balanceViewModel.displayMode; @override dynamic get keyIndex => transaction.id; From e84d02f66106ce055bb92f8f57bbeace31fc0b87 Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Fri, 9 Jun 2023 19:31:05 -0300 Subject: [PATCH 3/3] CW-383-replace-swipe-to-delete-wallet-action-with-a-delete-trash-icon-on-the-right (#935) * feat: replace "swipe to delete" wallet action with a delete / trash icon on the right * fix: remove InkWell and _actionPane actions * feat: add a bit of padding from the end --- .../screens/wallet_list/wallet_list_page.dart | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index b20b2bd1a..3f33ebc7a 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -149,12 +149,26 @@ class WalletListBodyState extends State { return wallet.isCurrent ? row - : Slidable( - key: Key('${wallet.key}'), - startActionPane: _actionPane(wallet), - endActionPane: _actionPane(wallet), - child: row, - ); + : Row(children: [ + Expanded(child: row), + GestureDetector( + onTap: () => _removeWallet(wallet), + child: Container( + height: 40, + width: 44, + padding: EdgeInsets.only(right: 20), + child: Center( + child: Image.asset('assets/images/trash.png', + height: 16, + width: 16, + color: Theme.of(context) + .primaryTextTheme + .titleLarge! + .color), + ), + ), + ) + ]); }), ), ), @@ -277,18 +291,4 @@ class WalletListBodyState extends State { _progressBar = null; }); } - - ActionPane _actionPane(WalletListItem wallet) => ActionPane( - motion: const ScrollMotion(), - extentRatio: 0.3, - children: [ - SlidableAction( - onPressed: (_) => _removeWallet(wallet), - backgroundColor: Colors.red, - foregroundColor: Colors.white, - icon: CupertinoIcons.delete, - label: S.of(context).delete, - ), - ], - ); }