improve evm ui (#3129)

* improve evm ui

* fix model generation

* add db schema

* bugfixes

* scrollable fav token modal

* make combined balance the default

* add crypto_full_icons to pubspec

* optimize transaction duplication check

* fix format issue

* add chain icons to swap tiles

* decrease asset tile font weight

* show chain icon in swap currency selector

* move chain icon for tokens

* new trade history row

* ui fix

* fix padding on modal

* remove vec
This commit is contained in:
malik1004x
2026-04-01 11:04:58 +02:00
committed by GitHub
parent ed34c4b3df
commit 915e76d4f9
51 changed files with 710 additions and 195 deletions
+51 -10
View File
@@ -854,6 +854,9 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
.getCryptoSymbol(_selectedCurrency as CryptoCurrency)
: _selectedCurrency.name.toUpperCase();
final chainIconPath = (_selectedCurrency is CryptoCurrency) ? _getCurrencyChainIconPath(_selectedCurrency as CryptoCurrency) : null;
return Column(
spacing: 12,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -939,16 +942,38 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
currencyToShow,
textAlign: TextAlign.center,
),
SizedBox(width:10),
RotatedBox(
quarterTurns: 2,
child: CakeImageWidget(imageUrl:
"assets/new-ui/dropdown_arrow.svg",
width: 4,
height: 4,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
)),
if (chainIconPath != null && chainIconPath.isNotEmpty) ...[
SizedBox(width: 4),
CakeImageWidget(
imageUrl: chainIconPath,
width: 12,
height: 12,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onSurfaceVariant,
BlendMode.srcIn),
),
SizedBox(width: 6)
] else
SizedBox(width: 10),
Container(
width:16,height:16,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(9999999999),
color: Theme.of(context).colorScheme.surfaceContainerHigh
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: RotatedBox(
quarterTurns: 2,
child: CakeImageWidget(imageUrl:
"assets/new-ui/dropdown_arrow.svg",
width: 4,
height: 4,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
)),
),
),
SizedBox(width:4),
],
),
@@ -1255,4 +1280,20 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
if (s.length <= head + tail + 3) return s;
return s.substring(0, head) + '...' + s.substring(s.length - tail);
}
String? _getCurrencyChainIconPath(CryptoCurrency curr) {
try{
if(curr.chainIconPath != null) return curr.chainIconPath!;
if(curr.tag != null) {
final currencyFromTag = CryptoCurrency.fromString(curr.tag!);
if(currencyFromTag.chainIconPath != null) {
return currencyFromTag.chainIconPath!;
}
}
}catch(_){}
return null;
}
}
@@ -13,7 +13,6 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
enum AssetDetailsModalModes { normal, ltcTransparent, ltcPrivate }
@@ -60,6 +59,7 @@ class AssetDetailsModal extends StatelessWidget {
title: "",
trailingIcon: Icon(Icons.close),
onTrailingPressed: Navigator.of(context).pop,
padding: EdgeInsets.only(top:12,right:18),
),
SafeArea(
child: Column(
@@ -89,25 +89,25 @@ class AssetDetailsModal extends StatelessWidget {
fontSize: 28, color: Theme.of(context).colorScheme.onPrimary),
)),
),
if (chainIconPath.isNotEmpty)
Align(
alignment: Alignment.bottomRight,
child: Container(
decoration: ShapeDecoration(
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.black)),
color: Colors.white),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: CakeImageWidget(imageUrl:
chainIconPath,
width: 18,
height: 18,
colorFilter:
ColorFilter.mode(Colors.black, BlendMode.srcIn),
),
)))
// if (chainIconPath.isNotEmpty)
// Align(
// alignment: Alignment.bottomRight,
// child: Container(
// decoration: ShapeDecoration(
// shape: RoundedSuperellipseBorder(
// borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: Colors.black)),
// color: Colors.white),
// child: Padding(
// padding: const EdgeInsets.all(4.0),
// child: CakeImageWidget(imageUrl:
// chainIconPath,
// width: 18,
// height: 18,
// colorFilter:
// ColorFilter.mode(Colors.black, BlendMode.srcIn),
// ),
// )))
],
),
),
@@ -125,14 +125,16 @@ class AssetDetailsModal extends StatelessWidget {
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface),
),
if(asset != null)
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(999999999)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
child: Text(
chainTitle,
asset?.title??"",
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
@@ -141,11 +143,22 @@ class AssetDetailsModal extends StatelessWidget {
],
),
if (subtitle.isNotEmpty)
Text(
subtitle,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
if(chainIconPath.isNotEmpty)
CakeImageWidget(
imageUrl: chainIconPath,
width:16,height:16,colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant,BlendMode.srcIn),
),
Text(
subtitle,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
],
),
],
)
@@ -17,7 +17,7 @@ class AssetTile extends StatelessWidget {
this.title,
this.trailingText,
this.modalMode = AssetDetailsModalModes.normal,
required this.wallet, required this.showSwap});
required this.wallet, required this.showSwap, required this.isFirst, required this.isLast});
final BalanceRecord balance;
final bool showSecondary;
@@ -27,6 +27,8 @@ class AssetTile extends StatelessWidget {
final String? trailingText;
final AssetDetailsModalModes modalMode;
final WalletBase wallet;
final bool isFirst;
final bool isLast;
@override
Widget build(BuildContext context) {
@@ -42,8 +44,8 @@ class AssetTile extends StatelessWidget {
showSwap: showSwap,
asset: balance.asset,
title: title ?? balance.asset.fullName ?? balance.asset.name,
chainTitle: _getChainTitle(),
subtitle: trailingText ?? "",
chainTitle: "",
subtitle: trailingText ?? _getChainTitle(),
amount: showSecondary ? balance.secondAvailableBalance : balance.availableBalance,
currencyTitle: balance.asset.title,
fiatAmount: showSecondary
@@ -57,23 +59,15 @@ class AssetTile extends StatelessWidget {
});
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 6.0),
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Container(
width: double.infinity,
height: 80,
height: 72,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context).colorScheme.surfaceContainerHigh,
Theme.of(context).colorScheme.surfaceContainer,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
borderRadius: BorderRadius.all(Radius.circular(20)),
border: Border.all(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
width: 1,
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.vertical(
top: isFirst ? Radius.circular(18) : Radius.zero,
bottom: isLast ? Radius.circular(18) : Radius.zero,
),
),
child: Padding(
@@ -87,16 +81,16 @@ class AssetTile extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 45,
height: 45,
width: 36,
height: 36,
child: Stack(
children: [
if((iconPath).isNotEmpty)
CakeImageWidget(imageUrl: iconPath)
else
Container(
width: 45,
height: 45,
width: 36,
height: 36,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(99999)),
@@ -107,27 +101,27 @@ class AssetTile extends StatelessWidget {
fontSize: 20, color: Theme.of(context).colorScheme.onPrimary),
)),
),
if (chainIconPath.isNotEmpty)
Align(
alignment: Alignment.bottomRight,
child: Container(
decoration: ShapeDecoration(
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(5),side: BorderSide(color: Colors.black)),
color: Colors.white),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: CakeImageWidget(
imageUrl: chainIconPath,
width: 12,
height: 12,
colorFilter:
ColorFilter.mode(Colors.black, BlendMode.srcIn),
),
)))
// if (chainIconPath.isNotEmpty)
// Align(
// alignment: Alignment.bottomRight,
// child: Container(
// decoration: ShapeDecoration(
// shape: RoundedSuperellipseBorder(
// borderRadius: BorderRadius.circular(5),side: BorderSide(color: Colors.black)),
// color: Colors.white),
// child: Padding(
// padding: const EdgeInsets.all(2.0),
// child: CakeImageWidget(
// imageUrl: chainIconPath,
// width: 12,
// height: 12,
// colorFilter:
// ColorFilter.mode(Colors.black, BlendMode.srcIn),
// ),
// )))
],
)),
SizedBox(width: 8.0),
SizedBox(width: 12.0),
Expanded(
child: Column(
spacing: 4.0,
@@ -139,7 +133,7 @@ class AssetTile extends StatelessWidget {
children: [
Text(
title ?? balance.asset.fullName ?? balance.asset.name,
style: TextStyle(fontWeight: FontWeight.bold),
style: TextStyle(fontWeight: FontWeight.w500),
),
if(trailingText != null)
Text(
@@ -184,9 +178,9 @@ class AssetTile extends StatelessWidget {
String _getChainTitle() {
try {
return CryptoCurrency.fromString(wallet.currency.tag ??wallet.currency.title).title;
return CryptoCurrency.fromString(wallet.currency.tag ??wallet.currency.title).fullName ?? "";
} catch(e) {
return wallet.currency.title;
return wallet.currency.fullName ?? "";
}
}
@@ -21,11 +21,22 @@ class AssetsSection extends StatelessWidget {
child: Observer(
builder: (context) {
final hasMweb = dashboardViewModel.hasMweb && dashboardViewModel.mwebEnabled;
return ListView.builder(
return ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.zero,
padding: EdgeInsets.symmetric(vertical: 18),
physics: NeverScrollableScrollPhysics(),
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length + (hasMweb ? 1 : 0),
separatorBuilder: (context, index) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Container(
color: Theme.of(context).colorScheme.surfaceContainer,
height:1,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Container(color: Theme.of(context).colorScheme.surfaceContainerHigh,),
),
),
),
itemBuilder: (context, index) {
return Observer(builder: (context) {
if (hasMweb) {
@@ -39,6 +50,8 @@ class AssetsSection extends StatelessWidget {
modalMode: index > 0
? AssetDetailsModalModes.ltcPrivate
: AssetDetailsModalModes.ltcTransparent,
isFirst: index == 0,
isLast: index != 0,
title: index > 0 ? "Litecoin Private" : null,
);
}
@@ -48,6 +61,8 @@ class AssetsSection extends StatelessWidget {
showSwap: dashboardViewModel.isEnabledSwapAction,
balance: balance,
wallet: dashboardViewModel.wallet,
isFirst: index == 0,
isLast: index == dashboardViewModel.balanceViewModel.formattedBalances.length-1,
chainIconPath: _getChainIconPath(),
);
});
@@ -69,6 +69,8 @@ class HistorySection extends StatelessWidget {
CryptoCurrency? asset;
if (transaction.additionalInfo["isLightning"] == true)
asset = CryptoCurrency.btcln;
else
asset = item.assetOfTransaction;
return GestureDetector(
onTap: () {
@@ -80,6 +82,8 @@ class HistorySection extends StatelessWidget {
date: DateFormat('HH:mm').format(transaction.date),
amount: item.formattedCryptoAmount,
amountFiat: item.formattedFiatAmount,
hasTokens: item.hasTokens,
chainIconPath: _getChainIconPath(),
roundedBottom: roundedBottom,
roundedTop: roundedTop,
bottomSeparator: !roundedBottom,
@@ -101,6 +105,7 @@ class HistorySection extends StatelessWidget {
child: HistoryTradeTile(
from: tradeFrom!,
to: tradeTo!,
provider: trade.provider,
date: DateFormat('HH:mm').format(item.trade.createdAt!),
amount: trade.amountFormatted(),
receiveAmount: trade.receiveAmountFormatted(),
@@ -171,4 +176,12 @@ class HistorySection extends StatelessWidget {
),
));
}
String _getChainIconPath() {
try {
return CryptoCurrency.fromString(dashboardViewModel.wallet.currency.tag ??dashboardViewModel.wallet.currency.title).chainIconPath!;
} catch(e) {
return dashboardViewModel.wallet.currency.chainIconPath ?? "";
}
}
}
@@ -17,6 +17,8 @@ class HistoryTile extends StatelessWidget {
required this.direction,
required this.pending,
required this.bottomSeparator,
required this.hasTokens,
this.chainIconPath,
this.asset,
});
@@ -27,6 +29,8 @@ class HistoryTile extends StatelessWidget {
final bool roundedTop;
final bool roundedBottom;
final bool bottomSeparator;
final bool hasTokens;
final String? chainIconPath;
final TransactionDirection direction;
final bool pending;
final CryptoCurrency? asset;
@@ -93,6 +97,33 @@ class HistoryTile extends StatelessWidget {
);
}
if(hasTokens) {
return Stack(children: [
Opacity(
opacity: pending ? 0.5 : 1,
child: CakeImageWidget(imageUrl: asset?.iconSvgPath ?? asset?.iconPath ??"", width: 34,
height: 34,),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
decoration: ShapeDecoration(
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadius.circular(5),side: BorderSide(color: Colors.black)),
color: Colors.white),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: CakeImageWidget(
imageUrl: chainIconPath,
width: 12,
height: 12,
colorFilter:
ColorFilter.mode(Colors.black, BlendMode.srcIn),
),
)))
],);
}
return CakeImageWidget(imageUrl:_getDirectionIcon(),
colorFilter: ColorFilter.mode(
direction == TransactionDirection.outgoing
@@ -4,22 +4,32 @@ import 'package:flutter/material.dart';
class HistoryTileBase extends StatelessWidget {
const HistoryTileBase({
super.key,
required this.title,
this.title,
this.titleWidget,
required this.date,
required this.amount,
this.amount,
required this.leadingIcon,
required this.amountFiat,
this.amountFiat,
required this.roundedTop,
required this.roundedBottom,
required this.bottomSeparator,
this.primaryTextColor,
this.asset,
});
this.amountWidget,
this.amountFiatWidget,
}) : assert((title != null || titleWidget != null) && (title == null || titleWidget == null)),
assert(
(amountWidget != null || amount != null) && (amountWidget == null || amount == null)),
assert((amountFiatWidget != null || amountFiat != null) &&
(amountFiatWidget == null || amountFiat == null));
final String title;
final String? title;
final Widget? titleWidget;
final String date;
final String amount;
final String amountFiat;
final String? amount;
final Widget? amountWidget;
final String? amountFiat;
final Widget? amountFiatWidget;
final Widget leadingIcon;
final bool roundedTop;
final bool roundedBottom;
@@ -105,8 +115,10 @@ class HistoryTileBase extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: TextStyle(color: Theme.of(context).colorScheme.onSurface)),
if(title != null)
Text(title!,
style: TextStyle(color: Theme.of(context).colorScheme.onSurface))
else if(titleWidget != null) titleWidget!,
Text(date,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant)),
@@ -115,12 +127,20 @@ class HistoryTileBase extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(amount,
style: TextStyle(
color: primaryTextColor ??
Theme.of(context).colorScheme.onSurface)),
Text(amountFiat, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)),
],
if (amount != null)
Text(amount!,
style: TextStyle(
color: primaryTextColor ??
Theme.of(context).colorScheme.onSurface))
else if (amountWidget != null)
amountWidget!,
if (amountFiat != null)
Text(amountFiat!,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant))
else if (amountFiatWidget != null)
amountFiatWidget!
],
),
],
),
@@ -1,5 +1,8 @@
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_tile_base.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
@@ -14,10 +17,12 @@ class HistoryTradeTile extends StatelessWidget {
required this.bottomSeparator,
required this.from,
required this.to,
required this.swapState});
required this.swapState,
required this.provider});
final CryptoCurrency from;
final CryptoCurrency to;
final ExchangeProviderDescription provider;
final String date;
final String amount;
final String receiveAmount;
@@ -34,7 +39,7 @@ class HistoryTradeTile extends StatelessWidget {
width: 50,
child: Stack(
children: [
Image.asset(_getIconPath(from),
CakeImageWidget(imageUrl: _getIconPath(from),
width: currencyIconSize, height: currencyIconSize),
Positioned(
top: currencyIconSize / 2,
@@ -45,7 +50,7 @@ class HistoryTradeTile extends StatelessWidget {
width: 2,
color: Theme.of(context).colorScheme.surfaceContainer),
shape: BoxShape.circle),
child: Image.asset(_getIconPath(to),
child: CakeImageWidget(imageUrl: _getIconPath(to),
width: currencyIconSize, height: currencyIconSize))),
],
),
@@ -56,12 +61,55 @@ class HistoryTradeTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final fromChainIcon = _getChainIcon(from);
final toChainIcon = _getChainIcon(to);
return HistoryTileBase(
title:
"${from.toString()}${from == CryptoCurrency.btcln ? "-LN" : ""} → ${to.toString()}${to == CryptoCurrency.btcln ? "-LN" : ""}",
// title:
// "${from.toString()}${from == CryptoCurrency.btcln ? "-LN" : ""} → ${to.toString()}${to == CryptoCurrency.btcln ? "-LN" : ""}",
titleWidget: Row(
spacing: 4,
children: [
Text(swapState.title),
CakeImageWidget(imageUrl: provider.image, width: 14, height: 14)
],
),
date: date,
amount: amount,
amountFiat: receiveAmount,
amountFiatWidget: Row(
spacing: 4,
children: [
Text("-", style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)),
Text(amount.withMaxDecimals(8),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500)),
Text(from.title, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)),
if (fromChainIcon.isNotEmpty)
CakeImageWidget(
imageUrl: fromChainIcon,
width: 12,
height: 12,
colorFilter:
ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
)
],
),
amountWidget: Row(
spacing: 4,
children: [
Text(
"+",
),
Text(
receiveAmount.withMaxDecimals(8),
style: TextStyle(fontWeight: FontWeight.w500),
),
Text(
to.title,
),
if (toChainIcon.isNotEmpty) CakeImageWidget(imageUrl: toChainIcon, width: 12, height: 12)
],
),
leadingIcon: _getLeadingStack(context),
roundedTop: roundedTop,
roundedBottom: roundedBottom,
@@ -93,4 +141,21 @@ class HistoryTradeTile extends StatelessWidget {
//TODO approporiate fallback
return "";
}
String _getChainIcon(CryptoCurrency currency) {
try {
if (currency.chainIconPath != null) {
return currency.chainIconPath!;
}
if ((currency.tag ?? "").isNotEmpty) {
final currencyFromTag = CryptoCurrency.fromString(currency.tag!);
if (currencyFromTag.chainIconPath != null) {
return currencyFromTag.chainIconPath!;
}
}
} catch (_) {}
return "";
}
}
@@ -1,3 +1,4 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cw_core/card_design.dart';
import 'package:flutter/material.dart';
@@ -25,11 +26,13 @@ class BalanceCard extends StatelessWidget {
this.balance = "",
this.fiatBalance = "",
this.assetName = "",
this.fiatCurrencyTitle = "",
this.designSwitchDuration = const Duration(),
this.actions = const [],
this.capitalizeAssetName = true,
this.onCustomizeTapped,
this.accountIndex
this.accountIndex,
this.fiatFirst = false
});
final double width;
@@ -39,6 +42,8 @@ class BalanceCard extends StatelessWidget {
final String accountName;
final String balance;
final String fiatBalance;
final String fiatCurrencyTitle;
final bool fiatFirst;
final int? accountIndex;
final bool capitalizeAssetName;
final String assetName;
@@ -53,14 +58,21 @@ class BalanceCard extends StatelessWidget {
final Duration textFadeDuration = Duration(milliseconds: 80);
final double iconWidth = width * 0.15;
final name = fiatFirst ? fiatCurrencyTitle : assetName;
final resolvedAssetName = capitalizeAssetName ? name.toUpperCase() : name.toLowerCase();
final leadText = fiatFirst ? S.of(context).wallet_balance : accountName;
final bool showText = accountBalance.isNotEmpty ||
accountName.isNotEmpty ||
leadText.isNotEmpty ||
balance.isNotEmpty ||
fiatBalance.isNotEmpty ||
assetName.isNotEmpty;
resolvedAssetName.isNotEmpty;
final height = width * 0.62;
return AnimatedContainer(
duration: designSwitchDuration,
width: width,
@@ -105,7 +117,7 @@ class BalanceCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
if(accountName.isNotEmpty || accountBalance.isNotEmpty)
if(leadText.isNotEmpty || accountBalance.isNotEmpty)
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -127,8 +139,8 @@ class BalanceCard extends StatelessWidget {
AnimatedDefaultTextStyle(
duration: designSwitchDuration,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.w500, color: design.colors.textColor),
child: Text(accountName),
fontWeight: FontWeight.w500, color: design.colors.textColor.withAlpha(leadText == accountName ? 255 : 128)),
child: Text(leadText),
),
],
),
@@ -157,7 +169,7 @@ class BalanceCard extends StatelessWidget {
);
},
child: Row(
key: ValueKey("$balance ${assetName.toUpperCase()}"),
key: ValueKey("$balance ${resolvedAssetName.toUpperCase()}"),
spacing: 8.0,
children: [
AnimatedDefaultTextStyle(
@@ -165,14 +177,14 @@ class BalanceCard extends StatelessWidget {
style: DefaultTextStyle.of(context)
.style
.copyWith(color: design.colors.textColor, fontSize: 28, fontWeight: FontWeight.w500, letterSpacing: -0.4),
child: Text(balance),
child: Text(fiatFirst ? fiatBalance : balance),
),
AnimatedDefaultTextStyle(
duration: designSwitchDuration,
style: DefaultTextStyle.of(context)
.style
.copyWith(color: design.colors.textColorSecondary, fontSize: 28, fontWeight: FontWeight.w400, letterSpacing: -0.4),
child: Text(capitalizeAssetName ? assetName.toUpperCase() : assetName.toLowerCase()),
child: Text(resolvedAssetName),
),
],
),
@@ -196,8 +208,8 @@ class BalanceCard extends StatelessWidget {
);
},
child: Text(
key: ValueKey(fiatBalance),
fiatBalance,
key: ValueKey(fiatFirst ? balance : fiatBalance),
fiatFirst ? "$assetName $balance" : "$fiatCurrencyTitle $fiatBalance",
),
),
),
@@ -115,8 +115,7 @@ class _CardsViewState extends State<CardsView> {
// The second balance should always be the lightning balance
// printV(widget.dashboardViewModel.balanceViewModel.formattedBalances.first.availableBalance);
final walletBalanceRecord = widget.dashboardViewModel.balanceViewModel.formattedBalances
.elementAtOrNull(widget.lightningMode ? 1 : 0);
final walletBalanceRecord = widget.dashboardViewModel.balanceViewModel.getMainBalanceRecord(widget.lightningMode);
late final String walletBalance;
late final String walletFiatBalance;
@@ -128,11 +127,16 @@ class _CardsViewState extends State<CardsView> {
walletBalance = walletBalanceRecord?.combinedAvailableBalance ?? "0";
walletFiatBalance = walletBalanceRecord?.combinedFiatAvailableBalance ?? "0.00";
}
} else {
} else if(widget.dashboardViewModel.balanceViewModel.showCombinedBalance){
walletBalance = "";
walletFiatBalance = widget.dashboardViewModel.balanceViewModel.combinedFiatBalance;
}else {
walletBalance = walletBalanceRecord?.availableBalance ?? "0";
walletFiatBalance = walletBalanceRecord?.fiatAvailableBalance ?? "${widget.dashboardViewModel.appStore.settingsStore.fiatCurrency.title} 0.00";
walletFiatBalance = walletBalanceRecord?.fiatAvailableBalanceRaw ?? "0.00";
}
// the card designs is empty if widget gets built before it loads.
// should get populated before user sees anything
final CardDesign cardDesign;
@@ -154,6 +158,8 @@ class _CardsViewState extends State<CardsView> {
accountBalance = account.balance ?? "0.00";
}
final assetName = widget.dashboardViewModel.balanceViewModel.showCombinedBalance ? "" : walletBalanceRecord?.formattedAssetTitle ?? assetTitleFallback;
final List<BalanceCardAction> actions = widget.lightningMode
? [
BalanceCardAction(
@@ -183,9 +189,11 @@ class _CardsViewState extends State<CardsView> {
accountName: accountName,
accountBalance: accountBalance,
designSwitchDuration: Duration(milliseconds: 150),
assetName: walletBalanceRecord?.formattedAssetTitle ?? assetTitleFallback,
assetName: assetName,
capitalizeAssetName: _shouldCapitalizeAssetName(),
balance: walletBalance,
fiatCurrencyTitle: walletBalanceRecord?.fiatCurrencyTicker ?? widget.dashboardViewModel.settingsStore.fiatCurrency.title,
fiatFirst: widget.dashboardViewModel.balanceViewModel.showCombinedBalance,
fiatBalance: walletFiatBalance,
selected: _selectedIndex == visualIndex,
onCustomizeTapped: _selectedIndex == visualIndex ? widget.onCustomizeTapped : null,
@@ -12,6 +12,7 @@ class ModalTopBar extends StatelessWidget {
this.onTrailingPressed=nothing,
this.leadingIcon,
this.trailingIcon,
this.padding,
this.leadingWidget,
this.trailingWidget}) {
if(leadingIcon != null && leadingWidget != null) {
@@ -27,6 +28,7 @@ class ModalTopBar extends StatelessWidget {
final VoidCallback onLeadingPressed;
final VoidCallback onTrailingPressed;
final Widget? leadingIcon;
final EdgeInsets? padding;
final Widget? trailingIcon;
final Widget? leadingWidget;
final Widget? trailingWidget;
@@ -36,7 +38,7 @@ class ModalTopBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(18),
padding: padding??EdgeInsets.all(18),
child: Stack(
alignment: Alignment.topCenter,
children: [
@@ -0,0 +1,61 @@
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:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
class FavoriteTokenModal extends StatelessWidget {
const FavoriteTokenModal({super.key, required this.tokens});
final List<CryptoCurrency> tokens;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.vertical(top: Radius.circular(18))
),
child: SafeArea(
child: Column(
children: [
ModalTopBar(
title: S.of(context).favorite_token,
leadingIcon: Icon(Icons.close),
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
child: Column(
children: [
Text(
S.of(context).favorite_token_desc,
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: SingleChildScrollView(
child: NewListSections(
sections: {
"": tokens
.map((item) => ListItemRegularRow(
keyValue: item.title,
label: "${item.fullName} (${item.title})",
iconPath: item.iconPath,
onTap: () => Navigator.of(context).pop(item)))
.toList()
},
),
),
),
)
],
),
)
],
),
),
);
}
}
@@ -1,14 +1,23 @@
import 'dart:math';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_toggle.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/pages/settings_page.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/favorite_token_modal.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
@@ -28,24 +37,79 @@ class HomeSettingsPage extends BasePage {
return SingleChildScrollView(
child: Column(
children: [
Observer(
builder: (_) => SettingsPickerCell<SortBalanceBy>(
title: S.current.sort_by,
items: SortBalanceBy.values,
selectedItem: _homeSettingsViewModel.sortBalanceBy,
onItemSelected: _homeSettingsViewModel.setSortBalanceBy,
),
),
// Observer(
// builder: (_) => SettingsPickerCell<SortBalanceBy>(
// title: S.current.sort_by,
// items: SortBalanceBy.values,
// selectedItem: _homeSettingsViewModel.sortBalanceBy,
// onItemSelected: _homeSettingsViewModel.setSortBalanceBy,
// ),
// ),
// Divider(color: Theme.of(context).colorScheme.outlineVariant),
// Observer(
// builder: (_) => SettingsSwitcherCell(
// title: S.of(context).pin_at_top(_homeSettingsViewModel.nativeToken.title),
// value: _homeSettingsViewModel.pinNativeToken,
// title: S.of(context).show_combined_balance,
// value: _homeSettingsViewModel.showCombinedBalance,
// onValueChange: (_, bool value) {
// _homeSettingsViewModel.setPinNativeToken(value);
// _homeSettingsViewModel.setShowCombinedBalance(value);
// },
// ),
// ),
// Observer(
// builder: (_) => _homeSettingsViewModel.showCombinedBalance
// ? SizedBox.shrink()
// : SettingsCellWithArrow(
// title: S.of(context).favorite_token,
// handler: (context) async {
//
// },
// )),
Padding(
padding: const EdgeInsets.all(12.0),
child: Observer(
builder: (_) => NewListSections(sections: {
"": [
ListItemRegularRow(
keyValue: "sort balance by",
label: S.of(context).sort_by,
trailingText: _homeSettingsViewModel.sortBalanceBy.toString(),
onTap: () {
showPopUp<void>(
context: context,
builder: (context) => Picker(
items: SortBalanceBy.values,
selectedAtIndex:
SortBalanceBy.values.indexOf(_homeSettingsViewModel.sortBalanceBy),
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: _homeSettingsViewModel.setSortBalanceBy,
isSeparated: false,
),
);
}),
ListItemToggle(
keyValue: "show combined",
label: S.of(context).show_combined_balance,
value: _homeSettingsViewModel.showCombinedBalance,
onChanged: (val) => _homeSettingsViewModel.setShowCombinedBalance(val)),
if (!_homeSettingsViewModel.showCombinedBalance)
ListItemRegularRow(
keyValue: "fav token",
label: S.of(context).favorite_token,
trailingText: _homeSettingsViewModel.favoriteToken.title,
showArrow: true,
onTap: () async {
final res = await showModalBottomSheet(
context: context,
builder: (context) =>
FavoriteTokenModal(tokens: _homeSettingsViewModel.enabledTokens));
if (res != null && res is CryptoCurrency) {
_homeSettingsViewModel.setFavoriteToken(res);
}
})
]
}),
),
),
Divider(color: Theme.of(context).colorScheme.outlineVariant),
const SizedBox(height: 20),
Row(
+1
View File
@@ -152,6 +152,7 @@ class TokenUtilities {
required WalletType walletType,
required String address,
}) async {
if(address.isEmpty) return null;
final lower = address.toLowerCase();
switch (walletType) {
case WalletType.ethereum:
@@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/utilities.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
@@ -6,8 +7,12 @@ import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -57,11 +62,16 @@ class BalanceRecord {
.toString().withMaxDecimals(8);
String get combinedFiatAvailableBalance =>
fiatAvailableBalance.split(" ").first +
fiatCurrencyTicker +
" " +
((double.tryParse(fiatAvailableBalance.split(" ").last) ?? 0) +
(double.tryParse(fiatSecondAvailableBalance.split(" ").last) ?? 0))
.toString().withMaxDecimals(8);
((double.tryParse(fiatAvailableBalanceRaw) ?? 0) +
(double.tryParse(fiatSecondAvailableBalanceRaw) ?? 0))
.toString()
.withMaxDecimals(8);
String get fiatAvailableBalanceRaw => fiatAvailableBalance.split(" ").last;
String get fiatSecondAvailableBalanceRaw => fiatSecondAvailableBalance.split(" ").last;
String get fiatCurrencyTicker => fiatAvailableBalance.split(" ").first;
}
class BalanceViewModel = BalanceViewModelBase with _$BalanceViewModel;
@@ -410,6 +420,58 @@ abstract class BalanceViewModelBase with Store {
return balance;
}
BalanceRecord? getMainBalanceRecord(bool lightningMode) {
if (lightningMode) {
return formattedBalances.elementAtOrNull(1);
}
if (wallet.walletInfo.favoriteTokenAddress != null) {
return formattedBalances.firstWhereOrNull((item) => (getTokenAddressBasedOnWallet(item.asset) ==
wallet.walletInfo.favoriteTokenAddress)) ??
formattedBalances.elementAt(0);
}
return formattedBalances.elementAt(0);
}
String? getTokenAddressBasedOnWallet(CryptoCurrency asset) {
if (wallet.type == WalletType.tron) {
return tron!.getTokenAddress(asset);
}
if (wallet.type == WalletType.solana) {
return solana!.getTokenAddress(asset);
}
if (isEVMCompatibleChain(wallet.type) && asset is Erc20Token) {
return evm!.getTokenAddress(asset);
}
if (wallet.type == WalletType.zano) {
return zano!.getZanoAssetAddress(asset);
}
return null;
}
@computed
bool get showCombinedBalance {
if(wallet.type == WalletType.bitcoin) return false;
if(balances.values.length == 1) return false;
return wallet.walletInfo.showCombinedBalance;
}
@computed
String get combinedFiatBalance {
double ret = 0.0;
for(final record in balances.values) {
ret += double.tryParse(record.fiatAvailableBalanceRaw) ?? 0;
}
return ret.toStringAsFixed(2);
}
Balance _currencyBalance(CryptoCurrency cryptoCurrency) {
final balance = wallet.balance[cryptoCurrency];
@@ -23,6 +23,7 @@ import 'package:cake_wallet/store/dashboard/order_filter_store.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cake_wallet/utils/tor.dart';
import 'package:cake_wallet/wownero/wownero.dart' as wow;
@@ -481,18 +482,29 @@ abstract class DashboardViewModelBase with Store {
}
// printV("Transaction disposer callback (relevantTxs: ${relevantTxs.length} current: ${transactions.length})");
String _txIdentityString(String txHash, TransactionDirection direction) => '${txHash}_$direction';
final existingKeys = transactions
.map((item) => _txIdentityString(item.transaction.txHash, item.transaction.direction))
.toSet();
final newTransactions = relevantTxs
.where((tx) => !existingKeys.contains(_txIdentityString(tx.txHash, tx.direction)))
.map((tx) => TransactionListItem(
transaction: tx,
balanceViewModel: balanceViewModel,
appStore: appStore,
key: ValueKey('${wallet.type.name}_transaction_history_item_${tx.id}_key'),
))
.where((item) => !transactions.contains(item));
.toList();
transactions.removeWhere((item) => newTransactions.any((tx) =>
tx.transaction.txHash == item.transaction.txHash &&
tx.transaction.direction == item.transaction.direction));
final newKeys = newTransactions
.map((item) => _txIdentityString(item.transaction.txHash, item.transaction.direction))
.toSet();
transactions.removeWhere(
(item) => newKeys.contains(_txIdentityString(item.transaction.txHash, item.transaction.direction)),
);
transactions.addAll(newTransactions);
// transactions.clear();
@@ -9,6 +9,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/utils/token_utilities.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/zano/zano.dart';
@@ -29,10 +30,14 @@ abstract class HomeSettingsViewModelBase with Store {
: tokens = ObservableSet<CryptoCurrency>(),
isAddingToken = false,
isDeletingToken = false,
isValidatingContractAddress = false {
isValidatingContractAddress = false,
showCombinedBalance = _balanceViewModel.wallet.walletInfo.showCombinedBalance,
favoriteToken = _balanceViewModel.wallet.currency {
_updateTokensList();
_updateLocalFavoriteToken();
// React to wallet changes
// React to wallet changes
reaction((_) => _balanceViewModel.wallet, (_) {
_updateTokensList();
});
@@ -55,6 +60,9 @@ abstract class HomeSettingsViewModelBase with Store {
final ObservableSet<CryptoCurrency> tokens;
@computed
List<CryptoCurrency> get enabledTokens => [_balanceViewModel.wallet.currency, ...tokens.where((item)=>item.enabled).toList()];
WalletType get walletType => _balanceViewModel.wallet.type;
@observable
@@ -491,6 +499,45 @@ abstract class HomeSettingsViewModelBase with Store {
}
}
// FIXME these two observables cause duplicated state and are needed because mobx. remove them as part of the refactor and replace with proper getters
@observable
bool showCombinedBalance;
@observable
CryptoCurrency favoriteToken;
@action
void setShowCombinedBalance(bool value) {
_balanceViewModel.wallet.walletInfo.showCombinedBalance = value;
showCombinedBalance = value;
_balanceViewModel.wallet.updateBalance();
_balanceViewModel.wallet.walletInfo.save();
}
@action
Future<void> setFavoriteToken(CryptoCurrency token) async {
final String? address;
if(token == _balanceViewModel.wallet.currency) {
address = null;
} else {
address = getTokenAddressBasedOnWallet(token);
}
_balanceViewModel.wallet.walletInfo.favoriteTokenAddress = address;
_balanceViewModel.wallet.walletInfo.save();
_updateLocalFavoriteToken();
}
@action
Future<void> _updateLocalFavoriteToken() async {
favoriteToken = await TokenUtilities.findTokenByAddress(
walletType: _balanceViewModel.wallet.type, address: _balanceViewModel.wallet.walletInfo.favoriteTokenAddress ?? "") ??
_balanceViewModel.wallet.currency;
}
@action
void _refreshTokensList() {
final _tokens = Set.of(tokens);