This commit is contained in:
Robert Malikowski
2026-07-01 16:49:19 +02:00
parent a2a0500213
commit afbc6db066
6 changed files with 255 additions and 75 deletions
+3 -2
View File
@@ -1494,10 +1494,11 @@ Future<void> setup({
getIt.registerFactory(() => BuyAmountViewModel());
getIt.registerFactory(() => BuySellViewModel(getIt.get<AppStore>()));
getIt.registerFactoryParam<BuySellViewModel, BuySellPageMode, void>(
(mode, _) => BuySellViewModel(mode: mode, getIt.get<AppStore>()));
getIt.registerFactoryParam<NewBuySellAmountPage, BuySellPageMode, void>((mode, _) =>
NewBuySellAmountPage(mode: mode, buySellViewModel: getIt.get<BuySellViewModel>()));
NewBuySellAmountPage(buySellViewModel: getIt.get<BuySellViewModel>(param1: mode)));
getIt.registerFactory(() => BuySellPage(getIt.get<BuySellViewModel>()));
@@ -1,16 +1,19 @@
import 'package:cake_wallet/entities/fiat_currency.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/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/themes/core/theme_extension.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/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class NewBuySellAmountPage extends StatefulWidget {
const NewBuySellAmountPage({super.key, required this.mode, required this.buySellViewModel});
const NewBuySellAmountPage({super.key, required this.buySellViewModel});
final BuySellPageMode mode;
final BuySellViewModel buySellViewModel;
@override
@@ -19,6 +22,7 @@ class NewBuySellAmountPage extends StatefulWidget {
class _NewBuySellAmountPageState extends State<NewBuySellAmountPage> {
bool _customAmountMode = false;
final customInputController = TextEditingController();
@override
Widget build(BuildContext context) {
@@ -40,22 +44,37 @@ class _NewBuySellAmountPageState extends State<NewBuySellAmountPage> {
ModalTopBar(
title: _pageTitle,
leadingIcon: Icon(Icons.close),
onLeadingPressed: Navigator.of(context).pop,
onLeadingPressed: Navigator.of(context, rootNavigator: true).pop,
),
Expanded(
child: BuySellDefaultAmountSelector(
defaultAmounts: widget.buySellViewModel.defaultAmounts,
currency: widget.buySellViewModel.fiatCurrency,
mode: widget.mode,
onSelected: (amount) {
if (amount == null) {
setState(() {
_customAmountMode = true;
});
} else {
widget.buySellViewModel.changeFiatAmount(amount: amount);
}
},
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);
}
},
),
))
],
),
@@ -63,7 +82,7 @@ class _NewBuySellAmountPageState extends State<NewBuySellAmountPage> {
);
}
String get _pageTitle => widget.mode == BuySellPageMode.buy
String get _pageTitle => widget.buySellViewModel.mode == BuySellPageMode.buy
? S.current.buy
: S.current.sell +
((widget.buySellViewModel.cryptoCurrencies.length == 1)
@@ -71,6 +90,62 @@ class _NewBuySellAmountPageState extends State<NewBuySellAmountPage> {
: "");
}
class BuySellCustomAmountInput extends StatelessWidget {
const BuySellCustomAmountInput(
{super.key,
required this.fiatCurrency,
required this.cryptoCurrency,
required this.cryptoAmount,
required this.controller,
required this.onContinuePressed,
required this.onChanged});
final FiatCurrency fiatCurrency;
final CryptoCurrency cryptoCurrency;
final String cryptoAmount;
final TextEditingController controller;
final VoidCallback onContinuePressed;
final Function(String) onChanged;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox.shrink(),
Column(
spacing: 8,
children: [
FloatingAmountInput(
currency: fiatCurrency,
controller: controller,
onChanged: onChanged,
),
Opacity(
opacity: cryptoAmount.isEmpty ? 0 : 1,
child: Text(
"≈ ${cryptoAmount} ${cryptoCurrency.symbol}",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
)
],
),
Padding(
padding: const EdgeInsets.all(18.0),
child: NewPrimaryButton(
onPressed: onContinuePressed,
text: S.of(context).continue_text,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary),
)
],
);
}
}
class BuySellDefaultAmountSelector extends StatelessWidget {
const BuySellDefaultAmountSelector(
{super.key,
@@ -100,6 +175,7 @@ class BuySellDefaultAmountSelector extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
// +1 for "custom" option
itemCount: defaultAmounts.length + 1,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
@@ -0,0 +1,45 @@
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/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
import 'package:flutter/material.dart';
class BuySellProviderPage extends StatelessWidget {
const BuySellProviderPage({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: Column(
))
],
)),
);
}
String get _pageTitle => buySellViewModel.mode == BuySellPageMode.buy
? S.current.buy
: S.current.sell + " " + (buySellViewModel.cryptoCurrency.fullName ?? "");
}
@@ -0,0 +1,92 @@
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class FloatingAmountInput extends StatefulWidget {
const FloatingAmountInput({super.key, required this.currency, required this.controller, this.focusNode, this.inputFormatters, this.onChanged, this.validator});
final Currency currency;
final TextEditingController controller;
final FocusNode? focusNode;
final List<TextInputFormatter>? inputFormatters;
final Function(String)? onChanged;
final FormFieldValidator<String>? validator;
@override
State<FloatingAmountInput> createState() => _FloatingAmountInputState();
}
class _FloatingAmountInputState extends State<FloatingAmountInput> {
bool _amountFocused = false;
late FocusNode focusNode = widget.focusNode ?? FocusNode();
@override
void initState() {
super.initState();
focusNode.addListener(() => setState(() => _amountFocused = focusNode.hasFocus));
}
@override
Widget build(BuildContext context) {
return Center(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
IntrinsicWidth(
child: TextFormField(
controller: widget.controller,
focusNode: focusNode,
maxLines: 1,
onChanged: widget.onChanged,
autovalidateMode: AutovalidateMode.always,
validator: widget.validator,
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: true,
),
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp(r'^\d*[.,]?\d*$'),
),
],
decoration: InputDecoration(
isDense: true,
isCollapsed: true,
contentPadding: EdgeInsets.zero,
fillColor: Colors.transparent,
hoverColor: Colors.transparent,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
hintText: _amountFocused || widget.controller.text.isNotEmpty
? null
: "0.00",
hintStyle: Theme.of(context).textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
style: Theme.of(context).textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 45,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
const SizedBox(width: 8),
Text(
widget.currency.symbol,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 45,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
);
}
}
+5 -40
View File
@@ -3,6 +3,7 @@ import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.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/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/currency_picker/fiat_currency_picker_sheet.dart';
@@ -412,7 +413,7 @@ class BuySellPage extends BasePage {
borderColor: Theme.of(context).colorScheme.outlineVariant,
onPushPasteButton: (context) async {},
onPushAddressBookButton: (context) async {},
fillColor: buySellViewModel.isBuyAction
fillColor: buySellViewModel.mode == BuySellPageMode.buy
? Theme.of(context).colorScheme.surfaceContainer
: Theme.of(context).colorScheme.surfaceContainerLow,
),
@@ -451,7 +452,7 @@ class BuySellPage extends BasePage {
addressTextFieldValidator: AddressValidator(type: buySellViewModel.cryptoCurrency),
onPushPasteButton: (context) async {},
onPushAddressBookButton: (context) async {},
fillColor: buySellViewModel.isBuyAction
fillColor: buySellViewModel.mode == BuySellPageMode.buy
? Theme.of(context).colorScheme.surfaceContainerLow
: Theme.of(context).colorScheme.surfaceContainer,
useSatoshis: buySellViewModel.useSatoshi,
@@ -461,50 +462,14 @@ class BuySellPage extends BasePage {
if (responsiveLayoutUtil.shouldRenderMobileUI) {
return Observer(
builder: (_) {
if (buySellViewModel.isBuyAction) {
return MobileExchangeCardsSection(
firstExchangeCard: fiatExchangeCard,
secondExchangeCard: cryptoExchangeCard,
onBuyTap: () => null,
onSellTap: () =>
buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
isBuySellOption: true,
);
} else {
return MobileExchangeCardsSection(
firstExchangeCard: cryptoExchangeCard,
secondExchangeCard: fiatExchangeCard,
onBuyTap: () =>
!buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
onSellTap: () => null,
isBuySellOption: true,
);
}
return Placeholder();
},
);
}
return Observer(
builder: (_) {
if (buySellViewModel.isBuyAction) {
return DesktopExchangeCardsSection(
firstExchangeCard: fiatExchangeCard,
secondExchangeCard: cryptoExchangeCard,
onBuyTap: () => null,
onSellTap: () =>
buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
isBuySellOption: true,
);
} else {
return DesktopExchangeCardsSection(
firstExchangeCard: cryptoExchangeCard,
secondExchangeCard: fiatExchangeCard,
onBuyTap: () =>
!buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
onSellTap: () => null,
isBuySellOption: true,
);
}
return Placeholder();
},
);
}
+17 -16
View File
@@ -11,10 +11,13 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/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:cw_core/crypto_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:flutter/cupertino.dart';
import 'package:mobx/mobx.dart';
@@ -25,6 +28,7 @@ class BuySellViewModel = BuySellViewModelBase with _$BuySellViewModel;
abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with Store {
BuySellViewModelBase(
AppStore appStore,
{required this.mode}
) : _cryptoAmount = '',
fiatAmount = '',
cryptoCurrencyAddress = '',
@@ -81,7 +85,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
final formattedFiatAmount = double.tryParse(fiatAmount);
final formattedCryptoAmount = double.tryParse(_cryptoAmount);
return isBuyAction
return mode == BuySellPageMode.buy
? formattedFiatAmount ?? 200.0
: formattedCryptoAmount ?? (cryptoCurrency == CryptoCurrency.btc ? 0.001 : 1);
}
@@ -100,8 +104,8 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
@observable
List<FiatCurrency> fiatCurrencies;
@observable
bool isBuyAction = true;
final BuySellPageMode mode;
@observable
List<BuyProvider> providerList;
@@ -234,11 +238,6 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
_initialize();
}
@action
void changeBuySellAction() {
isBuyAction = !isBuyAction;
_initialize();
}
@action
void changeFiatCurrency({required FiatCurrency currency}) {
@@ -267,16 +266,18 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
}
if (!isReadyToTrade && !isBuySellQuoteFailed) {
_cryptoAmount = S.current.fetching;
_cryptoAmount = "...";
return;
} else if (isBuySellQuoteFailed) {
_cryptoAmount = '';
return;
}
printV(bestRateQuote);
if (bestRateQuote != null) {
final enteredAmount = double.tryParse(fiatAmount.replaceAll(',', '.')) ?? 0;
final amount = enteredAmount / bestRateQuote!.rate;
printV(amount);
_cryptoAmount = amount.toString().withMaxDecimals(cryptoCurrency.decimals);
} else {
@@ -391,7 +392,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
}
void _setProviders() =>
providerList = isBuyAction ? availableBuyProviders : availableSellProviders;
providerList = mode == BuySellPageMode.buy ? availableBuyProviders : availableSellProviders;
Future<void> _initialize() async {
_setProviders();
@@ -420,7 +421,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
paymentMethodState = PaymentMethodLoading();
selectedPaymentMethod = null;
final result = await Future.wait(providerList.map((element) =>
element.getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency, isBuyAction).timeout(
element.getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency, mode == BuySellPageMode.buy).timeout(
Duration(seconds: 10),
onTimeout: () => [],
)));
@@ -456,7 +457,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
buySellQuotState = BuySellQuotLoading();
final List<BuyProvider> validProviders = providerList.where((provider) {
if (isBuyAction) {
if (mode == BuySellPageMode.buy) {
return provider.supportedCryptoList
.any((pair) => pair.from == cryptoCurrency && pair.to == fiatCurrency);
} else {
@@ -476,7 +477,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
fiatCurrency: fiatCurrency,
amount: amount,
paymentType: selectedPaymentMethod?.paymentMethodType,
isBuyAction: isBuyAction,
isBuyAction: mode == BuySellPageMode.buy,
walletAddress: wallet.walletAddresses.address,
customPaymentMethodType: selectedPaymentMethod?.customPaymentMethodType,
)
@@ -498,7 +499,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
return;
}
if (isBuyAction) {
if (mode == BuySellPageMode.buy) {
validQuotes.sort((a, b) => b.payout.compareTo(a.payout));
} else {
validQuotes.sort((a, b) => a.payout.compareTo(b.payout));
@@ -541,7 +542,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
final Quote effectiveBestRateQuote =
sortedRecommendedQuotes.reduce((a, b) {
return isBuyAction ? a.rate < b.rate ? a : b : a.rate > b.rate ? a : b;
return mode == BuySellPageMode.buy ? a.rate < b.rate ? a : b : a.rate > b.rate ? a : b;
});
@@ -563,7 +564,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
context: context,
quote: selectedQuote!,
amount: amount,
isBuyAction: isBuyAction,
isBuyAction: mode == BuySellPageMode.buy,
cryptoCurrencyAddress: cryptoCurrencyAddress,
);
} catch (e) {