diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index c08357e1a..3678f01fd 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -13,6 +13,12 @@ class ExchangeFormState extends ChangeNotifier { Exchange? _exchange; Exchange get exchange => _exchange ??= Exchange.defaultExchange; + String? _providerName; + // default to exchange name that isn't trocador + String get providerName => _providerName ??= Exchange.defaultExchange.name; + + String get combinedExchangeId => "${exchange.name} ($providerName)"; + ExchangeRateType _exchangeRateType = ExchangeRateType.estimated; ExchangeRateType get exchangeRateType => _exchangeRateType; set exchangeRateType(ExchangeRateType exchangeRateType) { @@ -20,8 +26,8 @@ class ExchangeFormState extends ChangeNotifier { // } - Estimate? _estimate; - Estimate? get estimate => _estimate; + List _estimates = []; + List get estimates => _estimates; bool _reversed = false; bool get reversed => _reversed; @@ -149,10 +155,12 @@ class ExchangeFormState extends ChangeNotifier { Future updateExchange({ required Exchange exchange, + required String providerName, required bool shouldUpdateData, required bool shouldNotifyListeners, }) async { _exchange = exchange; + _providerName = providerName; if (shouldUpdateData) { await _updateRangesAndEstimate( shouldNotifyListeners: false, @@ -173,6 +181,7 @@ class ExchangeFormState extends ChangeNotifier { required bool shouldNotifyListeners, }) { _exchange = null; + _providerName = null; _reversed = false; _rate = null; _sendAmount = null; @@ -445,7 +454,7 @@ class ExchangeFormState extends ChangeNotifier { ); return; } - final response = await exchange.getEstimate( + final response = await exchange.getEstimates( sendCurrency!.ticker, receiveCurrency!.ticker, amount, @@ -461,12 +470,16 @@ class ExchangeFormState extends ChangeNotifier { return; } - _estimate = response.value!; + _estimates = response.value!; if (reversed) { - _sendAmount = _estimate!.estimatedAmount; + _sendAmount = _estimates + .firstWhere((e) => e.exchangeProvider == providerName) + .estimatedAmount; } else { - _receiveAmount = _estimate!.estimatedAmount; + _receiveAmount = _estimates + .firstWhere((e) => e.exchangeProvider == providerName) + .estimatedAmount; } _rate = @@ -518,7 +531,7 @@ class ExchangeFormState extends ChangeNotifier { "\n\t reversed: $reversed," "\n\t sendAmount: $sendAmount," "\n\t receiveAmount: $receiveAmount," - "\n\t estimate: $estimate," + "\n\t estimates: $estimates," "\n\t minSendAmount: $minSendAmount," "\n\t maxSendAmount: $maxSendAmount," "\n\t minReceiveAmount: $minReceiveAmount," diff --git a/lib/models/exchange/response_objects/estimate.dart b/lib/models/exchange/response_objects/estimate.dart index d93fcb560..9284c8340 100644 --- a/lib/models/exchange/response_objects/estimate.dart +++ b/lib/models/exchange/response_objects/estimate.dart @@ -7,7 +7,8 @@ class Estimate { final bool reversed; final String? warningMessage; final String? rateId; - final String? exchangeProvider; + final String exchangeProvider; + final String? kycRating; Estimate({ required this.estimatedAmount, @@ -15,10 +16,15 @@ class Estimate { required this.reversed, this.warningMessage, this.rateId, - this.exchangeProvider, + required this.exchangeProvider, + this.kycRating, }); - factory Estimate.fromMap(Map map) { + factory Estimate.fromMap( + Map map, { + required String exchangeProvider, + String? kycRating, + }) { try { return Estimate( estimatedAmount: Decimal.parse(map["estimatedAmount"] as String), @@ -26,6 +32,8 @@ class Estimate { reversed: map["reversed"] as bool, warningMessage: map["warningMessage"] as String?, rateId: map["rateId"] as String?, + exchangeProvider: exchangeProvider, + kycRating: kycRating, ); } catch (e, s) { Logging.instance.log("Estimate.fromMap(): $e\n$s", level: LogLevel.Error); @@ -41,6 +49,7 @@ class Estimate { "warningMessage": warningMessage, "rateId": rateId, "exchangeProvider": exchangeProvider, + "kycRating": kycRating, }; } diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 3ecee0994..9c22cd36a 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -409,7 +409,11 @@ class _ExchangeFormState extends ConsumerState { final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; final sendAmount = ref.read(exchangeFormStateProvider).sendAmount!; - final estimate = ref.read(exchangeFormStateProvider).estimate!; + final estimate = ref.read(exchangeFormStateProvider).estimates.firstWhere( + (e) => + e.exchangeProvider == + ref.read(exchangeFormStateProvider).providerName, + ); if (rateType == ExchangeRateType.fixed && toTicker.toUpperCase() == "WOW") { await showDialog( diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart new file mode 100644 index 000000000..ed161553c --- /dev/null +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -0,0 +1,306 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_info_button.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; + +class ExchangeProviderOption extends ConsumerStatefulWidget { + const ExchangeProviderOption({ + Key? key, + required this.exchange, + required this.exchangeProvider, + required this.fixedRate, + required this.reversed, + }) : super(key: key); + + final Exchange exchange; + final String exchangeProvider; + final bool fixedRate; + final bool reversed; + + @override + ConsumerState createState() => + _ExchangeProviderOptionState(); +} + +class _ExchangeProviderOptionState + extends ConsumerState { + final isDesktop = Util.isDesktop; + late final String _id; + + @override + void initState() { + _id = "${widget.exchange.name} (${widget.exchangeProvider})"; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final sendCurrency = ref + .watch(exchangeFormStateProvider.select((value) => value.sendCurrency)); + final receivingCurrency = ref.watch( + exchangeFormStateProvider.select((value) => value.receiveCurrency)); + final fromAmount = ref + .watch(exchangeFormStateProvider.select((value) => value.sendAmount)); + final toAmount = ref.watch( + exchangeFormStateProvider.select((value) => value.receiveAmount)); + + final selected = ref.watch(exchangeFormStateProvider + .select((value) => value.combinedExchangeId)) == + _id; + + return ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: () { + if (!selected) { + showLoading( + whileFuture: ref.read(exchangeFormStateProvider).updateExchange( + exchange: widget.exchange, + shouldUpdateData: true, + shouldNotifyListeners: true, + providerName: widget.exchangeProvider, + ), + context: context, + message: "Updating rates", + isDesktop: isDesktop, + ); + } + }, + child: Container( + color: Colors.transparent, + child: Padding( + padding: + isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Padding( + padding: EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: _id, + groupValue: ref.watch(exchangeFormStateProvider + .select((value) => value.combinedExchangeId)), + onChanged: (_) { + if (!selected) { + ref.read(exchangeFormStateProvider).updateExchange( + exchange: widget.exchange, + shouldUpdateData: true, + shouldNotifyListeners: true, + providerName: widget.exchangeProvider, + ); + } + }, + ), + ), + ), + const SizedBox( + width: 14, + ), + Padding( + padding: const EdgeInsets.only(top: 5.0), + child: SizedBox( + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + child: SvgPicture.asset( + Assets.exchange.getIconFor( + exchangeName: widget.exchange.name, + ), + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ), + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.exchangeProvider, + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + if (sendCurrency != null && + receivingCurrency != null && + toAmount != null && + toAmount > Decimal.zero && + fromAmount != null && + fromAmount > Decimal.zero) + FutureBuilder( + future: widget.exchange.getEstimates( + sendCurrency.ticker, + receivingCurrency.ticker, + widget.reversed ? toAmount : fromAmount, + widget.fixedRate, + widget.reversed, + ), + builder: (context, + AsyncSnapshot>> + snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + final estimates = snapshot.data?.value; + if (estimates != null && + estimates + .where((e) => + e.exchangeProvider == + widget.exchangeProvider) + .isNotEmpty) { + final estimate = estimates.firstWhere((e) => + e.exchangeProvider == + widget.exchangeProvider); + int decimals; + try { + decimals = coinFromTickerCaseInsensitive( + receivingCurrency.ticker) + .decimals; + } catch (_) { + decimals = 8; // some reasonable alternative + } + Amount rate; + if (estimate.reversed) { + rate = (toAmount / estimate.estimatedAmount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } else { + rate = (estimate.estimatedAmount / fromAmount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } + + return ConditionalParent( + condition: widget.exchange.name == + TrocadorExchange.exchangeName, + builder: (child) { + return Row( + children: [ + child, + TrocadorKYCInfoButton( + kycType: TrocadorKYCType.fromString( + estimate.kycRating!, + ), + ), + ], + ); + }, + child: Text( + "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${receivingCurrency.ticker.toUpperCase()}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ); + } else if (snapshot.data?.exception + is PairUnavailableException) { + return Text( + "Unsupported pair", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + Logging.instance.log( + "$runtimeType failed to fetch rate for $_id}: ${snapshot.data}", + level: LogLevel.Warning, + ); + return Text( + "Failed to fetch rate", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Loading", + "Loading.", + "Loading..", + "Loading...", + ], + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + }, + ), + if (!(sendCurrency != null && + receivingCurrency != null && + toAmount != null && + toAmount > Decimal.zero && + fromAmount != null && + fromAmount > Decimal.zero)) + Text( + "n/a", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index 6fcf69a74..feeb6b661 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -1,27 +1,13 @@ -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart'; import 'package:stackwallet/models/exchange/aggregate_currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_option.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; -import 'package:stackwallet/utilities/amount/amount.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/show_loading.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_info_button.dart'; -import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class ExchangeProviderOptions extends ConsumerStatefulWidget { @@ -63,11 +49,16 @@ class _ExchangeProviderOptionsState @override Widget build(BuildContext context) { - final sendCurrency = ref.watch(exchangeFormStateProvider).sendCurrency; - final receivingCurrency = - ref.watch(exchangeFormStateProvider).receiveCurrency; - final fromAmount = ref.watch(exchangeFormStateProvider).sendAmount; - final toAmount = ref.watch(exchangeFormStateProvider).receiveAmount; + final sendCurrency = ref.watch( + exchangeFormStateProvider.select( + (value) => value.sendCurrency, + ), + ); + final receivingCurrency = ref.watch( + exchangeFormStateProvider.select( + (value) => value.receiveCurrency, + ), + ); final showChangeNow = exchangeSupported( exchangeName: ChangeNowExchange.exchangeName, @@ -93,239 +84,12 @@ class _ExchangeProviderOptionsState child: Column( children: [ if (showChangeNow) - ConditionalParent( - condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), - child: GestureDetector( - onTap: () { - if (ref.read(exchangeFormStateProvider).exchange.name != - ChangeNowExchange.exchangeName) { - showLoading( - whileFuture: - ref.read(exchangeFormStateProvider).updateExchange( - exchange: ChangeNowExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ), - context: context, - message: "Updating rates", - isDesktop: isDesktop, - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Padding( - padding: - EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: ChangeNowExchange.exchangeName, - groupValue: ref.watch(exchangeFormStateProvider - .select((value) => value.exchange.name)), - onChanged: (_) { - if (ref - .read(exchangeFormStateProvider) - .exchange - .name != - ChangeNowExchange.exchangeName) { - ref - .read(exchangeFormStateProvider) - .updateExchange( - exchange: ChangeNowExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ); - } - }, - ), - ), - ), - const SizedBox( - width: 14, - ), - Padding( - padding: const EdgeInsets.only(top: 5.0), - child: SizedBox( - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - child: SvgPicture.asset( - Assets.exchange.changeNow, - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - ChangeNowExchange.exchangeName, - style: - STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - if (sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero) - FutureBuilder( - future: - ChangeNowExchange.instance.getEstimate( - sendCurrency.ticker, - receivingCurrency.ticker, - widget.reversed ? toAmount : fromAmount, - widget.fixedRate, - widget.reversed, - ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Coin coin; - try { - coin = coinFromTickerCaseInsensitive( - receivingCurrency.ticker); - } catch (_) { - coin = Coin.bitcoin; - } - Amount rate; - if (estimate.reversed) { - rate = (toAmount / - estimate.estimatedAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: - coin.decimals); - } else { - rate = (estimate.estimatedAmount / - fromAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: - coin.decimals); - } - - return Text( - "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale), - ), - )} ${receivingCurrency.ticker.toUpperCase()}", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else if (snapshot.data?.exception - is PairUnavailableException) { - return Text( - "Unsupported pair", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}", - level: LogLevel.Warning, - ); - return Text( - "Failed to fetch rate", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading", - "Loading.", - "Loading..", - "Loading...", - ], - style: - STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }, - ), - if (!(sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero)) - Text( - "n/a", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), + ExchangeProviderOption( + exchange: ChangeNowExchange.instance, + exchangeProvider: ChangeNowExchange.exchangeName, + fixedRate: widget.fixedRate, + reversed: widget.reversed, ), - if (showChangeNow && showMajesticBank) isDesktop ? Container( @@ -336,239 +100,12 @@ class _ExchangeProviderOptionsState : const SizedBox( height: 16, ), - if (showMajesticBank) - ConditionalParent( - condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), - child: GestureDetector( - onTap: () { - if (ref.read(exchangeFormStateProvider).exchange.name != - MajesticBankExchange.exchangeName) { - showLoading( - whileFuture: - ref.read(exchangeFormStateProvider).updateExchange( - exchange: MajesticBankExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ), - context: context, - isDesktop: isDesktop, - message: "Updating rates", - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Padding( - padding: - EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: MajesticBankExchange.exchangeName, - groupValue: ref.watch(exchangeFormStateProvider - .select((value) => value.exchange.name)), - onChanged: (_) { - if (ref - .read(exchangeFormStateProvider) - .exchange - .name != - MajesticBankExchange.exchangeName) { - ref - .read(exchangeFormStateProvider) - .updateExchange( - exchange: MajesticBankExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ); - } - }, - ), - ), - ), - const SizedBox( - width: 14, - ), - Padding( - padding: const EdgeInsets.only(top: 5.0), - child: SizedBox( - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - child: SvgPicture.asset( - Assets.exchange.majesticBankBlue, - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - MajesticBankExchange.exchangeName, - style: - STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - if (sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero) - FutureBuilder( - future: - MajesticBankExchange.instance.getEstimate( - sendCurrency.ticker, - receivingCurrency.ticker, - widget.reversed ? toAmount : fromAmount, - widget.fixedRate, - widget.reversed, - ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Coin coin; - try { - coin = coinFromTickerCaseInsensitive( - receivingCurrency.ticker); - } catch (_) { - coin = Coin.bitcoin; - } - Amount rate; - if (estimate.reversed) { - rate = (toAmount / - estimate.estimatedAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: coin.decimals, - ); - } else { - rate = (estimate.estimatedAmount / - fromAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: coin.decimals, - ); - } - - return Text( - "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale), - ), - )} ${receivingCurrency.ticker.toUpperCase()}", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else if (snapshot.data?.exception - is PairUnavailableException) { - return Text( - "Unsupported pair", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for Majestic Bank: ${snapshot.data}", - level: LogLevel.Warning, - ); - return Text( - "Failed to fetch rate", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading", - "Loading.", - "Loading..", - "Loading...", - ], - style: - STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }, - ), - if (!(sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero)) - Text( - "n/a", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), + ExchangeProviderOption( + exchange: MajesticBankExchange.instance, + exchangeProvider: MajesticBankExchange.exchangeName, + fixedRate: widget.fixedRate, + reversed: widget.reversed, ), if ((showChangeNow || showMajesticBank) && showTrocador) isDesktop @@ -580,448 +117,13 @@ class _ExchangeProviderOptionsState : const SizedBox( height: 16, ), - if (showTrocador) - ConditionalParent( - condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), - child: GestureDetector( - onTap: () { - if (ref.read(exchangeFormStateProvider).exchange.name != - TrocadorExchange.exchangeName) { - showLoading( - whileFuture: - ref.read(exchangeFormStateProvider).updateExchange( - exchange: TrocadorExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ), - context: context, - isDesktop: isDesktop, - message: "Updating rates", - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Padding( - padding: - EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: TrocadorExchange.exchangeName, - groupValue: ref.watch(exchangeFormStateProvider - .select((value) => value.exchange.name)), - onChanged: (_) { - if (ref - .read(exchangeFormStateProvider) - .exchange - .name != - TrocadorExchange.exchangeName) { - ref - .read(exchangeFormStateProvider) - .updateExchange( - exchange: TrocadorExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ); - } - }, - ), - ), - ), - const SizedBox( - width: 14, - ), - Padding( - padding: const EdgeInsets.only(top: 5.0), - child: SizedBox( - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - child: SvgPicture.asset( - Assets.exchange.trocador, - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - TrocadorExchange.exchangeName, - style: - STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - if (sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero) - FutureBuilder( - future: TrocadorExchange.instance.getEstimate( - sendCurrency.ticker, - receivingCurrency.ticker, - widget.reversed ? toAmount : fromAmount, - widget.fixedRate, - widget.reversed, - ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Coin coin; - try { - coin = coinFromTickerCaseInsensitive( - receivingCurrency.ticker); - } catch (_) { - coin = Coin.bitcoin; - } - Amount rate; - if (estimate.reversed) { - rate = (toAmount / - estimate.estimatedAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: coin.decimals, - ); - } else { - rate = (estimate.estimatedAmount / - fromAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: coin.decimals, - ); - } - - return Text( - "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale), - ), - )} ${receivingCurrency.ticker.toUpperCase()}", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else if (snapshot.data?.exception - is PairUnavailableException) { - return Text( - "Unsupported pair", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for Trocador: ${snapshot.data}", - level: LogLevel.Warning, - ); - return Text( - "Failed to fetch rate", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading", - "Loading.", - "Loading..", - "Loading...", - ], - style: - STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }, - ), - if (!(sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero)) - Text( - "n/a", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - const TrocadorKYCInfoButton( - kycType: TrocadorKYCType.a, - ), - ], - ), - ), - ), - ), + ExchangeProviderOption( + fixedRate: widget.fixedRate, + reversed: widget.reversed, + exchange: TrocadorExchange.instance, + exchangeProvider: 'LetsExchange', ), - // if (isDesktop) - // Container( - // height: 1, - // color: Theme.of(context).extension()!.background, - // ), - // if (!isDesktop) - // const SizedBox( - // height: 16, - // ), - // ConditionalParent( - // condition: isDesktop, - // builder: (child) => MouseRegion( - // cursor: SystemMouseCursors.click, - // child: child, - // ), - // child: GestureDetector( - // onTap: () { - // if (ref.read(currentExchangeNameStateProvider.state).state != - // SimpleSwapExchange.exchangeName) { - // // ref.read(currentExchangeNameStateProvider.state).state = - // // SimpleSwapExchange.exchangeName; - // ref.read(exchangeFormStateProvider).exchange = - // Exchange.fromName(ref - // .read(currentExchangeNameStateProvider.state) - // .state); - // } - // }, - // child: Container( - // color: Colors.transparent, - // child: Padding( - // padding: isDesktop - // ? const EdgeInsets.all(16) - // : const EdgeInsets.all(0), - // child: Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // SizedBox( - // width: 20, - // height: 20, - // child: Radio( - // activeColor: Theme.of(context) - // .extension()! - // .radioButtonIconEnabled, - // value: SimpleSwapExchange.exchangeName, - // groupValue: ref - // .watch(currentExchangeNameStateProvider.state) - // .state, - // onChanged: (value) { - // if (value is String) { - // ref - // .read(currentExchangeNameStateProvider.state) - // .state = value; - // ref.read(exchangeFormStateProvider).exchange = - // Exchange.fromName(ref - // .read(currentExchangeNameStateProvider - // .state) - // .state); - // } - // }, - // ), - // ), - // const SizedBox( - // width: 14, - // ), - // // SvgPicture.asset( - // // Assets.exchange.simpleSwap, - // // width: isDesktop ? 32 : 24, - // // height: isDesktop ? 32 : 24, - // // ), - // // const SizedBox( - // // width: 10, - // // ), - // // Expanded( - // // child: Column( - // // mainAxisAlignment: MainAxisAlignment.start, - // // mainAxisSize: MainAxisSize.min, - // // crossAxisAlignment: CrossAxisAlignment.start, - // // children: [ - // // Text( - // // SimpleSwapExchange.exchangeName, - // // style: STextStyles.titleBold12(context).copyWith( - // // color: Theme.of(context) - // // .extension()! - // // .textDark2, - // // ), - // // ), - // // if (from != null && - // // to != null && - // // toAmount != null && - // // toAmount! > Decimal.zero && - // // fromAmount != null && - // // fromAmount! > Decimal.zero) - // // FutureBuilder( - // // future: SimpleSwapExchange().getEstimate( - // // from!, - // // to!, - // // // reversed ? toAmount! : fromAmount!, - // // fromAmount!, - // // fixedRate, - // // // reversed, - // // false, - // // ), - // // builder: (context, - // // AsyncSnapshot> - // // snapshot) { - // // if (snapshot.connectionState == - // // ConnectionState.done && - // // snapshot.hasData) { - // // final estimate = snapshot.data?.value; - // // if (estimate != null) { - // // Decimal rate = (estimate.estimatedAmount / - // // fromAmount!) - // // .toDecimal( - // // scaleOnInfinitePrecision: 12); - // // - // // Coin coin; - // // try { - // // coin = - // // coinFromTickerCaseInsensitive(to!); - // // } catch (_) { - // // coin = Coin.bitcoin; - // // } - // // return Text( - // // "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( - // // value: rate, - // // locale: ref.watch( - // // localeServiceChangeNotifierProvider - // // .select( - // // (value) => value.locale), - // // ), - // // decimalPlaces: - // // Constants.decimalPlacesForCoin( - // // coin), - // // )} ${to!.toUpperCase()}", - // // style: - // // STextStyles.itemSubtitle12(context) - // // .copyWith( - // // color: Theme.of(context) - // // .extension()! - // // .textSubtitle1, - // // ), - // // ); - // // } else { - // // Logging.instance.log( - // // "$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}", - // // level: LogLevel.Warning, - // // ); - // // return Text( - // // "Failed to fetch rate", - // // style: - // // STextStyles.itemSubtitle12(context) - // // .copyWith( - // // color: Theme.of(context) - // // .extension()! - // // .textSubtitle1, - // // ), - // // ); - // // } - // // } else { - // // return AnimatedText( - // // stringsToLoopThrough: const [ - // // "Loading", - // // "Loading.", - // // "Loading..", - // // "Loading...", - // // ], - // // style: STextStyles.itemSubtitle12(context) - // // .copyWith( - // // color: Theme.of(context) - // // .extension()! - // // .textSubtitle1, - // // ), - // // ); - // // } - // // }, - // // ), - // // // if (!(from != null && - // // // to != null && - // // // (reversed - // // // ? toAmount != null && toAmount! > Decimal.zero - // // // : fromAmount != null && - // // // fromAmount! > Decimal.zero))) - // // if (!(from != null && - // // to != null && - // // toAmount != null && - // // toAmount! > Decimal.zero && - // // fromAmount != null && - // // fromAmount! > Decimal.zero)) - // // Text( - // // "n/a", - // // style: STextStyles.itemSubtitle12(context) - // // .copyWith( - // // color: Theme.of(context) - // // .extension()! - // // .textSubtitle1, - // // ), - // // ), - // // ], - // // ), - // // ), - // ], - // ), - // ), - // ), - // ), - // ), ], ), ); diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index 57823a813..6f050d2e2 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -483,6 +483,7 @@ class ChangeNowAPI { reversed: false, rateId: value.rateId, warningMessage: value.warningMessage, + exchangeProvider: ChangeNowExchange.exchangeName, ), ); } catch (_) { @@ -566,6 +567,7 @@ class ChangeNowAPI { reversed: reversed, rateId: value.rateId, warningMessage: value.warningMessage, + exchangeProvider: ChangeNowExchange.exchangeName, ), ); } catch (_) { diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index f5b68d300..a6037b511 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -128,7 +128,7 @@ class ChangeNowExchange extends Exchange { } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -151,7 +151,10 @@ class ChangeNowExchange extends Exchange { fromAmount: amount, ); } - return response; + return ExchangeResponse( + value: response.value == null ? null : [response.value!], + exception: response.exception, + ); } @override diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index c4979788b..1db451457 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -60,7 +60,7 @@ abstract class Exchange { bool fixedRate, ); - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart index 0980abc62..95031490c 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart @@ -170,7 +170,7 @@ class MajesticBankExchange extends Exchange { } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -192,8 +192,9 @@ class MajesticBankExchange extends Exchange { estimatedAmount: reversed ? calc.fromAmount : calc.receiveAmount, fixedRate: fixedRate, reversed: reversed, + exchangeProvider: MajesticBankExchange.exchangeName, ); - return ExchangeResponse(value: estimate); + return ExchangeResponse(value: [estimate]); } @override diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart index 2822d4a1e..0bfe93a38 100644 --- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -89,7 +89,7 @@ class SimpleSwapExchange extends Exchange { } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -109,11 +109,14 @@ class SimpleSwapExchange extends Exchange { } return ExchangeResponse( - value: Estimate( - estimatedAmount: Decimal.parse(response.value!), - fixedRate: fixedRate, - reversed: reversed, - ), + value: [ + Estimate( + estimatedAmount: Decimal.parse(response.value!), + fixedRate: fixedRate, + reversed: reversed, + exchangeProvider: SimpleSwapExchange.exchangeName, + ), + ], ); } diff --git a/lib/services/exchange/trocador/response_objects/trocador_quote.dart b/lib/services/exchange/trocador/response_objects/trocador_quote.dart index 7b4a86984..ada8725f5 100644 --- a/lib/services/exchange/trocador/response_objects/trocador_quote.dart +++ b/lib/services/exchange/trocador/response_objects/trocador_quote.dart @@ -5,7 +5,8 @@ class TrocadorQuote { final String kycRating; final int insurance; final bool fixed; - final Decimal amountTo; + final Decimal? amountTo; + final Decimal? amountFrom; final Decimal waste; TrocadorQuote({ @@ -14,6 +15,7 @@ class TrocadorQuote { required this.insurance, required this.fixed, required this.amountTo, + required this.amountFrom, required this.waste, }); @@ -24,7 +26,8 @@ class TrocadorQuote { insurance: map['insurance'] as int, // wtf trocador? fixed: map['fixed'] == "True", - amountTo: Decimal.parse(map['amount_to'].toString()), + amountTo: Decimal.tryParse(map['amount_to'].toString()), + amountFrom: Decimal.tryParse(map['amount_from'].toString()), waste: Decimal.parse(map['waste'].toString()), ); } @@ -37,6 +40,7 @@ class TrocadorQuote { 'insurance: $insurance, ' 'fixed: $fixed, ' 'amountTo: $amountTo, ' + 'amountFrom: $amountFrom, ' 'waste: $waste ' ')'; } diff --git a/lib/services/exchange/trocador/trocador_exchange.dart b/lib/services/exchange/trocador/trocador_exchange.dart index a4bfb26a3..8085cfca5 100644 --- a/lib/services/exchange/trocador/trocador_exchange.dart +++ b/lib/services/exchange/trocador/trocador_exchange.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:decimal/decimal.dart'; import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; @@ -8,6 +10,7 @@ import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_coin.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart'; import 'package:stackwallet/services/exchange/trocador/trocador_api.dart'; import 'package:uuid/uuid.dart'; @@ -177,7 +180,7 @@ class TrocadorExchange extends Exchange { } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -208,15 +211,47 @@ class TrocadorExchange extends Exchange { return ExchangeResponse(exception: response.exception); } + final List estimates = []; + final List cOrLowerQuotes = []; + + for (final quote in response.value!.quotes) { + if (quote.fixed == isPayment) { + final rating = quote.kycRating.toLowerCase(); + if (rating == "a" || rating == "b") { + estimates.add( + Estimate( + estimatedAmount: isPayment ? quote.amountFrom! : quote.amountTo!, + fixedRate: quote.fixed, + reversed: isPayment, + exchangeProvider: quote.provider, + rateId: response.value!.tradeId, + kycRating: quote.kycRating, + ), + ); + } else { + cOrLowerQuotes.add(quote); + } + } + } + + cOrLowerQuotes.sort((a, b) => b.waste.compareTo(a.waste)); + + for (int i = 0; i < max(3, cOrLowerQuotes.length); i++) { + final quote = cOrLowerQuotes[i]; + estimates.add( + Estimate( + estimatedAmount: isPayment ? quote.amountFrom! : quote.amountTo!, + fixedRate: quote.fixed, + reversed: isPayment, + exchangeProvider: quote.provider, + rateId: response.value!.tradeId, + kycRating: quote.kycRating, + ), + ); + } + return ExchangeResponse( - value: Estimate( - estimatedAmount: - isPayment ? response.value!.amountFrom : response.value!.amountTo, - fixedRate: isPayment, - reversed: isPayment, - exchangeProvider: response.value!.provider, - rateId: response.value!.tradeId, - ), + value: estimates, ); } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 645d5d317..6444b2896 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -64,6 +68,22 @@ class _EXCHANGE { String get majesticBankBlue => "${_path}mb_blue.svg"; String get majesticBankGreen => "${_path}mb_green.svg"; String get trocador => "${_path}trocador.svg"; + + String getIconFor({required String exchangeName}) { + switch (exchangeName) { + case SimpleSwapExchange.exchangeName: + return simpleSwap; + case ChangeNowExchange.exchangeName: + return changeNow; + case MajesticBankExchange.exchangeName: + return majesticBankBlue; + case TrocadorExchange.exchangeName: + return trocador; + default: + throw ArgumentError("Invalid exchange name passed to " + "Assets.exchange.getIconFor()"); + } + } } class _BUY { diff --git a/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart b/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart index e0a11465d..538ee8c37 100644 --- a/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart +++ b/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart @@ -3,4 +3,13 @@ enum TrocadorKYCType { b, c, d; + + static TrocadorKYCType fromString(String type) { + for (final result in values) { + if (result.name == type.toLowerCase()) { + return result; + } + } + throw ArgumentError("Invalid trocador kyc type: $type"); + } }