mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +00:00
update the flow
This commit is contained in:
parent
e745600b30
commit
534bed355c
13 changed files with 309 additions and 194 deletions
|
@ -117,10 +117,10 @@ packages:
|
|||
dependency: "direct overridden"
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
|
||||
sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.7"
|
||||
version: "7.2.7+1"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:cake_wallet/buy/buy_provider.dart';
|
|||
import 'package:cake_wallet/buy/payment_method.dart';
|
||||
import 'package:cake_wallet/core/selectable_option.dart';
|
||||
import 'package:cake_wallet/entities/provider_types.dart';
|
||||
import 'package:cake_wallet/exchange/limits.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
|
||||
enum ProviderRecommendation { bestRate, lowKyc, successRate }
|
||||
|
@ -47,6 +48,7 @@ class Quote extends SelectableOption {
|
|||
this.rampId,
|
||||
this.rampName,
|
||||
this.rampIconPath,
|
||||
this.limits,
|
||||
}) : super(title: provider.isAggregator ? rampName ?? '' : provider.title);
|
||||
|
||||
final double rate;
|
||||
|
@ -64,6 +66,7 @@ class Quote extends SelectableOption {
|
|||
bool isSelected = false;
|
||||
bool isBestRate = false;
|
||||
bool isBuyAction;
|
||||
Limits? limits;
|
||||
|
||||
late Currency sourceCurrency;
|
||||
late Currency destinationCurrency;
|
||||
|
@ -83,18 +86,32 @@ class Quote extends SelectableOption {
|
|||
List<String> get badges => recommendations.map((e) => e.title).toList();
|
||||
|
||||
@override
|
||||
String get leftSubTitle => this.rate > 0
|
||||
? '1 ${isBuyAction ? destinationCurrency : sourceCurrency} = ${rate.toStringAsFixed(2)} ${isBuyAction ? sourceCurrency : destinationCurrency}'
|
||||
: '';
|
||||
String get topLeftSubTitle =>
|
||||
this.rate > 0 ? '1 $fiatName = ${rate.toStringAsFixed(2)} $cryptoName' : '';
|
||||
|
||||
@override
|
||||
String? get rightSubTitle => '';
|
||||
String get bottomLeftSubTitle {
|
||||
if (limits != null) {
|
||||
final min = limits!.min;
|
||||
final max = limits!.max;
|
||||
return 'min: ${min} ${sourceCurrency.toString()} | max: ${max == double.infinity ? '' : '${max} ${sourceCurrency.toString()}'}';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
String get cryptoName => isBuyAction ? sourceCurrency.toString() : destinationCurrency.toString();
|
||||
|
||||
String get fiatName => isBuyAction ? destinationCurrency.toString() : sourceCurrency.toString();
|
||||
|
||||
@override
|
||||
String get rightSubTitleLightIconPath => provider.isAggregator ? provider.lightIcon : '';
|
||||
String? get topRightSubTitle => '';
|
||||
|
||||
@override
|
||||
String get rightSubTitleDarkIconPath => provider.isAggregator ? provider.darkIcon : '';
|
||||
String get topRightSubTitleLightIconPath => provider.isAggregator ? provider.lightIcon : '';
|
||||
|
||||
@override
|
||||
String get topRightSubTitleDarkIconPath => provider.isAggregator ? provider.darkIcon : '';
|
||||
|
||||
String get quoteTitle => '${provider.title} - ${paymentType.name}';
|
||||
|
||||
|
@ -109,8 +126,10 @@ class Quote extends SelectableOption {
|
|||
void set setDestinationCurrency(Currency destinationCurrency) =>
|
||||
this.destinationCurrency = destinationCurrency;
|
||||
|
||||
factory Quote.fromOnramperJson(Map<String, dynamic> json,
|
||||
bool isBuyAction, Map<String, dynamic> metaData, PaymentType paymentType) {
|
||||
void set setLimits(Limits limits) => this.limits = limits;
|
||||
|
||||
factory Quote.fromOnramperJson(Map<String, dynamic> json, bool isBuyAction,
|
||||
Map<String, dynamic> metaData, PaymentType paymentType) {
|
||||
final rate = _toDouble(json['rate']) ?? 0.0;
|
||||
final networkFee = _toDouble(json['networkFee']) ?? 0.0;
|
||||
final transactionFee = _toDouble(json['transactionFee']) ?? 0.0;
|
||||
|
@ -129,6 +148,29 @@ class Quote extends SelectableOption {
|
|||
.whereType<ProviderRecommendation>()
|
||||
.toList();
|
||||
|
||||
final availablePaymentMethods = json['availablePaymentMethods'] as List<dynamic>? ?? [];
|
||||
double minLimit = 0.0;
|
||||
double maxLimit = double.infinity;
|
||||
|
||||
for (var paymentMethod in availablePaymentMethods) {
|
||||
if (paymentMethod is Map<String, dynamic>) {
|
||||
final details = paymentMethod['details'] as Map<String, dynamic>?;
|
||||
|
||||
if (details != null) {
|
||||
final limits = details['limits'] as Map<String, dynamic>?;
|
||||
|
||||
if (limits != null && limits.isNotEmpty) {
|
||||
final firstLimitEntry = limits.values.first as Map<String, dynamic>?;
|
||||
if (firstLimitEntry != null) {
|
||||
minLimit = _toDouble(firstLimitEntry['min'])?.roundToDouble() ?? 0.0;
|
||||
maxLimit = _toDouble(firstLimitEntry['max'])?.roundToDouble() ?? double.infinity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Quote(
|
||||
rate: rate,
|
||||
feeAmount: feeAmount,
|
||||
|
@ -143,6 +185,7 @@ class Quote extends SelectableOption {
|
|||
recommendations: enumRecommendations,
|
||||
provider: ProvidersHelper.getProviderByType(ProviderType.onramper)!,
|
||||
isBuyAction: isBuyAction,
|
||||
limits: Limits(min: minLimit, max: maxLimit),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -155,6 +198,17 @@ class Quote extends SelectableOption {
|
|||
final networkFee = _toDouble(json['networkFeeAmount']) ?? 0.0;
|
||||
final transactionFee = _toDouble(json['extraFeeAmount']) ?? 0.0;
|
||||
final feeAmount = double.parse((fee + networkFee + transactionFee).toStringAsFixed(2));
|
||||
|
||||
final baseCurrency = json['baseCurrency'] as Map<String, dynamic>?;
|
||||
|
||||
double minLimit = 0.0;
|
||||
double maxLimit = double.infinity;
|
||||
|
||||
if (baseCurrency != null) {
|
||||
minLimit = _toDouble(baseCurrency['minAmount']) ?? minLimit;
|
||||
maxLimit = _toDouble(baseCurrency['maxAmount']) ?? maxLimit;
|
||||
}
|
||||
|
||||
return Quote(
|
||||
rate: rate,
|
||||
feeAmount: feeAmount,
|
||||
|
@ -166,23 +220,32 @@ class Quote extends SelectableOption {
|
|||
quoteId: json['signature'] as String? ?? '',
|
||||
provider: ProvidersHelper.getProviderByType(ProviderType.moonpay)!,
|
||||
isBuyAction: isBuyAction,
|
||||
limits: Limits(min: minLimit, max: maxLimit),
|
||||
);
|
||||
}
|
||||
|
||||
factory Quote.fromDFXJson(
|
||||
Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) {
|
||||
Map<String, dynamic> json,
|
||||
bool isBuyAction,
|
||||
PaymentType paymentType,
|
||||
) {
|
||||
final rate = _toDouble(json['exchangeRate']) ?? 0.0;
|
||||
final fees = json['fees'] as Map<String, dynamic>;
|
||||
|
||||
final minVolume = _toDouble(json['minVolume']) ?? 0.0;
|
||||
final maxVolume = _toDouble(json['maxVolume']) ?? double.infinity;
|
||||
|
||||
return Quote(
|
||||
rate: isBuyAction ? rate : 1 / rate,
|
||||
feeAmount: _toDouble(json['feeAmount']) ?? 0.0,
|
||||
networkFee: _toDouble(fees['networkFee']) ?? 0.0,
|
||||
networkFee: _toDouble(fees['network']) ?? 0.0,
|
||||
transactionFee: _toDouble(fees['rate']) ?? 0.0,
|
||||
payout: _toDouble(json['payout']) ?? 0.0,
|
||||
paymentType: paymentType,
|
||||
recommendations: [ProviderRecommendation.lowKyc],
|
||||
provider: ProvidersHelper.getProviderByType(ProviderType.dfx)!,
|
||||
isBuyAction: isBuyAction,
|
||||
limits: Limits(min: minVolume, max: maxVolume),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -204,11 +267,11 @@ class Quote extends SelectableOption {
|
|||
recommendations: [],
|
||||
provider: ProvidersHelper.getProviderByType(ProviderType.robinhood)!,
|
||||
isBuyAction: isBuyAction,
|
||||
limits: Limits(min: 0.0, max: double.infinity),
|
||||
);
|
||||
}
|
||||
|
||||
factory Quote.fromMeldJson(
|
||||
Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) {
|
||||
factory Quote.fromMeldJson(Map<String, dynamic> json, bool isBuyAction, PaymentType paymentType) {
|
||||
final quotes = json['quotes'][0] as Map<String, dynamic>;
|
||||
return Quote(
|
||||
rate: quotes['exchangeRate'] as double? ?? 0.0,
|
||||
|
@ -220,6 +283,7 @@ class Quote extends SelectableOption {
|
|||
recommendations: [],
|
||||
provider: ProvidersHelper.getProviderByType(ProviderType.meld)!,
|
||||
isBuyAction: isBuyAction,
|
||||
limits: Limits(min: 0.0, max: double.infinity),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -264,8 +264,8 @@ class DFXBuyProvider extends BuyProvider {
|
|||
if (responseData is Map<String, dynamic>) {
|
||||
final paymentType = _getPaymentTypeByString(responseData['paymentMethod'] as String?);
|
||||
final quote = Quote.fromDFXJson(responseData, isBuyAction, paymentType);
|
||||
quote.setSourceCurrency = isBuyAction ? cryptoCurrency : fiatCurrency;
|
||||
quote.setDestinationCurrency = isBuyAction ? fiatCurrency : cryptoCurrency;
|
||||
quote.setSourceCurrency = isBuyAction ? fiatCurrency : cryptoCurrency;
|
||||
quote.setDestinationCurrency = isBuyAction ? cryptoCurrency : fiatCurrency;
|
||||
return [quote];
|
||||
} else {
|
||||
print('DFX: Unexpected data type: ${responseData.runtimeType}');
|
||||
|
|
|
@ -199,8 +199,8 @@ class MoonPayProvider extends BuyProvider {
|
|||
final quote =
|
||||
Quote.fromMoonPayJson(data, isBuyAction, _getPaymentTypeByString(paymentMethods));
|
||||
|
||||
quote.setSourceCurrency = isBuyAction ? cryptoCurrency : fiatCurrency;
|
||||
quote.setDestinationCurrency = isBuyAction ? fiatCurrency : cryptoCurrency;
|
||||
quote.setSourceCurrency = isBuyAction ? fiatCurrency : cryptoCurrency;
|
||||
quote.setDestinationCurrency = isBuyAction ? cryptoCurrency : fiatCurrency;
|
||||
|
||||
return [quote];
|
||||
} else {
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
|||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/buy_quote.dart';
|
||||
import 'package:cake_wallet/buy/payment_method.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
|
||||
|
@ -18,8 +19,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:http/http.dart' as http;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../entities/fiat_currency.dart';
|
||||
|
||||
class RobinhoodBuyProvider extends BuyProvider {
|
||||
RobinhoodBuyProvider(
|
||||
{required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
|
||||
|
@ -170,8 +169,8 @@ class RobinhoodBuyProvider extends BuyProvider {
|
|||
if (response.statusCode == 200) {
|
||||
final paymentType = _getPaymentTypeByString(responseData['paymentMethod'] as String?);
|
||||
final quote = Quote.fromRobinhoodJson(responseData, isBuyAction, paymentType);
|
||||
quote.setSourceCurrency = isBuyAction ? cryptoCurrency : fiatCurrency;
|
||||
quote.setDestinationCurrency = isBuyAction ? fiatCurrency : cryptoCurrency;
|
||||
quote.setSourceCurrency = isBuyAction ? fiatCurrency : cryptoCurrency;
|
||||
quote.setDestinationCurrency = isBuyAction ? cryptoCurrency : fiatCurrency;
|
||||
return [quote];
|
||||
} else {
|
||||
if (responseData.containsKey('message')) {
|
||||
|
|
|
@ -17,15 +17,25 @@ abstract class SelectableOption extends SelectableItem {
|
|||
|
||||
String? get description => null;
|
||||
|
||||
String? get leftSubTitle => null;
|
||||
String? get topLeftSubTitle => null;
|
||||
|
||||
String? get leftSubTitleIconPath => null;
|
||||
String? get topLeftSubTitleIconPath => null;
|
||||
|
||||
String? get rightSubTitle => null;
|
||||
String? get topRightSubTitle => null;
|
||||
|
||||
String? get rightSubTitleLightIconPath => null;
|
||||
String? get topRightSubTitleLightIconPath => null;
|
||||
|
||||
String? get rightSubTitleDarkIconPath => null;
|
||||
String? get topRightSubTitleDarkIconPath => null;
|
||||
|
||||
String? get bottomLeftSubTitle => null;
|
||||
|
||||
String? get bottomLeftSubTitleIconPath => null;
|
||||
|
||||
String? get bottomRightSubTitle => null;
|
||||
|
||||
String? get bottomRightSubTitleLightIconPath => null;
|
||||
|
||||
String? get bottomRightSubTitleDarkIconPath => null;
|
||||
|
||||
List<String> get badges => [];
|
||||
|
||||
|
|
|
@ -1190,9 +1190,9 @@ Future<void> setup({
|
|||
getIt.registerFactoryParam<BuyOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final items = args.first as List<SelectableItem>;
|
||||
final pickAnOption = args[1] as void Function(SelectableOption option)?;
|
||||
|
||||
final confirmOption = args[2] as void Function(BuildContext contex)?;
|
||||
return BuyOptionsPage(
|
||||
items: items, pickAnOption: pickAnOption);
|
||||
items: items, pickAnOption: pickAnOption, confirmOption: confirmOption);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<PaymentMethodOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
|
|
|
@ -4,10 +4,11 @@ import 'package:cake_wallet/src/screens/select_options_page.dart';
|
|||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class BuyOptionsPage extends SelectOptionsPage {
|
||||
BuyOptionsPage({required this.items, this.pickAnOption});
|
||||
BuyOptionsPage({required this.items, this.pickAnOption, this.confirmOption});
|
||||
|
||||
final List<SelectableItem> items;
|
||||
final Function(SelectableOption option)? pickAnOption;
|
||||
final Function(BuildContext context)? confirmOption;
|
||||
|
||||
@override
|
||||
String get pageTitle => S.current.choose_a_provider;
|
||||
|
@ -27,9 +28,6 @@ class BuyOptionsPage extends SelectOptionsPage {
|
|||
@override
|
||||
double? get imageWidth => 40;
|
||||
|
||||
@override
|
||||
TextStyle? get subTitleTextStyle => null;
|
||||
|
||||
@override
|
||||
Color? get selectedBackgroundColor => null;
|
||||
|
||||
|
@ -41,4 +39,10 @@ class BuyOptionsPage extends SelectOptionsPage {
|
|||
|
||||
@override
|
||||
void Function(SelectableOption option)? get onOptionTap => pickAnOption;
|
||||
|
||||
@override
|
||||
String get primaryButtonText => S.current.confirm;
|
||||
|
||||
@override
|
||||
void Function(BuildContext context)? get primaryButtonAction => confirmOption;
|
||||
}
|
||||
|
|
|
@ -39,8 +39,6 @@ class BuySellPage extends BasePage {
|
|||
final _depositAddressFocus = FocusNode();
|
||||
final _cryptoAmountFocus = FocusNode();
|
||||
final _receiveAddressFocus = FocusNode();
|
||||
final _cryptoAmountDebounce = Debounce(Duration(milliseconds: 500));
|
||||
final _fiatAmountDebounce = Debounce(Duration(milliseconds: 500));
|
||||
var _isReactionsSet = false;
|
||||
|
||||
final arrowBottomPurple = Image.asset(
|
||||
|
@ -155,8 +153,6 @@ class BuySellPage extends BasePage {
|
|||
children: [
|
||||
SizedBox(height: 12),
|
||||
_buildPaymentMethodTile(context),
|
||||
SizedBox(height: 12),
|
||||
_buildQuoteTile(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -165,15 +161,15 @@ class BuySellPage extends BasePage {
|
|||
bottomSection: Column(children: [
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
text: 'Next',
|
||||
text: S.current.choose_a_provider,
|
||||
onPressed: () async {
|
||||
if(!_formKey.currentState!.validate()) return;
|
||||
await buySellViewModel.launchTrade(context);
|
||||
buySellViewModel.onTapChoseProvider(context);
|
||||
},
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isDisabled: !buySellViewModel.isReadyToTrade,
|
||||
isLoading: false)),
|
||||
isDisabled: false,
|
||||
isLoading: !buySellViewModel.isReadyToTrade)),
|
||||
]),
|
||||
)),
|
||||
));
|
||||
|
@ -214,43 +210,6 @@ class BuySellPage extends BasePage {
|
|||
return OptionTilePlaceholder(errorText: 'No payment methods available', borderRadius: 30);
|
||||
}
|
||||
|
||||
Widget _buildQuoteTile(BuildContext context) {
|
||||
if (buySellViewModel.buySellQuotState is BuySellQuotLoading ||
|
||||
buySellViewModel.buySellQuotState is InitialBuySellQuotState) {
|
||||
return OptionTilePlaceholder(
|
||||
leadingIcon: Icons.arrow_forward_ios,
|
||||
borderRadius: 30,
|
||||
imageWidth: 50,
|
||||
imageHeight: 50,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
isDarkTheme: buySellViewModel.isDarkTheme);
|
||||
}
|
||||
if (buySellViewModel.buySellQuotState is BuySellQuotLoaded &&
|
||||
buySellViewModel.selectedQuote != null) {
|
||||
return Observer(builder: (_) {
|
||||
final selectedQuote = buySellViewModel.selectedQuote!;
|
||||
return ProviderOptionTile(
|
||||
lightImagePath: selectedQuote.lightIconPath,
|
||||
darkImagePath: selectedQuote.darkIconPath,
|
||||
title: selectedQuote.title,
|
||||
badges: selectedQuote.badges,
|
||||
imageWidth: 50,
|
||||
imageHeight: 50,
|
||||
leftSubTitle: selectedQuote.leftSubTitle,
|
||||
rightSubTitleLightIconPath: selectedQuote.rightSubTitleLightIconPath,
|
||||
rightSubTitleDarkIconPath: selectedQuote.rightSubTitleDarkIconPath,
|
||||
onPressed: () => _pickQuote(context),
|
||||
leadingIcon: Icons.arrow_forward_ios,
|
||||
isLightMode: !buySellViewModel.isDarkTheme,
|
||||
borderRadius: 30,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
titleTextStyle:
|
||||
textLargeBold(color: Theme.of(context).extension<CakeTextTheme>()!.titleColor));
|
||||
});
|
||||
}
|
||||
return OptionTilePlaceholder(errorText: 'No quotes available', borderRadius: 30);
|
||||
}
|
||||
|
||||
void _pickPaymentMethod(BuildContext context) async {
|
||||
final currentOption = buySellViewModel.selectedPaymentMethod;
|
||||
await Navigator.of(context).pushNamed(
|
||||
|
@ -269,16 +228,6 @@ class BuySellPage extends BasePage {
|
|||
}
|
||||
}
|
||||
|
||||
void _pickQuote(BuildContext context) async {
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.buyOptionsPage,
|
||||
arguments: [
|
||||
buySellViewModel.quoteOptions,
|
||||
buySellViewModel.changeOption
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _setReactions(BuildContext context, BuySellViewModel buySellViewModel) {
|
||||
if (_isReactionsSet) {
|
||||
return;
|
||||
|
@ -315,17 +264,13 @@ class BuySellPage extends BasePage {
|
|||
|
||||
fiatAmountController.addListener(() {
|
||||
if (fiatAmountController.text != buySellViewModel.fiatAmount) {
|
||||
_fiatAmountDebounce.run(() {
|
||||
buySellViewModel.changeFiatAmount(amount: fiatAmountController.text);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
cryptoAmountController.addListener(() {
|
||||
if (cryptoAmountController.text != buySellViewModel.cryptoAmount) {
|
||||
_cryptoAmountDebounce.run(() {
|
||||
buySellViewModel.changeCryptoAmount(amount: cryptoAmountController.text);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -27,9 +27,6 @@ class PaymentMethodOptionsPage extends SelectOptionsPage {
|
|||
@override
|
||||
double? get imageWidth => null;
|
||||
|
||||
@override
|
||||
TextStyle? get subTitleTextStyle => null;
|
||||
|
||||
@override
|
||||
Color? get selectedBackgroundColor => null;
|
||||
|
||||
|
@ -41,4 +38,10 @@ class PaymentMethodOptionsPage extends SelectOptionsPage {
|
|||
|
||||
@override
|
||||
void Function(SelectableOption option)? get onOptionTap => pickAnOption;
|
||||
|
||||
@override
|
||||
String get primaryButtonText => S.current.confirm;
|
||||
|
||||
@override
|
||||
void Function(BuildContext context)? get primaryButtonAction => null;
|
||||
}
|
||||
|
|
|
@ -22,20 +22,22 @@ abstract class SelectOptionsPage extends BasePage {
|
|||
|
||||
double? get imageWidth;
|
||||
|
||||
TextStyle? get subTitleTextStyle;
|
||||
|
||||
Color? get selectedBackgroundColor;
|
||||
|
||||
double? get tileBorderRadius;
|
||||
|
||||
String get bottomSectionText;
|
||||
|
||||
bool get confirmButtonEnabled => true;
|
||||
bool get primaryButtonEnabled => true;
|
||||
|
||||
String get primaryButtonText => '';
|
||||
|
||||
List<SelectableItem> get items;
|
||||
|
||||
void Function(SelectableOption option)? get onOptionTap;
|
||||
|
||||
void Function(BuildContext context)? get primaryButtonAction;
|
||||
|
||||
@override
|
||||
String get title => pageTitle;
|
||||
|
||||
|
@ -44,11 +46,9 @@ abstract class SelectOptionsPage extends BasePage {
|
|||
return ScrollableWithBottomSection(
|
||||
content: BodySelectOptionsPage(
|
||||
items: items,
|
||||
// Updated to pass items list
|
||||
onOptionTap: onOptionTap,
|
||||
tilePadding: tilePadding,
|
||||
tileBorderRadius: tileBorderRadius,
|
||||
subTitleTextStyle: subTitleTextStyle,
|
||||
imageHeight: imageHeight,
|
||||
imageWidth: imageWidth,
|
||||
innerPadding: innerPadding),
|
||||
|
@ -65,10 +65,14 @@ abstract class SelectOptionsPage extends BasePage {
|
|||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
),
|
||||
if (confirmButtonEnabled)
|
||||
if (primaryButtonEnabled)
|
||||
LoadingPrimaryButton(
|
||||
text: 'Confirm',
|
||||
onPressed: () => Navigator.pop(context),
|
||||
text: primaryButtonText,
|
||||
onPressed: () {
|
||||
primaryButtonAction != null
|
||||
? primaryButtonAction!(context)
|
||||
: Navigator.pop(context);
|
||||
},
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isDisabled: false,
|
||||
|
@ -86,7 +90,6 @@ class BodySelectOptionsPage extends StatefulWidget {
|
|||
this.onOptionTap,
|
||||
this.tilePadding,
|
||||
this.tileBorderRadius,
|
||||
this.subTitleTextStyle,
|
||||
this.imageHeight,
|
||||
this.imageWidth,
|
||||
this.innerPadding,
|
||||
|
@ -96,7 +99,6 @@ class BodySelectOptionsPage extends StatefulWidget {
|
|||
final void Function(SelectableOption option)? onOptionTap;
|
||||
final EdgeInsets? tilePadding;
|
||||
final double? tileBorderRadius;
|
||||
final TextStyle? subTitleTextStyle;
|
||||
final double? imageHeight;
|
||||
final double? imageWidth;
|
||||
final EdgeInsets? innerPadding;
|
||||
|
@ -175,20 +177,20 @@ class _BodySelectOptionsPageState extends State<BodySelectOptionsPage> {
|
|||
imageWidth: widget.imageWidth,
|
||||
padding: widget.innerPadding,
|
||||
description: item.description,
|
||||
leftSubTitle: item.leftSubTitle,
|
||||
rightSubTitle: item.rightSubTitle,
|
||||
rightSubTitleLightIconPath: item.rightSubTitleLightIconPath,
|
||||
rightSubTitleDarkIconPath: item.rightSubTitleDarkIconPath,
|
||||
topLeftSubTitle: item.topLeftSubTitle,
|
||||
topRightSubTitle: item.topRightSubTitle,
|
||||
rightSubTitleLightIconPath: item.topRightSubTitleLightIconPath,
|
||||
rightSubTitleDarkIconPath: item.topRightSubTitleDarkIconPath,
|
||||
bottomLeftSubTitle: item.bottomLeftSubTitle,
|
||||
badges: item.badges,
|
||||
isSelected: item.isOptionSelected,
|
||||
subTitleTextStyle: widget.subTitleTextStyle,
|
||||
borderRadius: widget.tileBorderRadius,
|
||||
isLightMode: isLightMode,
|
||||
onPressed: () => _handleOptionTap(item),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink(); // Fallback for any unsupported items
|
||||
return const SizedBox.shrink();
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -10,8 +10,10 @@ class ProviderOptionTile extends StatelessWidget {
|
|||
required this.lightImagePath,
|
||||
required this.darkImagePath,
|
||||
required this.title,
|
||||
this.leftSubTitle,
|
||||
this.rightSubTitle,
|
||||
this.topLeftSubTitle,
|
||||
this.topRightSubTitle,
|
||||
this.bottomLeftSubTitle,
|
||||
this.bottomRightSubTitle,
|
||||
this.leftSubTitleIconPath,
|
||||
this.rightSubTitleLightIconPath,
|
||||
this.rightSubTitleDarkIconPath,
|
||||
|
@ -22,7 +24,8 @@ class ProviderOptionTile extends StatelessWidget {
|
|||
this.imageWidth,
|
||||
this.padding,
|
||||
this.titleTextStyle,
|
||||
this.subTitleTextStyle,
|
||||
this.firstSubTitleTextStyle,
|
||||
this.secondSubTitleTextStyle,
|
||||
this.leadingIcon,
|
||||
this.selectedBackgroundColor,
|
||||
this.isSelected = false,
|
||||
|
@ -33,8 +36,10 @@ class ProviderOptionTile extends StatelessWidget {
|
|||
final String lightImagePath;
|
||||
final String darkImagePath;
|
||||
final String title;
|
||||
final String? leftSubTitle;
|
||||
final String? rightSubTitle;
|
||||
final String? topLeftSubTitle;
|
||||
final String? topRightSubTitle;
|
||||
final String? bottomLeftSubTitle;
|
||||
final String? bottomRightSubTitle;
|
||||
final String? leftSubTitleIconPath;
|
||||
final String? rightSubTitleLightIconPath;
|
||||
final String? rightSubTitleDarkIconPath;
|
||||
|
@ -45,7 +50,8 @@ class ProviderOptionTile extends StatelessWidget {
|
|||
final double? imageWidth;
|
||||
final EdgeInsets? padding;
|
||||
final TextStyle? titleTextStyle;
|
||||
final TextStyle? subTitleTextStyle;
|
||||
final TextStyle? firstSubTitleTextStyle;
|
||||
final TextStyle? secondSubTitleTextStyle;
|
||||
final IconData? leadingIcon;
|
||||
final Color? selectedBackgroundColor;
|
||||
final bool isSelected;
|
||||
|
@ -73,7 +79,6 @@ class ProviderOptionTile extends StatelessWidget {
|
|||
? Theme.of(context).extension<OptionTileTheme>()!.titleColor
|
||||
: Theme.of(context).cardColor;
|
||||
|
||||
|
||||
final imagePath = isSelected
|
||||
? isLightMode
|
||||
? darkImagePath
|
||||
|
@ -128,54 +133,18 @@ class ProviderOptionTile extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (leftSubTitle != null || rightSubTitle != null)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
leftSubTitle != null || leftSubTitleIconPath != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
if (leftSubTitleIconPath != null &&
|
||||
leftSubTitleIconPath!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: getImage(leftSubTitleIconPath!),
|
||||
),
|
||||
Text(
|
||||
leftSubTitle ?? '',
|
||||
style: subTitleTextStyle ??
|
||||
TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: textColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
rightSubTitle != null || rightSubTitleIconPath != null
|
||||
? Row(
|
||||
children: [
|
||||
if (rightSubTitleIconPath != null && rightSubTitleIconPath.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: getImage(rightSubTitleIconPath, imageColor: textColor),
|
||||
),
|
||||
Text(
|
||||
rightSubTitle ?? '',
|
||||
style: subTitleTextStyle ??
|
||||
TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: textColor),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Offstage(),
|
||||
],
|
||||
),
|
||||
if (topLeftSubTitle != null || topRightSubTitle != null)
|
||||
subTitleWidget(
|
||||
leftSubTitle: topLeftSubTitle,
|
||||
subTitleIconPath: leftSubTitleIconPath,
|
||||
textColor: textColor,
|
||||
rightSubTitle: topRightSubTitle,
|
||||
rightSubTitleIconPath: rightSubTitleIconPath),
|
||||
if (bottomLeftSubTitle != null || bottomRightSubTitle != null)
|
||||
subTitleWidget(
|
||||
leftSubTitle: bottomLeftSubTitle,
|
||||
textColor: textColor,
|
||||
subTitleFontSize: 12),
|
||||
if (badges != null && badges!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
|
@ -194,6 +163,68 @@ class ProviderOptionTile extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class subTitleWidget extends StatelessWidget {
|
||||
const subTitleWidget({
|
||||
super.key,
|
||||
this.leftSubTitle,
|
||||
this.subTitleIconPath,
|
||||
required this.textColor,
|
||||
this.rightSubTitle,
|
||||
this.rightSubTitleIconPath,
|
||||
this.subTitleFontSize = 16,
|
||||
});
|
||||
|
||||
final String? leftSubTitle;
|
||||
final String? subTitleIconPath;
|
||||
final Color textColor;
|
||||
final String? rightSubTitle;
|
||||
final String? rightSubTitleIconPath;
|
||||
final double subTitleFontSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
leftSubTitle != null || subTitleIconPath != null
|
||||
? Row(
|
||||
children: [
|
||||
if (subTitleIconPath != null && subTitleIconPath!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: getImage(subTitleIconPath!),
|
||||
),
|
||||
Text(
|
||||
leftSubTitle ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: subTitleFontSize,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: textColor),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Offstage(),
|
||||
rightSubTitle != null || rightSubTitleIconPath != null
|
||||
? Row(
|
||||
children: [
|
||||
if (rightSubTitleIconPath != null && rightSubTitleIconPath!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: getImage(rightSubTitleIconPath!, imageColor: textColor),
|
||||
),
|
||||
Text(
|
||||
rightSubTitle ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: subTitleFontSize, fontWeight: FontWeight.w700, color: textColor),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Offstage(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Badge extends StatelessWidget {
|
||||
Badge({required this.textColor, required this.backgroundColor, required this.title});
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
|||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/provider_types.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
|
@ -53,12 +54,6 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
|||
_initialize();
|
||||
}
|
||||
|
||||
@observable
|
||||
List<CryptoCurrency> cryptoCurrencies;
|
||||
|
||||
@observable
|
||||
List<FiatCurrency> fiatCurrencies;
|
||||
|
||||
final NumberFormat _cryptoNumberFormat;
|
||||
late Timer bestRateSync;
|
||||
|
||||
|
@ -99,12 +94,15 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
|||
|
||||
SettingsStore settingsStore;
|
||||
|
||||
List<SelectableItem> get quoteOptions => [
|
||||
OptionTitle(title: 'Recommended'),
|
||||
...sortedRecommendedQuotes,
|
||||
OptionTitle(title: 'All Providers'),
|
||||
...sortedQuotes
|
||||
];
|
||||
Quote? bestRateQuote;
|
||||
|
||||
Quote? selectedQuote;
|
||||
|
||||
@observable
|
||||
List<CryptoCurrency> cryptoCurrencies;
|
||||
|
||||
@observable
|
||||
List<FiatCurrency> fiatCurrencies;
|
||||
|
||||
@observable
|
||||
bool isBuyAction = true;
|
||||
|
@ -136,12 +134,6 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
|||
@observable
|
||||
String cryptoCurrencyAddress;
|
||||
|
||||
@observable
|
||||
Quote? bestRateQuote;
|
||||
|
||||
@observable
|
||||
Quote? selectedQuote;
|
||||
|
||||
@observable
|
||||
PaymentMethod? selectedPaymentMethod;
|
||||
|
||||
|
@ -152,11 +144,17 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
|||
BuySellQuotLoadingState buySellQuotState;
|
||||
|
||||
@computed
|
||||
bool get isReadyToTrade =>
|
||||
selectedQuote != null &&
|
||||
selectedPaymentMethod != null &&
|
||||
paymentMethodState is PaymentMethodLoaded &&
|
||||
buySellQuotState is BuySellQuotLoaded;
|
||||
bool get isReadyToTrade {
|
||||
final hasSelectedQuote = selectedQuote != null;
|
||||
final hasSelectedPaymentMethod = selectedPaymentMethod != null;
|
||||
final isPaymentMethodLoaded = paymentMethodState is PaymentMethodLoaded;
|
||||
final isBuySellQuotLoaded = buySellQuotState is BuySellQuotLoaded;
|
||||
|
||||
return hasSelectedQuote &&
|
||||
hasSelectedPaymentMethod &&
|
||||
isPaymentMethodLoaded &&
|
||||
isBuySellQuotLoaded;
|
||||
}
|
||||
|
||||
@action
|
||||
void reset() {
|
||||
|
@ -209,8 +207,6 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
|||
.toString()
|
||||
.replaceAll(RegExp('\\,'), '');
|
||||
}
|
||||
|
||||
await calculateBestRate();
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -235,7 +231,6 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
|||
.toString()
|
||||
.replaceAll(RegExp('\\,'), '');
|
||||
}
|
||||
await calculateBestRate();
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -254,6 +249,61 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
|||
}
|
||||
}
|
||||
|
||||
void onTapChoseProvider(BuildContext context) async {
|
||||
final initialQuotes = List<Quote>.from(sortedRecommendedQuotes + sortedQuotes);
|
||||
await calculateBestRate();
|
||||
final newQuotes = (sortedRecommendedQuotes + sortedQuotes);
|
||||
|
||||
for (var quote in newQuotes) quote.limits = null;
|
||||
|
||||
final newQuoteProviders = newQuotes
|
||||
.map((quote) => quote.provider.isAggregator ? quote.rampName : quote.provider.title)
|
||||
.toSet();
|
||||
|
||||
final outOfLimitQuotes = initialQuotes.where((initialQuote) {
|
||||
return !newQuoteProviders.contains(
|
||||
initialQuote.provider.isAggregator ? initialQuote.rampName : initialQuote.provider.title);
|
||||
}).map((missingQuote) {
|
||||
final quote = Quote(
|
||||
rate: missingQuote.rate,
|
||||
feeAmount: missingQuote.feeAmount,
|
||||
networkFee: missingQuote.networkFee,
|
||||
transactionFee: missingQuote.transactionFee,
|
||||
payout: missingQuote.payout,
|
||||
rampId: missingQuote.rampId,
|
||||
rampName: missingQuote.rampName,
|
||||
rampIconPath: missingQuote.rampIconPath,
|
||||
paymentType: missingQuote.paymentType,
|
||||
quoteId: missingQuote.quoteId,
|
||||
recommendations: missingQuote.recommendations,
|
||||
provider: missingQuote.provider,
|
||||
isBuyAction: missingQuote.isBuyAction,
|
||||
limits: missingQuote.limits,
|
||||
);
|
||||
quote.sourceCurrency = missingQuote.sourceCurrency;
|
||||
quote.destinationCurrency = missingQuote.destinationCurrency;
|
||||
return quote;
|
||||
}).toList();
|
||||
|
||||
final updatedQuoteOptions = List<SelectableItem>.from([
|
||||
OptionTitle(title: 'Recommended'),
|
||||
...sortedRecommendedQuotes,
|
||||
if (sortedQuotes.isNotEmpty) OptionTitle(title: 'All Providers'),
|
||||
...sortedQuotes,
|
||||
if (outOfLimitQuotes.isNotEmpty) OptionTitle(title: 'Out of Limits'),
|
||||
...outOfLimitQuotes,
|
||||
]);
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.buyOptionsPage,
|
||||
arguments: [
|
||||
updatedQuoteOptions,
|
||||
changeOption,
|
||||
launchTrade,
|
||||
],
|
||||
).then((value) => calculateBestRate());
|
||||
}
|
||||
|
||||
void _onPairChange() {
|
||||
_initialize();
|
||||
}
|
||||
|
@ -353,10 +403,17 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
|||
if (sortedRecommendedQuotes.isNotEmpty) {
|
||||
sortedRecommendedQuotes.first
|
||||
..isBestRate = true
|
||||
..isSelected = true
|
||||
..recommendations.insert(0, ProviderRecommendation.bestRate);
|
||||
bestRateQuote = sortedRecommendedQuotes.first;
|
||||
|
||||
sortedRecommendedQuotes.sort((a, b) {
|
||||
if (a.provider is OnRamperBuyProvider) return -1;
|
||||
if (b.provider is OnRamperBuyProvider) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
selectedQuote = sortedRecommendedQuotes.first;
|
||||
sortedRecommendedQuotes.first.isSelected = true;
|
||||
}
|
||||
|
||||
buySellQuotState = BuySellQuotLoaded();
|
||||
|
@ -365,7 +422,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
|||
@action
|
||||
Future<void> launchTrade(BuildContext context) async {
|
||||
final provider = selectedQuote!.provider;
|
||||
provider.launchProvider(
|
||||
await provider.launchProvider(
|
||||
context: context,
|
||||
quote: selectedQuote!,
|
||||
amount: amount,
|
||||
|
|
Loading…
Reference in a new issue