mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-27 06:18:50 +01:00
new buy/sell
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/provider_types.dart';
|
||||
import 'package:cake_wallet/exchange/limits.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
enum ProviderRecommendation { bestRate, lowKyc, successRate }
|
||||
@@ -13,11 +14,11 @@ extension RecommendationTitle on ProviderRecommendation {
|
||||
String get title {
|
||||
switch (this) {
|
||||
case ProviderRecommendation.bestRate:
|
||||
return 'BEST RATE';
|
||||
return S.current.best_rate;
|
||||
case ProviderRecommendation.lowKyc:
|
||||
return 'LOW KYC';
|
||||
case ProviderRecommendation.successRate:
|
||||
return 'HIGHEST SUCCESS RATE';
|
||||
return S.current.highest_success_rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,17 +132,21 @@ extension PaymentTypeTitle on PaymentType {
|
||||
String? get darkIconPath {
|
||||
switch (this) {
|
||||
case PaymentType.all:
|
||||
return 'assets/images/usd_round_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/all_methods.svg';
|
||||
case PaymentType.creditCard:
|
||||
case PaymentType.debitCard:
|
||||
case PaymentType.yellowCardBankTransfer:
|
||||
return 'assets/images/card_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/debit_card.svg';
|
||||
case PaymentType.bankTransfer:
|
||||
return 'assets/images/bank_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/bank_transfer.svg';
|
||||
case PaymentType.skrill:
|
||||
return 'assets/images/skrill.svg';
|
||||
case PaymentType.applePay:
|
||||
return 'assets/images/apple_pay_round_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/apple_pay.svg';
|
||||
case PaymentType.googlePay:
|
||||
return "assets/new-ui/buy_payment_methods/google_pay.svg";
|
||||
case PaymentType.paypal:
|
||||
return "assets/new-ui/buy_payment_methods/paypal.svg";
|
||||
case PaymentType.revolutPay:
|
||||
return 'assets/images/revolut_dark.svg';
|
||||
default:
|
||||
@@ -150,6 +154,12 @@ extension PaymentTypeTitle on PaymentType {
|
||||
}
|
||||
}
|
||||
|
||||
bool get isMonochromeIcon => [
|
||||
"assets/new-ui/buy_payment_methods/all_methods.svg",
|
||||
"assets/new-ui/buy_payment_methods/debit_card.svg",
|
||||
"assets/new-ui/buy_payment_methods/bank_transfer.svg"
|
||||
].contains(darkIconPath);
|
||||
|
||||
String? get description {
|
||||
switch (this) {
|
||||
default:
|
||||
@@ -190,7 +200,7 @@ class PaymentMethod extends SelectableOption {
|
||||
return PaymentMethod(
|
||||
paymentMethodType: PaymentType.all,
|
||||
customTitle: 'All Payment Methods',
|
||||
customIconPath: 'assets/images/dollar_coin.svg');
|
||||
customIconPath: 'assets/new-ui/buy_payment_methods/all_methods.svg');
|
||||
}
|
||||
|
||||
factory PaymentMethod.fromOnramperJson(Map<String, dynamic> json) {
|
||||
|
||||
+1
-1
@@ -1495,7 +1495,7 @@ Future<void> setup({
|
||||
getIt.registerFactory(() => BuyAmountViewModel());
|
||||
|
||||
getIt.registerFactoryParam<BuySellViewModel, BuySellPageMode, void>(
|
||||
(mode, _) => BuySellViewModel(mode: mode, getIt.get<AppStore>()));
|
||||
(mode, _) => BuySellViewModel(mode: mode, getIt.get<AppStore>(), fiatConversionStore: getIt.get<FiatConversionStore>()));
|
||||
|
||||
getIt.registerFactoryParam<NewBuySellAmountPage, BuySellPageMode, void>((mode, _) =>
|
||||
NewBuySellAmountPage(buySellViewModel: getIt.get<BuySellViewModel>(param1: mode)));
|
||||
|
||||
@@ -22,6 +22,8 @@ class ListItemRegularRow extends ListItem {
|
||||
this.leadingIconSize,
|
||||
this.badgeIconSize,
|
||||
this.iconColor,
|
||||
this.secondaryLabel,
|
||||
this.subtitleColor,
|
||||
});
|
||||
|
||||
final String? subtitle;
|
||||
@@ -31,10 +33,12 @@ class ListItemRegularRow extends ListItem {
|
||||
final String? badgeIconPath;
|
||||
final String? copyableText;
|
||||
final VoidCallback? onTap;
|
||||
final String? secondaryLabel;
|
||||
final bool showArrow;
|
||||
final Widget? bottomWidget;
|
||||
final Widget? trailingWidget;
|
||||
final bool truncateTrailingText;
|
||||
final Color? subtitleColor;
|
||||
final Color? foregroundColor;
|
||||
final double? trailingIconSize;
|
||||
final Widget? leadingIconErrorWidget;
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cake_wallet/buy/payment_method.dart';
|
||||
import 'package:cake_wallet/buy/sell_buy_states.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/buy_sell/buy_sell_provider_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/floating_amount_input.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/themes/core/theme_extension.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class NewBuySellAmountPage extends StatefulWidget {
|
||||
const NewBuySellAmountPage({super.key, required this.buySellViewModel});
|
||||
@@ -22,7 +34,24 @@ class NewBuySellAmountPage extends StatefulWidget {
|
||||
|
||||
class _NewBuySellAmountPageState extends State<NewBuySellAmountPage> {
|
||||
bool _customAmountMode = false;
|
||||
bool _isLoadingPaymentMethods = false;
|
||||
final customInputController = TextEditingController();
|
||||
final customInputFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// this is a hack for the "up to" display to show the actual upper limit.
|
||||
// limits are loaded alongside quotes, and quotes for some providers only load properly with an amount and payment method selected.
|
||||
// it'll be refactored soon anyway, i guess.
|
||||
widget.buySellViewModel.changeFiatAmount(amount: "1000");
|
||||
widget.buySellViewModel.calculateBestRate();
|
||||
when(
|
||||
(_) => widget.buySellViewModel.paymentMethodState is PaymentMethodLoaded,
|
||||
() => widget.buySellViewModel.selectedPaymentMethod = widget.buySellViewModel.paymentMethods
|
||||
.firstWhere((item) => item.paymentMethodType == PaymentType.all));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -41,40 +70,61 @@ class _NewBuySellAmountPageState extends State<NewBuySellAmountPage> {
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: _pageTitle,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context, rootNavigator: true).pop,
|
||||
Observer(
|
||||
builder: (_) => ModalTopBar(
|
||||
title: _pageTitle,
|
||||
bottomText: !_customAmountMode || widget.buySellViewModel.maxFiatAmount == null
|
||||
? null
|
||||
: "${S.of(context).up_to} ~${widget.buySellViewModel.maxFiatAmount} ${widget.buySellViewModel.fiatCurrency.title}",
|
||||
leadingIcon: Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context, rootNavigator: true).pop,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
child: _customAmountMode
|
||||
? Observer(
|
||||
builder: (_) => BuySellCustomAmountInput(
|
||||
fiatCurrency: widget.buySellViewModel.fiatCurrency,
|
||||
cryptoCurrency: widget.buySellViewModel.cryptoCurrency,
|
||||
cryptoAmount: widget.buySellViewModel.cryptoAmount,
|
||||
controller: customInputController,
|
||||
onContinuePressed: () {},
|
||||
onChanged: (amount) =>
|
||||
widget.buySellViewModel.changeFiatAmount(amount: amount),
|
||||
))
|
||||
: BuySellDefaultAmountSelector(
|
||||
key: ValueKey(0),
|
||||
defaultAmounts: widget.buySellViewModel.defaultAmounts,
|
||||
currency: widget.buySellViewModel.fiatCurrency,
|
||||
mode: widget.buySellViewModel.mode,
|
||||
onSelected: (amount) {
|
||||
if (amount == null) {
|
||||
setState(() {
|
||||
_customAmountMode = true;
|
||||
});
|
||||
} else {
|
||||
widget.buySellViewModel.changeFiatAmount(amount: amount);
|
||||
}
|
||||
},
|
||||
),
|
||||
child: Observer(
|
||||
builder: (_) => AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
child: _customAmountMode
|
||||
? BuySellCustomAmountInput(
|
||||
fiatCurrency: widget.buySellViewModel.fiatCurrency,
|
||||
cryptoCurrency: widget.buySellViewModel.cryptoCurrency,
|
||||
cryptoAmount: widget.buySellViewModel.cryptoAmount,
|
||||
isLoading: _isLoadingPaymentMethods,
|
||||
hasCurrencySelector: widget.buySellViewModel.hasMultipleCurrencies,
|
||||
onCurrencySelectorPressed: () => selectCryptoCurrency(context),
|
||||
controller: customInputController,
|
||||
focusNode: customInputFocusNode,
|
||||
onContinuePressed: () {
|
||||
navigateToProviders(context);
|
||||
},
|
||||
onChanged: (amount) =>
|
||||
widget.buySellViewModel.changeFiatAmount(amount: amount),
|
||||
)
|
||||
: BuySellDefaultAmountSelector(
|
||||
key: ValueKey(0),
|
||||
defaultAmounts: widget.buySellViewModel.defaultAmounts,
|
||||
fiatCurrency: widget.buySellViewModel.fiatCurrency,
|
||||
currentAmount: widget.buySellViewModel.fiatAmount,
|
||||
hasCurrencySelector: widget.buySellViewModel.hasMultipleCurrencies,
|
||||
onCurrencySelectorPressed: () => selectCryptoCurrency(context),
|
||||
cryptoCurrency: widget.buySellViewModel.cryptoCurrency,
|
||||
isLoading: _isLoadingPaymentMethods,
|
||||
mode: widget.buySellViewModel.mode,
|
||||
onSelected: (amount) async {
|
||||
if (amount == null) {
|
||||
// this resets the rate and prevents showing 0 usd = 0.something btc
|
||||
await widget.buySellViewModel.changeFiatAmount(amount: "");
|
||||
setState(() {
|
||||
_customAmountMode = true;
|
||||
});
|
||||
customInputFocusNode.requestFocus();
|
||||
} else {
|
||||
await widget.buySellViewModel.changeFiatAmount(amount: amount);
|
||||
navigateToProviders(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
@@ -82,12 +132,60 @@ class _NewBuySellAmountPageState extends State<NewBuySellAmountPage> {
|
||||
);
|
||||
}
|
||||
|
||||
void selectCryptoCurrency(BuildContext context) => CurrencyPickerSheet.show(
|
||||
context: context,
|
||||
args: CurrencyPickerArgs(
|
||||
items: widget.buySellViewModel.activeWalletCurrencies.toList(),
|
||||
onSelected: (item) => widget.buySellViewModel.changeCryptoCurrency(currency: item),
|
||||
symbolResolver: widget.buySellViewModel.amountParsingProxy.getCryptoSymbol,
|
||||
));
|
||||
|
||||
String get _pageTitle => widget.buySellViewModel.mode == BuySellPageMode.buy
|
||||
? S.current.buy
|
||||
: S.current.sell +
|
||||
((widget.buySellViewModel.cryptoCurrencies.length == 1)
|
||||
? " ${widget.buySellViewModel.cryptoCurrencies.first.fullName}"
|
||||
: "");
|
||||
|
||||
Future<void> navigateToProviders(BuildContext context) async {
|
||||
if (_isLoadingPaymentMethods) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setState(() {
|
||||
_isLoadingPaymentMethods = true;
|
||||
});
|
||||
|
||||
await asyncWhen((_) => [PaymentMethodLoaded, PaymentMethodFailed]
|
||||
.contains(widget.buySellViewModel.paymentMethodState.runtimeType));
|
||||
|
||||
if (widget.buySellViewModel.paymentMethodState is PaymentMethodFailed) {
|
||||
showPopUp(
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).failed_to_load_payment_methods,
|
||||
alertContent: S.of(context).please_try_again_later,
|
||||
buttonText: "OK",
|
||||
buttonAction: Navigator.of(context).pop));
|
||||
return;
|
||||
}
|
||||
|
||||
widget.buySellViewModel.selectedPaymentMethod = widget.buySellViewModel.paymentMethods
|
||||
.firstWhere((item) => item.paymentMethodType == PaymentType.all);
|
||||
|
||||
// unawaited because BuySellProviderPage has a nice little "loading rates..." ui thingie
|
||||
unawaited(widget.buySellViewModel.calculateBestRate());
|
||||
|
||||
final page = BuySellProviderPage(buySellViewModel: widget.buySellViewModel);
|
||||
Navigator.of(context).push(CupertinoPageRoute(
|
||||
builder: (context) => Material(color: Colors.transparent, child: page)));
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoadingPaymentMethods = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BuySellCustomAmountInput extends StatelessWidget {
|
||||
@@ -98,12 +196,20 @@ class BuySellCustomAmountInput extends StatelessWidget {
|
||||
required this.cryptoAmount,
|
||||
required this.controller,
|
||||
required this.onContinuePressed,
|
||||
required this.onChanged});
|
||||
required this.isLoading,
|
||||
required this.onChanged,
|
||||
required this.focusNode,
|
||||
required this.hasCurrencySelector,
|
||||
required this.onCurrencySelectorPressed});
|
||||
|
||||
final FiatCurrency fiatCurrency;
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
final String cryptoAmount;
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
final bool isLoading;
|
||||
final bool hasCurrencySelector;
|
||||
final VoidCallback onCurrencySelectorPressed;
|
||||
final VoidCallback onContinuePressed;
|
||||
final Function(String) onChanged;
|
||||
|
||||
@@ -116,8 +222,11 @@ class BuySellCustomAmountInput extends StatelessWidget {
|
||||
Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
BuySellCurrencyPickerPill(curr: cryptoCurrency, onTap: onCurrencySelectorPressed),
|
||||
SizedBox.shrink(),
|
||||
FloatingAmountInput(
|
||||
currency: fiatCurrency,
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
@@ -137,6 +246,7 @@ class BuySellCustomAmountInput extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
child: NewPrimaryButton(
|
||||
onPressed: onContinuePressed,
|
||||
isLoading: isLoading,
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary),
|
||||
@@ -150,12 +260,22 @@ class BuySellDefaultAmountSelector extends StatelessWidget {
|
||||
const BuySellDefaultAmountSelector(
|
||||
{super.key,
|
||||
required this.defaultAmounts,
|
||||
required this.currency,
|
||||
required this.fiatCurrency,
|
||||
required this.mode,
|
||||
required this.onSelected});
|
||||
required this.onSelected,
|
||||
this.currentAmount,
|
||||
required this.isLoading,
|
||||
required this.hasCurrencySelector,
|
||||
required this.onCurrencySelectorPressed,
|
||||
required this.cryptoCurrency});
|
||||
|
||||
final List<String> defaultAmounts;
|
||||
final FiatCurrency currency;
|
||||
final String? currentAmount;
|
||||
final bool isLoading;
|
||||
final bool hasCurrencySelector;
|
||||
final VoidCallback onCurrencySelectorPressed;
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
final FiatCurrency fiatCurrency;
|
||||
final BuySellPageMode mode;
|
||||
final Function(String?) onSelected;
|
||||
|
||||
@@ -165,6 +285,7 @@ class BuySellDefaultAmountSelector extends StatelessWidget {
|
||||
spacing: 24,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
BuySellCurrencyPickerPill(curr: cryptoCurrency, onTap: onCurrencySelectorPressed),
|
||||
Text(
|
||||
mode == BuySellPageMode.sell
|
||||
? S.of(context).choose_amount_to_sell
|
||||
@@ -184,7 +305,8 @@ class BuySellDefaultAmountSelector extends StatelessWidget {
|
||||
final String? item = index == defaultAmounts.length ? null : defaultAmounts[index];
|
||||
|
||||
return BuySellAmountPill(
|
||||
amount: item == null ? null : Money.parse(item, currency),
|
||||
isLoading: isLoading && item == currentAmount,
|
||||
amount: item == null ? null : Money.parse(item, fiatCurrency),
|
||||
onTap: () => onSelected(item),
|
||||
);
|
||||
}),
|
||||
@@ -195,10 +317,11 @@ class BuySellDefaultAmountSelector extends StatelessWidget {
|
||||
}
|
||||
|
||||
class BuySellAmountPill extends StatelessWidget {
|
||||
const BuySellAmountPill({super.key, this.amount, required this.onTap});
|
||||
const BuySellAmountPill({super.key, this.amount, required this.onTap, required this.isLoading});
|
||||
|
||||
final Money? amount;
|
||||
final VoidCallback onTap;
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -224,35 +347,85 @@ class BuySellAmountPill extends StatelessWidget {
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(9999999999),
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (amount != null)
|
||||
Text(
|
||||
amount!.toStringWithPrecision(fractionalDigits: 0),
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||||
child: isLoading
|
||||
? CupertinoActivityIndicator()
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (amount != null)
|
||||
Text(
|
||||
amount!.toStringWithPrecision(fractionalDigits: 0),
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||||
),
|
||||
Text(
|
||||
amount?.currency.symbol ?? S.of(context).custom,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: amount == null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
),
|
||||
Text(
|
||||
amount?.currency.symbol ?? S.of(context).custom,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: amount == null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
if (amount == null)
|
||||
Text(
|
||||
S.of(context).enter_amount,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BuySellCurrencyPickerPill extends StatelessWidget {
|
||||
const BuySellCurrencyPickerPill({super.key, required this.curr, required this.onTap});
|
||||
|
||||
final CryptoCurrency curr;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(999999999),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 5, bottom: 5, left: 5, right: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 10,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: curr.iconPath,
|
||||
width: 30,
|
||||
height: 30,
|
||||
),
|
||||
Text(
|
||||
curr.fullName ?? curr.title,
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/dropdown_arrow.svg",
|
||||
width: 8,
|
||||
height: 8,
|
||||
colorFilter:
|
||||
ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
if (amount == null)
|
||||
Text(
|
||||
S.of(context).enter_amount,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import 'package:cake_wallet/buy/payment_method.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/buy_sell/buy_sell_redirecting_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class BuySellConfirmationPage extends StatelessWidget {
|
||||
const BuySellConfirmationPage({super.key, required this.buySellViewModel});
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: _pageTitle,
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(child: Observer(
|
||||
builder: (_) {
|
||||
return Column(
|
||||
spacing: 24,
|
||||
children: [
|
||||
Column(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
"${buySellViewModel.fiatAmount} ${buySellViewModel.fiatCurrency}",
|
||||
style: TextStyle(fontSize: 32),
|
||||
),
|
||||
Text(
|
||||
"≈ ${buySellViewModel.amountForQuote(buySellViewModel.selectedQuote!).toStringWithSymbol(fractionalDigits: 8)}",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w500),
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: NewListSections(sections: {
|
||||
"": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "provider",
|
||||
label: S.of(context).provider,
|
||||
trailingWidget: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: buySellViewModel.selectedQuote!.darkIconPath,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
buySellViewModel.selectedQuote!.rampName ??
|
||||
buySellViewModel.selectedQuote!.provider.title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
SizedBox.shrink()
|
||||
],
|
||||
)),
|
||||
ListItemRegularRow(
|
||||
showArrow: false,
|
||||
keyValue: "payment method",
|
||||
label: S.of(context).payment_method,
|
||||
trailingText: buySellViewModel.selectedQuote!.paymentType.title),
|
||||
ListItemRegularRow(
|
||||
showArrow: false,
|
||||
keyValue: "rate",
|
||||
label: S.of(context).rate,
|
||||
trailingText: buySellViewModel.selectedQuote!.topLeftSubTitle)
|
||||
]
|
||||
}),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(18),
|
||||
child: NewPrimaryButton(
|
||||
onPressed: () => confirm(context),
|
||||
text: S.of(context).proceed,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String get _pageTitle =>
|
||||
(buySellViewModel.mode == BuySellPageMode.buy ? S.current.buy : S.current.sell) +
|
||||
" " +
|
||||
(buySellViewModel.cryptoCurrency.fullName ?? "");
|
||||
|
||||
void confirm(BuildContext context) {
|
||||
final page = BuySellRedirectingPage(buySellViewModel: buySellViewModel);
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(
|
||||
builder: (context) => Material(
|
||||
child: page,
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import 'package:cake_wallet/buy/payment_method.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class BuySellPaymentMethodPage extends StatelessWidget {
|
||||
const BuySellPaymentMethodPage({super.key, required this.buySellViewModel});
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).payment_method,
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Observer(
|
||||
builder: (_) => NewListSections(sections: {
|
||||
"": buySellViewModel.paymentMethods
|
||||
.map((item) => ListItemRegularRow(
|
||||
keyValue: item.title,
|
||||
label: item.title,
|
||||
showArrow: false,
|
||||
iconPath: item.darkIconPath,
|
||||
iconColor: item.paymentMethodType.isMonochromeIcon
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: null,
|
||||
trailingWidget: buySellViewModel.selectedPaymentMethod == item
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 16,
|
||||
)
|
||||
: null,
|
||||
onTap: () async {
|
||||
buySellViewModel.changeOption(item);
|
||||
buySellViewModel.calculateBestRate();
|
||||
Navigator.of(context).pop();
|
||||
}))
|
||||
.toList()
|
||||
}),
|
||||
),
|
||||
))
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,31 @@
|
||||
import 'package:cake_wallet/buy/buy_quote.dart';
|
||||
import 'package:cake_wallet/buy/sell_buy_states.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_dropdown.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/buy_sell/buy_sell_confirmation_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/buy_sell/buy_sell_payment_method_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class BuySellProviderPage extends StatelessWidget {
|
||||
class BuySellProviderPage extends StatefulWidget {
|
||||
const BuySellProviderPage({super.key, required this.buySellViewModel});
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
|
||||
@override
|
||||
State<BuySellProviderPage> createState() => _BuySellProviderPageState();
|
||||
}
|
||||
|
||||
class _BuySellProviderPageState extends State<BuySellProviderPage> {
|
||||
bool _allProvidersExpanded = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@@ -31,15 +48,103 @@ class BuySellProviderPage extends StatelessWidget {
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(child: Column(
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
if(widget.buySellViewModel.buySellQuotState is BuySellQuotFailed) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 24,
|
||||
children: [
|
||||
Icon(Icons.warning_amber_outlined, size: 48),
|
||||
Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).could_not_load_quotes,
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text((widget.buySellViewModel.buySellQuotState as BuySellQuotFailed).errorMessage ??
|
||||
S.of(context).please_try_again_later)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if(widget.buySellViewModel.buySellQuotState is BuySellQuotLoading) {
|
||||
return Center(child: Row(mainAxisAlignment:MainAxisAlignment.center, spacing:8, children: [
|
||||
CupertinoActivityIndicator(),
|
||||
Text(S.of(context).loading_rates, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),)
|
||||
],),);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 18),
|
||||
child: NewListSections(showHeader: true, sections: {
|
||||
"": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "payment method",
|
||||
label: S.of(context).payment_method,
|
||||
showArrow: true,
|
||||
onTap: (){
|
||||
|
||||
final page =BuySellPaymentMethodPage(buySellViewModel: widget.buySellViewModel);
|
||||
Navigator.of(context).push(CupertinoPageRoute(builder: (context)=>Material(color: Colors.transparent,child: page,)));
|
||||
},
|
||||
trailingText: widget.buySellViewModel.selectedPaymentMethod?.title)
|
||||
],
|
||||
S.of(context).available_providers: [
|
||||
...widget.buySellViewModel.sortedRecommendedQuotes.map(quoteListItem),
|
||||
ListItemDropdown(
|
||||
keyValue: "more options",
|
||||
label: S.of(context).more_options,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_allProvidersExpanded = !_allProvidersExpanded;
|
||||
});
|
||||
}),
|
||||
if (_allProvidersExpanded)
|
||||
...widget.buySellViewModel.sortedQuotes.map(quoteListItem)
|
||||
]
|
||||
}),
|
||||
);
|
||||
},
|
||||
))
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
String get _pageTitle => buySellViewModel.mode == BuySellPageMode.buy
|
||||
String get _pageTitle => (widget.buySellViewModel.mode == BuySellPageMode.buy
|
||||
? S.current.buy
|
||||
: S.current.sell + " " + (buySellViewModel.cryptoCurrency.fullName ?? "");
|
||||
: S.current.sell) + " " + (widget.buySellViewModel.cryptoCurrency.fullName ?? "");
|
||||
|
||||
ListItem quoteListItem(Quote quote) => ListItemRegularRow(
|
||||
keyValue: quote.provider.title,
|
||||
label: quote.rampName ?? quote.provider.title,
|
||||
secondaryLabel: quote.rampName != null? quote.provider.title : null,
|
||||
subtitle: quote.badges.isEmpty ? null : quote.badges.join(" - "),
|
||||
subtitleColor: Theme.of(context).colorScheme.primary,
|
||||
iconPath: quote.darkIconPath,
|
||||
onTap: (){
|
||||
widget.buySellViewModel.changeOption(quote);
|
||||
navigateToConfirmation(context);
|
||||
},
|
||||
leadingIconSize: 32,
|
||||
trailingWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(widget.buySellViewModel.amountForQuote(quote).toStringWithSymbol(fractionalDigits: 8)),
|
||||
Text("= ${widget.buySellViewModel.fiatAmountForQuote(quote).toStringWithSymbol(fractionalDigits: 2, trimZeros: false)}", style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant))
|
||||
|
||||
],));
|
||||
|
||||
void navigateToConfirmation(BuildContext context) {
|
||||
|
||||
final page = BuySellConfirmationPage(buySellViewModel: widget.buySellViewModel);
|
||||
Navigator.of(context).push(CupertinoPageRoute(builder: (context)=>Material(color: Colors.transparent,child: page,)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import 'package:cake_wallet/buy/sell_buy_states.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class BuySellRedirectingPage extends StatefulWidget {
|
||||
const BuySellRedirectingPage({super.key, required this.buySellViewModel});
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
|
||||
@override
|
||||
State<BuySellRedirectingPage> createState() => _BuySellRedirectingPageState();
|
||||
}
|
||||
|
||||
class _BuySellRedirectingPageState extends State<BuySellRedirectingPage> {
|
||||
bool _hasRedirected = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.delayed(Duration(seconds: 2)).then((_) async {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => widget.buySellViewModel.launchTrade(context));
|
||||
setState(() {
|
||||
_hasRedirected = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final showExitButton =
|
||||
_hasRedirected || widget.buySellViewModel.buySellQuotState is BuySellQuotFailed;
|
||||
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox.shrink(),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
if (widget.buySellViewModel.buySellQuotState is BuySellQuotFailed) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 24,
|
||||
children: [
|
||||
Icon(Icons.warning_amber_outlined, size: 48),
|
||||
Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).could_not_proceed_with_trade,
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text((widget.buySellViewModel.buySellQuotState as BuySellQuotFailed)
|
||||
.errorMessage ??
|
||||
S.of(context).please_try_again_later)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 24,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: widget.buySellViewModel.selectedQuote!.darkIconPath,
|
||||
width: 64,
|
||||
height: 64,
|
||||
),
|
||||
Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text(
|
||||
"${S.of(context).connecting_you_to} ${widget.buySellViewModel.selectedQuote!.provider.title}...",
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(
|
||||
"${widget.buySellViewModel.fiatAmount} ${widget.buySellViewModel.fiatCurrency} → ${widget.buySellViewModel.amountForQuote(widget.buySellViewModel.selectedQuote!).toStringWithSymbol(fractionalDigits: 8)}")
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
showExitButton
|
||||
? Padding(
|
||||
padding: EdgeInsets.all(18),
|
||||
child: NewPrimaryButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
text: S.of(context).close,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
)
|
||||
: SizedBox.shrink()
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,15 @@ class BuySellSelectorModal extends StatelessWidget {
|
||||
|
||||
void openBuySellPage(BuildContext context, BuySellPageMode mode) {
|
||||
Navigator.of(context).pop();
|
||||
showModalBottomSheet(useSafeArea:true, isScrollControlled: true, context: context, builder: (modalContext)=>ModalNavigator(rootPage: getIt.get<NewBuySellAmountPage>(param1: mode), parentContext: context,));
|
||||
final page = getIt.get<NewBuySellAmountPage>(param1: mode);
|
||||
showModalBottomSheet(
|
||||
useSafeArea: true,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (modalContext) => ModalNavigator(
|
||||
rootPage: page,
|
||||
parentContext: context,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class ModalTopBar extends StatelessWidget {
|
||||
this.onTrailingPressed=nothing,
|
||||
this.leadingIcon,
|
||||
this.trailingIcon,
|
||||
this.bottomText,
|
||||
this.padding,
|
||||
this.leadingWidget,
|
||||
this.trailingWidget}) {
|
||||
@@ -25,6 +26,7 @@ class ModalTopBar extends StatelessWidget {
|
||||
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final String? bottomText;
|
||||
final VoidCallback onLeadingPressed;
|
||||
final VoidCallback onTrailingPressed;
|
||||
final Widget? leadingIcon;
|
||||
@@ -37,25 +39,32 @@ class ModalTopBar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasBottomText =bottomText != null && bottomText!.isNotEmpty;
|
||||
return Padding(
|
||||
padding: padding??EdgeInsets.all(18),
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
Positioned(
|
||||
top: 6,
|
||||
top: hasBottomText ? -4 : 6,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Text(
|
||||
title,
|
||||
key: ValueKey(title),
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Text(
|
||||
title,
|
||||
key: ValueKey(title),
|
||||
style: TextStyle(fontSize: hasBottomText ? 16 : 18, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
if(hasBottomText)
|
||||
Text(bottomText!, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),)
|
||||
],
|
||||
),
|
||||
if (subtitle != null && subtitle!.isNotEmpty)
|
||||
Text(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:vector_graphics/vector_graphics.dart';
|
||||
@@ -86,7 +87,7 @@ class CakeImageWidget extends StatelessWidget {
|
||||
allowDrawingOutsideViewBox: allowDrawingOutsideViewBox ?? false,
|
||||
fit: fit ?? BoxFit.contain,
|
||||
placeholderBuilder: (_) {
|
||||
return loadingWidget ?? const Center(child: CircularProgressIndicator());
|
||||
return loadingWidget ?? SizedBox(height: height, width: width, child: Center(child: CupertinoActivityIndicator()));
|
||||
},
|
||||
errorBuilder: (_, __, ___) => _buildErrorWidget(context),
|
||||
)
|
||||
@@ -99,7 +100,7 @@ class CakeImageWidget extends StatelessWidget {
|
||||
filterQuality: filterQuality ?? FilterQuality.medium,
|
||||
loadingBuilder: (_, Widget child, ImageChunkEvent? progress) {
|
||||
if (progress == null) return child;
|
||||
return loadingWidget ?? const Center(child: CircularProgressIndicator());
|
||||
return loadingWidget ?? SizedBox(height: height, width: width, child: Center(child: CupertinoActivityIndicator()));
|
||||
},
|
||||
errorBuilder: (_, __, ___) => _buildErrorWidget(context),
|
||||
);
|
||||
|
||||
@@ -29,31 +29,29 @@ class ListItemDropdownWidget extends StatelessWidget {
|
||||
return ListItemStyleWrapper(
|
||||
isFirstInSection: isFirstInSection,
|
||||
isLastInSection: isLastInSection,
|
||||
onTap: onTap,
|
||||
builder: (context, textStyle, labelStyle) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: textStyle),
|
||||
Row(
|
||||
children: [
|
||||
if (trailingText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Text(
|
||||
trailingText!,
|
||||
style: labelStyle,
|
||||
),
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: textStyle),
|
||||
Row(
|
||||
children: [
|
||||
if (trailingText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Text(
|
||||
trailingText!,
|
||||
style: labelStyle,
|
||||
),
|
||||
Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -23,22 +23,25 @@ class ListItemRegularRowWidget extends StatelessWidget {
|
||||
this.foregroundColor,
|
||||
this.trailingIconSize,
|
||||
this.bottomWidget,
|
||||
this.subtitleColor,
|
||||
this.trailingWidget,
|
||||
this.copyableText,
|
||||
this.leadingIconErrorWidget,
|
||||
this.leadingIconSize,
|
||||
this.badgeIconSize,
|
||||
this.iconColor});
|
||||
this.iconColor, this.secondaryLabel});
|
||||
|
||||
final String keyValue;
|
||||
final String label;
|
||||
final String? subtitle;
|
||||
final String? trailingText;
|
||||
final String? secondaryLabel;
|
||||
final String? iconPath;
|
||||
final String? badgeIconPath;
|
||||
final VoidCallback? onTap;
|
||||
final bool isFirstInSection;
|
||||
final bool isLastInSection;
|
||||
final Color? subtitleColor;
|
||||
final bool showArrow;
|
||||
final String? trailingIconPath;
|
||||
final Widget? bottomWidget;
|
||||
@@ -131,14 +134,21 @@ class ListItemRegularRowWidget extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
)
|
||||
else
|
||||
Text(label,
|
||||
style: foregroundColor == null
|
||||
? textStyle
|
||||
: textStyle.copyWith(color: foregroundColor)),
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(label,
|
||||
style: foregroundColor == null
|
||||
? textStyle
|
||||
: textStyle.copyWith(color: foregroundColor)),
|
||||
if(secondaryLabel != null)
|
||||
Text(secondaryLabel!, style: textStyle.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),)
|
||||
],
|
||||
),
|
||||
if (subtitle != null)
|
||||
Text(
|
||||
subtitle!,
|
||||
style: labelStyle.copyWith(fontSize: 12),
|
||||
style: subtitleColor == null ? labelStyle.copyWith(fontSize: 12) : labelStyle.copyWith(fontSize: 12, color: subtitleColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -98,6 +98,8 @@ class NewListSections extends StatelessWidget {
|
||||
iconPath: item.iconPath,
|
||||
badgeIconPath: item.badgeIconPath,
|
||||
trailingIconPath: item.trailingIconPath,
|
||||
secondaryLabel: item.secondaryLabel,
|
||||
subtitleColor: item.subtitleColor,
|
||||
onTap: tapHandlers[item.keyValue] ?? item.onTap,
|
||||
isFirstInSection: isFirst,
|
||||
isLastInSection: isLast,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/buy_quote.dart';
|
||||
@@ -13,8 +14,9 @@ import 'package:cake_wallet/entities/provider_types.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/buy_sell_page.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
@@ -28,7 +30,7 @@ class BuySellViewModel = BuySellViewModelBase with _$BuySellViewModel;
|
||||
abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with Store {
|
||||
BuySellViewModelBase(
|
||||
AppStore appStore,
|
||||
{required this.mode}
|
||||
{required this.mode, required this.fiatConversionStore}
|
||||
) : _cryptoAmount = '',
|
||||
fiatAmount = '',
|
||||
cryptoCurrencyAddress = '',
|
||||
@@ -60,6 +62,8 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
|
||||
late Timer bestRateSync;
|
||||
|
||||
final FiatConversionStore fiatConversionStore;
|
||||
|
||||
List<BuyProvider> get availableBuyProviders {
|
||||
final providerTypes = ProvidersHelper.getAvailableBuyProviderTypes();
|
||||
return providerTypes
|
||||
@@ -153,7 +157,28 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
@observable
|
||||
bool skipIsReadyToTradeReaction = false;
|
||||
|
||||
@computed
|
||||
String? get maxFiatAmount {
|
||||
if ((sortedQuotes.isEmpty && sortedRecommendedQuotes.isEmpty) ||
|
||||
buySellQuotState is! BuySellQuotLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final allQuotes = sortedRecommendedQuotes.followedBy(sortedQuotes);
|
||||
|
||||
final maxAmount = allQuotes.fold<double>(0.0, (current, item) {
|
||||
final limitMax = item.limits?.max?.toDouble() ?? 0.0;
|
||||
return max(current, limitMax);
|
||||
});
|
||||
|
||||
return maxAmount.toStringAsFixed(2);
|
||||
}
|
||||
|
||||
Money amountForQuote(Quote quote) => Money.parse(double.parse(fiatAmount)/quote.rate, cryptoCurrency);
|
||||
|
||||
Money fiatAmountForQuote(Quote quote) {
|
||||
return Money.parse((fiatConversionStore.prices[cryptoCurrency]! * double.parse(amountForQuote(quote).toString())).toStringAsFixed(2), fiatCurrency);
|
||||
}
|
||||
|
||||
// based on usd values, should have roughly equal worth (was done with ai though so it's subject to correction)
|
||||
static final Map<FiatCurrency, List<String>> _defaultAmountsMap = {
|
||||
@@ -252,6 +277,13 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
isCryptoCurrencyAddressEnabled = !(cryptoCurrency == wallet.currency);
|
||||
}
|
||||
|
||||
|
||||
@computed
|
||||
Iterable<CryptoCurrency> get activeWalletCurrencies => wallet.balance.keys;
|
||||
|
||||
@computed
|
||||
bool get hasMultipleCurrencies => activeWalletCurrencies.length > 1;
|
||||
|
||||
@action
|
||||
void changeCryptoCurrencyAddress(String address) => cryptoCurrencyAddress = address;
|
||||
|
||||
@@ -401,6 +433,9 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
cryptoCurrencyAddress = _getInitialCryptoCurrencyAddress();
|
||||
paymentMethodState = InitialPaymentMethod();
|
||||
buySellQuotState = InitialBuySellQuotState();
|
||||
sortedRecommendedQuotes.clear();
|
||||
sortedQuotes.clear();
|
||||
paymentMethods.clear();
|
||||
await _getAvailablePaymentTypes();
|
||||
await calculateBestRate();
|
||||
}
|
||||
@@ -458,16 +493,25 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
|
||||
final List<BuyProvider> validProviders = providerList.where((provider) {
|
||||
if (mode == BuySellPageMode.buy) {
|
||||
return provider.supportedCryptoList
|
||||
.any((pair) => pair.from == cryptoCurrency && pair.to == fiatCurrency);
|
||||
return provider.supportedCryptoList.any((pair) =>
|
||||
pair.from.symbol == cryptoCurrency.symbol &&
|
||||
pair.from.tag == cryptoCurrency.tag &&
|
||||
pair.to.symbol == fiatCurrency.symbol &&
|
||||
pair.to.tag == fiatCurrency.tag
|
||||
);
|
||||
} else {
|
||||
return provider.supportedFiatList
|
||||
.any((pair) => pair.from == fiatCurrency && pair.to == cryptoCurrency);
|
||||
return provider.supportedFiatList.any((pair) =>
|
||||
pair.from.symbol == fiatCurrency.symbol &&
|
||||
pair.from.tag == fiatCurrency.tag &&
|
||||
pair.to.symbol == cryptoCurrency.symbol &&
|
||||
pair.to.tag == cryptoCurrency.tag
|
||||
);
|
||||
}
|
||||
}).toList();
|
||||
|
||||
|
||||
if (validProviders.isEmpty) {
|
||||
buySellQuotState = BuySellQuotFailed();
|
||||
buySellQuotState = BuySellQuotFailed(errorMessage: "Couldn't find a provider that supports ${cryptoCurrency.fullName}.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user