mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
fix ui edge cases (#3109)
* wrap navbar in observer to account for disabling pages * always display fiat amount with 2 decimals on confirm sheet * don't show limit popup if max limit is 0 (this signifies the providers didn't load yet) * fix deposit amount getting set to "ALL" in race condition * disable showing hardware wallets on l2 wallet selector * fix reset in account customizer if designs weren't loaded properly * add withDecimals string extension * add fallback on failed display amount parse
This commit is contained in:
@@ -17,6 +17,26 @@ extension MaxDecimals on String {
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
String withDecimals(int decimals) {
|
||||
var parts = split(".");
|
||||
|
||||
if (parts.length > 2) {
|
||||
parts = [parts.first, parts.sublist(1).join("")];
|
||||
}
|
||||
|
||||
if (parts.length == 1) {
|
||||
parts.add("");
|
||||
}
|
||||
|
||||
if (parts[1].length > decimals) {
|
||||
parts[1] = parts[1].substring(0, decimals);
|
||||
} else {
|
||||
parts[1] = parts[1].padRight(decimals, '0');
|
||||
}
|
||||
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
|
||||
/// Format a stringified number to a localized representation
|
||||
/// 1.000.000,00 in de_DE
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
|
||||
class AmountParsingProxy {
|
||||
final BitcoinAmountDisplayMode displayMode;
|
||||
@@ -19,11 +20,19 @@ class AmountParsingProxy {
|
||||
|
||||
/// [getCryptoOutputAmount] turns the input [amount] into the preferred representation of [cryptoCurrency]
|
||||
String getDisplayCryptoAmount(String amount, CryptoCurrency cryptoCurrency) {
|
||||
if (useSatoshi(cryptoCurrency) && amount.isNotEmpty) {
|
||||
return cryptoCurrency.parseAmount(amount.withMaxDecimals(cryptoCurrency.decimals)).toString();
|
||||
|
||||
try {
|
||||
if (useSatoshi(cryptoCurrency) && amount.isNotEmpty) {
|
||||
return cryptoCurrency.parseAmount(amount.withMaxDecimals(cryptoCurrency.decimals)).toString();
|
||||
}
|
||||
|
||||
return amount.withMaxDecimals(cryptoCurrency.decimals);
|
||||
|
||||
} catch(_) {
|
||||
printV("failed to parse amount $amount for currency ${cryptoCurrency.title}, falling back to showing unparsed");
|
||||
return amount;
|
||||
}
|
||||
|
||||
return amount.withMaxDecimals(cryptoCurrency.decimals);
|
||||
}
|
||||
|
||||
/// [getCryptoStringRepresentation] turns the input [amount] into the preferred representation of [cryptoCurrency]
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dar
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:cw_core/balance_card_style_settings.dart';
|
||||
import 'package:cw_core/card_design.dart';
|
||||
import 'package:cw_core/generate_name.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
@@ -381,7 +382,9 @@ class _AccountCustomizerState extends State<AccountCustomizer> {
|
||||
selected: true,
|
||||
designSwitchDuration: Duration(milliseconds: 200),
|
||||
width: cardWidth,
|
||||
design: widget.dashboardViewModel.cardDesigns[i],
|
||||
design: i >= widget.dashboardViewModel.cardDesigns.length
|
||||
? CardDesign.genericDefault
|
||||
: widget.dashboardViewModel.cardDesigns[i],
|
||||
),
|
||||
order: i,
|
||||
accountListItem: accounts[i]));
|
||||
|
||||
@@ -292,7 +292,10 @@ class _NewSwapPageState extends State<NewSwapPage> {
|
||||
|
||||
_depositAmountDebounce.run(() {
|
||||
widget.exchangeViewModel.calculateBestRate();
|
||||
widget.exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
|
||||
if (depositAmountController.text != widget.exchangeViewModel.depositAmount &&
|
||||
depositAmountController.text != S.of(context).all) {
|
||||
widget.exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
|
||||
}
|
||||
widget.exchangeViewModel.isReceiveAmountEntered = false;
|
||||
widget.exchangeViewModel.isFixedRateMode = false;
|
||||
if (!receiveKey.currentState!.amountFocusNode.hasFocus) {
|
||||
|
||||
@@ -57,7 +57,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
if (widget.showOtherWallets) {
|
||||
() async {
|
||||
items.addAll((await WalletInfo.getAll())
|
||||
.where((item) => item.type == widget.sendViewModel.walletType));
|
||||
.where((item) => item.type == widget.sendViewModel.walletType && item.hardwareWalletType == null));
|
||||
items.sort((a, b) {
|
||||
if (a.name == widget.sendViewModel.wallet.name)
|
||||
return -1;
|
||||
|
||||
@@ -154,8 +154,10 @@ class SendTransactionDetails extends StatelessWidget {
|
||||
String sumStr<T>(List<T> list, double Function(T) picker) =>
|
||||
sumBy(list, picker).toString();
|
||||
|
||||
String sumWithUnit<T>(List<T> list, double Function(T) picker, String unit) =>
|
||||
"${sumStr(list, picker)} $unit";
|
||||
String sumWithUnit<T>(List<T> list, double Function(T) picker, String unit, {int? decimals}) {
|
||||
final str = sumStr(list, picker);
|
||||
return "${decimals == null ? str : str.withDecimals(decimals)} $unit";
|
||||
}
|
||||
|
||||
|
||||
Widget _buildMainContent(BuildContext context) {
|
||||
@@ -198,6 +200,7 @@ class SendTransactionDetails extends StatelessWidget {
|
||||
sendViewModel.outputs,
|
||||
(o) => double.tryParse(o.fiatAmount.replaceAll(",", "")) ?? 0,
|
||||
sendViewModel.fiatCurrency.title,
|
||||
decimals:2
|
||||
)
|
||||
: sendViewModel.pendingTransactionFiatAmountFormatted;
|
||||
|
||||
@@ -206,6 +209,7 @@ class SendTransactionDetails extends StatelessWidget {
|
||||
sendViewModel.outputs,
|
||||
(o) => double.tryParse(o.estimatedFeeFiatAmount.replaceAll(",", "")) ?? 0,
|
||||
sendViewModel.fiatCurrency.title,
|
||||
decimals:2
|
||||
)
|
||||
: sendViewModel.pendingTransactionFeeFiatAmountFormatted;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class SwapLimitPopup extends StatelessWidget {
|
||||
final double? amount = double.tryParse(exchangeViewModel.depositAmountCanonical);
|
||||
final max = exchangeViewModel.limits.max ?? double.infinity;
|
||||
final min = exchangeViewModel.limits.min ?? 0;
|
||||
final tooLarge = amount != null && amount > max;
|
||||
final tooLarge = amount != null && max != 0 && amount > max;
|
||||
final tooSmall = amount != null && min != 0 && amount < min;
|
||||
final show = amount != null && (tooLarge || tooSmall);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:cake_wallet/entities/new_main_actions.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
@@ -123,124 +124,126 @@ class _NEWNewMainNavBarState extends State<NewMainNavBar> {
|
||||
final activeColor = theme.colorScheme.onSurface;
|
||||
final inactiveColor = theme.colorScheme.primary;
|
||||
|
||||
final visibleActions = NewMainActions.all
|
||||
.where(
|
||||
(action) => action.canShow?.call(widget.dashboardViewModel) ?? true)
|
||||
.toList();
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final visibleActions = NewMainActions.all
|
||||
.where((action) => action.canShow?.call(widget.dashboardViewModel) ?? true)
|
||||
.toList();
|
||||
|
||||
final pillWidth = _estimatePillWidthForAction(
|
||||
context, visibleActions[widget.selectedIndex],
|
||||
color: activeColor);
|
||||
final pillWidth = _estimatePillWidthForAction(context, visibleActions[widget.selectedIndex],
|
||||
color: activeColor);
|
||||
|
||||
final barWidth = calcBarWidth(pillWidth, visibleActions.length);
|
||||
final barWidth = calcBarWidth(pillWidth, visibleActions.length);
|
||||
|
||||
final currentAction = visibleActions[widget.selectedIndex];
|
||||
final currentAction = visibleActions[widget.selectedIndex];
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: SafeArea(
|
||||
bottom: !(Platform.isIOS),
|
||||
top: false,
|
||||
child: Padding(
|
||||
// tux PLEASE consult me (malik) before removing this padding.
|
||||
padding: EdgeInsets.only(bottom: NewMainNavBar.barBottomPadding),
|
||||
child: AnimatedContainer(
|
||||
duration: barResizeDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
width: barWidth,
|
||||
child: ClipRSuperellipse(
|
||||
borderRadius: BorderRadius.circular(barBorderRadius),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
|
||||
child: Container(
|
||||
height: NewMainNavBar.barHeight,
|
||||
decoration: ShapeDecoration(
|
||||
color: backgroundColor,
|
||||
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(barBorderRadius),
|
||||
side: const BorderSide(color: Color(0x14FFFFFF), width: 1),
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: SafeArea(
|
||||
bottom: !(Platform.isIOS),
|
||||
top: false,
|
||||
child: Padding(
|
||||
// tux PLEASE consult me (malik) before removing this padding.
|
||||
padding: EdgeInsets.only(bottom: NewMainNavBar.barBottomPadding),
|
||||
child: AnimatedContainer(
|
||||
duration: barResizeDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
width: barWidth,
|
||||
child: ClipRSuperellipse(
|
||||
borderRadius: BorderRadius.circular(barBorderRadius),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
|
||||
child: Container(
|
||||
height: NewMainNavBar.barHeight,
|
||||
decoration: ShapeDecoration(
|
||||
color: backgroundColor,
|
||||
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(barBorderRadius),
|
||||
side: const BorderSide(color: Color(0x14FFFFFF), width: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: barHorizontalPadding),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
AnimatedPill(
|
||||
left: calcLeft(widget.selectedIndex, pillWidth),
|
||||
pillColor: pillColor,
|
||||
currentAction: currentAction,
|
||||
pillIconHeight: pillIconHeight,
|
||||
pillIconWidth: pillIconWidth,
|
||||
pillIconSpacing: pillIconSpacing,
|
||||
pillBorderRadius: pillBorderRadius,
|
||||
contentColor: activeColor,
|
||||
estimateWidthForAction: pillWidth,
|
||||
pillTextStyle: pillTextStyle,
|
||||
pillMoveDuration: pillMoveDuration,
|
||||
pillResizeDuration: pillResizeDuration,
|
||||
),
|
||||
for (int i = 0; i < visibleActions.length; i++)
|
||||
AnimatedPositioned(
|
||||
duration: pillResizeDuration,
|
||||
width: iconBoxWidth,
|
||||
left: calcLeft(i, pillWidth)+((i == widget.selectedIndex) ? iconHorizontalPadding/100 : 0),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: InkWell(
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
splashColor: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(pillBorderRadius),
|
||||
onTap: () => _onItemTap(i),
|
||||
child: AnimatedContainer(
|
||||
duration: _firstFrame
|
||||
? Duration.zero
|
||||
: inactiveIconMoveDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
width:
|
||||
i == widget.selectedIndex ? pillWidth : iconBoxWidth,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedAlign(
|
||||
duration: inactiveIconFadeDuration,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: barHorizontalPadding),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
AnimatedPill(
|
||||
left: calcLeft(widget.selectedIndex, pillWidth),
|
||||
pillColor: pillColor,
|
||||
currentAction: currentAction,
|
||||
pillIconHeight: pillIconHeight,
|
||||
pillIconWidth: pillIconWidth,
|
||||
pillIconSpacing: pillIconSpacing,
|
||||
pillBorderRadius: pillBorderRadius,
|
||||
contentColor: activeColor,
|
||||
estimateWidthForAction: pillWidth,
|
||||
pillTextStyle: pillTextStyle,
|
||||
pillMoveDuration: pillMoveDuration,
|
||||
pillResizeDuration: pillResizeDuration,
|
||||
),
|
||||
for (int i = 0; i < visibleActions.length; i++)
|
||||
AnimatedPositioned(
|
||||
duration: pillResizeDuration,
|
||||
width: iconBoxWidth,
|
||||
left: calcLeft(i, pillWidth)+((i == widget.selectedIndex) ? iconHorizontalPadding/100 : 0),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: InkWell(
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
splashColor: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(pillBorderRadius),
|
||||
onTap: () => _onItemTap(i),
|
||||
child: AnimatedContainer(
|
||||
duration: _firstFrame
|
||||
? Duration.zero
|
||||
: inactiveIconMoveDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
width:
|
||||
i == widget.selectedIndex ? pillWidth : iconBoxWidth,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedScale(
|
||||
duration: inactiveIconAppearDuration,
|
||||
child: AnimatedAlign(
|
||||
duration: inactiveIconFadeDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
scale: (i == widget.selectedIndex) ? 0.857 : 1.0,
|
||||
child: TweenAnimationBuilder<Color?>(
|
||||
tween: ColorTween(
|
||||
begin: (i == widget.selectedIndex) ? inactiveColor : activeColor,
|
||||
end: (i==widget.selectedIndex) ? activeColor : inactiveColor,
|
||||
),
|
||||
duration: iconColorChangeDuration,
|
||||
builder: (context, value, child) {
|
||||
return Container(
|
||||
height: NewMainNavBar.barHeight,
|
||||
child: CakeImageWidget(imageUrl:
|
||||
visibleActions[i].image,
|
||||
width: iconWidth,
|
||||
height: iconHeight,
|
||||
//fit: BoxFit.scaleDown,
|
||||
colorFilter: ColorFilter.mode(
|
||||
value ?? inactiveColor,
|
||||
BlendMode.srcIn,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedScale(
|
||||
duration: inactiveIconAppearDuration,
|
||||
curve: Curves.easeOutCubic,
|
||||
scale: (i == widget.selectedIndex) ? 0.857 : 1.0,
|
||||
child: TweenAnimationBuilder<Color?>(
|
||||
tween: ColorTween(
|
||||
begin: (i == widget.selectedIndex) ? inactiveColor : activeColor,
|
||||
end: (i==widget.selectedIndex) ? activeColor : inactiveColor,
|
||||
),
|
||||
duration: iconColorChangeDuration,
|
||||
builder: (context, value, child) {
|
||||
return Container(
|
||||
height: NewMainNavBar.barHeight,
|
||||
child: CakeImageWidget(imageUrl:
|
||||
visibleActions[i].image,
|
||||
width: iconWidth,
|
||||
height: iconHeight,
|
||||
//fit: BoxFit.scaleDown,
|
||||
colorFilter: ColorFilter.mode(
|
||||
value ?? inactiveColor,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user