fix-<-01-fiat-amounts (#3421)

* fix: handling of "< 0.1" Amounts in send flow

* refactor: clean up imports and restructure classes in `send_page.dart` apply lint
This commit is contained in:
Konstantin Ullrich
2026-07-20 14:19:09 +03:00
committed by GitHub
parent 7dc84e6045
commit 6209e36dfc
3 changed files with 898 additions and 825 deletions
File diff suppressed because it is too large Load Diff
@@ -1,23 +1,24 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:flutter/material.dart';
import "package:cake_wallet/generated/i18n.dart";
import "package:cake_wallet/new-ui/widgets/modern_button.dart";
import "package:cw_core/crypto_amount_format.dart";
import "package:flutter/material.dart";
class FiatAmountBar extends StatelessWidget {
const FiatAmountBar(
{super.key,
required this.fiatInputMode,
required this.onSwitchButtonPressed,
this.onAllButtonPressed,
required this.cryptoAmount,
required this.fiatAmount,
required this.cryptoCurrencySymbol,
required this.fiatCurrencySymbol,
this.allAmount,
this.foregroundElementColor,
this.textColor,
this.allAmountColor,
this.allAmountTextColor});
const FiatAmountBar({
required this.fiatInputMode,
required this.onSwitchButtonPressed,
required this.cryptoAmount,
required this.fiatAmount,
required this.cryptoCurrencySymbol,
required this.fiatCurrencySymbol,
super.key,
this.onAllButtonPressed,
this.allAmount,
this.foregroundElementColor,
this.textColor,
this.allAmountColor,
this.allAmountTextColor,
});
final bool fiatInputMode;
final VoidCallback onSwitchButtonPressed;
@@ -34,42 +35,41 @@ class FiatAmountBar extends StatelessWidget {
final Color? allAmountTextColor;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 8,
children: [
ModernButton.svg(
backgroundColor: foregroundElementColor,
size: 28,
svgPath: "assets/new-ui/switch.svg",
iconSize: 18,
onPressed: onSwitchButtonPressed,
),
GestureDetector(
onTap: onSwitchButtonPressed,
child: Text(
fiatInputMode
? "${cryptoAmount.isEmpty ? "0" : cryptoAmount.withMaxDecimals(8)} $cryptoCurrencySymbol"
: "${fiatAmount.isEmpty ? "0" : fiatAmount} $fiatCurrencySymbol",
style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
),
),
],
),
if (allAmount != null && allAmount!.isNotEmpty)
Widget build(BuildContext context) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 8,
children: [
Text(
"${S.of(context).max}.",
style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
ModernButton.svg(
backgroundColor: foregroundElementColor,
size: 28,
svgPath: "assets/new-ui/switch.svg",
iconSize: 18,
onPressed: onSwitchButtonPressed,
),
Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(999999)),
child: Material(
GestureDetector(
onTap: onSwitchButtonPressed,
child: Text(
fiatInputMode
? "${cryptoAmount.isEmpty ? "0" : cryptoAmount.withMaxDecimals(8)} $cryptoCurrencySymbol"
: "${fiatAmount.isEmpty ? "0" : fiatAmount} $fiatCurrencySymbol",
style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
),
),
],
),
if (allAmount != null && allAmount!.isNotEmpty)
Row(
spacing: 8,
children: [
Text(
"${S.of(context).max}.",
style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
),
Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(999999)),
child: Material(
color: allAmountColor ??
foregroundElementColor ??
Theme.of(context).colorScheme.surfaceContainer,
@@ -78,22 +78,23 @@ class FiatAmountBar extends StatelessWidget {
borderRadius: BorderRadius.circular(99999),
onTap: onAllButtonPressed,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(
formatAmount(allAmount!),
_formatAmount(allAmount!),
style: TextStyle(
color: allAmountTextColor ?? Theme.of(context).colorScheme.primary),
color: allAmountTextColor ?? Theme.of(context).colorScheme.primary,
),
),
),
)),
)
],
)
],
);
}
),
),
),
],
),
],
);
String formatAmount(String amount) {
String _formatAmount(String amount) {
try {
return double.parse(amount).toStringAsPrecision(8).replaceFirst(RegExp(r"\.?0+$"), "");
} catch (e) {
@@ -1,20 +1,20 @@
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
import 'package:cake_wallet/new-ui/widgets/send_page/floating_icon_button.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/utils/decimal_input_formatter.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart";
import "package:cake_wallet/new-ui/widgets/send_page/floating_icon_button.dart";
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
import "package:cake_wallet/utils/decimal_input_formatter.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_mobx/flutter_mobx.dart";
class NewSendAmountInput extends StatefulWidget {
const NewSendAmountInput({
super.key,
required this.currency,
required this.maxDecimals,
required this.hasPicker,
required this.onPickerClicked,
required this.currencyIconPath,
required this.amountController,
super.key,
this.validator,
});
@@ -41,27 +41,28 @@ class _NewSendAmountInputState extends State<NewSendAmountInput> {
}
@override
Widget build(BuildContext context) {
return FormField<String>(
key: formFieldKey,
initialValue: widget.amountController.text,
validator: widget.validator,
builder: (state) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
Widget build(BuildContext context) => FormField<String>(
key: formFieldKey,
initialValue: widget.amountController.text,
validator: widget.validator,
builder: (state) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: widget.hasPicker
? Theme.of(context).colorScheme.surfaceContainerHigh
: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(18)),
color: widget.hasPicker
? Theme.of(context).colorScheme.surfaceContainerHigh
: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(18),
),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.horizontal(left: Radius.circular(18))),
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: const BorderRadius.horizontal(left: Radius.circular(18)),
),
child: Row(
mainAxisSize: MainAxisSize.max,
spacing: 8,
@@ -79,82 +80,87 @@ class _NewSendAmountInputState extends State<NewSendAmountInput> {
],
controller: widget.amountController,
decoration: InputDecoration(
hintText: widget.maxDecimals > 0 ? "0" : "0.00",
hintText: widget.maxDecimals == 0 ? "0" : "0.00",
errorMaxLines: 3,
),
onChanged: state.didChange,
),
),
FloatingIconButton(
iconPath: "assets/new-ui/paste.svg",
onPressed: () async {
final data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && data.text != null) {
final text = data.text!;
widget.amountController.value = TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
);
}
}),
iconPath: "assets/new-ui/paste.svg",
onPressed: () async {
final data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && data.text != null) {
final text = data.text!;
widget.amountController.value = TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
);
}
},
),
],
),
),
),
IntrinsicWidth(
child: Observer(
builder: (_) {
return GestureDetector(
onTap: widget.onPickerClicked,
child: Container(
alignment: Alignment.center,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(18),
bottomRight: Radius.circular(18)),
color: widget.hasPicker
? Theme.of(context).colorScheme.surfaceContainerHigh
: Theme.of(context).colorScheme.surfaceContainer,
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
spacing: 8,
children: [
if (widget.hasPicker && widget.currencyIconPath.isNotEmpty)
TokenImageWidget(
imageUrl: widget.currencyIconPath, size: 24),
Text(widget.currency),
if (widget.hasPicker)
CakeImageWidget(
imageUrl: "assets/new-ui/chooser.svg",
width: 12,
height: 12,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.primary,
BlendMode.srcIn),
)
],
))),
);
},
builder: (_) => GestureDetector(
onTap: widget.onPickerClicked,
child: Container(
alignment: Alignment.center,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(18),
bottomRight: Radius.circular(18),
),
color: widget.hasPicker
? Theme.of(context).colorScheme.surfaceContainerHigh
: Theme.of(context).colorScheme.surfaceContainer,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
spacing: 8,
children: [
if (widget.hasPicker && widget.currencyIconPath.isNotEmpty)
TokenImageWidget(
imageUrl: widget.currencyIconPath,
size: 24,
),
Text(widget.currency),
if (widget.hasPicker)
CakeImageWidget(
imageUrl: "assets/new-ui/chooser.svg",
width: 12,
height: 12,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.primary,
BlendMode.srcIn,
),
),
],
),
),
),
),
),
),
],
)),
if (state.hasError)
Padding(
padding: EdgeInsets.only(top: 6, left: 8),
child: Text(
state.errorText!,
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.error),
),
)
],
),
);
}
),
if (state.hasError)
Padding(
padding: const EdgeInsets.only(top: 6, left: 8),
child: Text(
state.errorText!,
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.error),
),
),
],
),
);
}