CW-1125-d-euro-ui-enhancements (#2369)

* feat: enhance dEuro Savings UI and functionality with fiat support and tooltips

* feat: add "Reinvest Interest" feature and improve dEuro Savings functionality

- Implemented the "Reinvest Interest" feature for dEuro Savings.
- Updated localized strings for multiple languages.
- Enhanced error handling for insufficient gas fees.
- Incorporated loading state and tooltips in Savings Card and Interest Card.
- Improved UI responsiveness and button state management.

* feat: localize deuro tooltips

* feat: enhance dEuro Savings with reinvestment feature and localized strings

- Added "Reinvest Interest" functionality for dEuro Savings.
- Updated localized strings across multiple languages.
- Improved modal bottom sheet handling for savings add/remove operations.
- Enhanced tooltips and UI responsiveness.

* feat: improve dEuro Savings page with info chips and localized strings

* Update res/values/strings_de.arb

* fix: update dEuro action type reset in transaction handling
This commit is contained in:
Konstantin Ullrich
2025-07-11 01:36:40 +03:00
committed by GitHub
parent d67c32dff1
commit 3ba2e4283e
45 changed files with 1110 additions and 297 deletions
@@ -1,6 +1,7 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/info_chip.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/interest_card_widget.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_card_widget.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_edit_sheet.dart';
@@ -14,9 +15,11 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:url_launcher/url_launcher_string.dart';
class DEuroSavingsPage extends BasePage {
final DEuroViewModel _dEuroViewModel;
@@ -44,8 +47,7 @@ class DEuroSavingsPage extends BasePage {
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onSurface,
overlayColor: WidgetStateColor.resolveWith(
(states) => Colors.transparent),
overlayColor: WidgetStateColor.resolveWith((states) => Colors.transparent),
),
onPressed: _dEuroViewModel.reloadSavingsUserData,
child: Icon(
@@ -61,8 +63,7 @@ class DEuroSavingsPage extends BasePage {
@override
Widget body(BuildContext context) {
WidgetsBinding.instance
.addPostFrameCallback((_) => _setReactions(context, _dEuroViewModel));
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context, _dEuroViewModel));
return Container(
width: double.infinity,
@@ -73,19 +74,50 @@ class DEuroSavingsPage extends BasePage {
isDarkTheme: currentTheme.isDark,
interestRate: "${_dEuroViewModel.interestRate}%",
savingsBalance: _dEuroViewModel.savingsBalance,
fiatSavingsBalance: _dEuroViewModel.fiatSavingsBalance,
currency: CryptoCurrency.deuro,
fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
onAddSavingsPressed: () => _onSavingsAdd(context),
onRemoveSavingsPressed: () => _onSavingsRemove(context),
onApproveSavingsPressed: _dEuroViewModel.prepareApproval,
onTooltipPressed: () => _onSavingsTooltipPressed(context),
isEnabled: _dEuroViewModel.isEnabled,
isLoading: _dEuroViewModel.isLoading,
),
),
Observer(
builder: (_) => InterestCardWidget(
isDarkTheme: currentTheme.isDark,
title: S.of(context).deuro_savings_collect_interest,
collectedInterest: _dEuroViewModel.accruedInterest,
fiatAccruedInterest: _dEuroViewModel.fiatAccruedInterest,
fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
accruedInterest: _dEuroViewModel.accruedInterest,
onCollectInterest: _dEuroViewModel.prepareCollectInterest,
onReinvestInterest: _dEuroViewModel.prepareReinvestInterest,
onTooltipPressed: () => _onInterestTooltipPressed(context),
isEnabled: _dEuroViewModel.isEnabled,
),
),
Spacer(),
SafeArea(
child: Padding(
padding: EdgeInsets.only(bottom: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InfoChip(
label: S.of(context).deuro_about_deuro,
icon: CupertinoIcons.info,
onPressed: () => _showWelcomeTooltip(context),
),
SizedBox(width: 10),
InfoChip(
label: S.of(context).website,
icon: CupertinoIcons.globe,
onPressed: () => launchUrlString("https://deuro.com/"),
)
],
),
),
),
],
@@ -94,14 +126,12 @@ class DEuroSavingsPage extends BasePage {
}
Future<void> _onSavingsAdd(BuildContext context) async {
final amount = await Navigator.of(context).push(MaterialPageRoute<String>(
builder: (BuildContext context) => SavingEditPage(isAdding: true)));
final amount = await _showEditBottomSheet(context, isAdding: true);
if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, true);
}
Future<void> _onSavingsRemove(BuildContext context) async {
final amount = await Navigator.of(context).push(MaterialPageRoute<String>(
builder: (BuildContext context) => SavingEditPage(isAdding: false)));
final amount = await _showEditBottomSheet(context, isAdding: false);
if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, false);
}
@@ -112,20 +142,41 @@ class DEuroSavingsPage extends BasePage {
reaction((_) => dEuroViewModel.transaction, (PendingTransaction? tx) async {
if (tx == null) return;
String title;
switch (_dEuroViewModel.actionType) {
case DEuroActionType.deposit:
title = S.of(context).deuro_savings_add;
break;
case DEuroActionType.withdraw:
title = S.of(context).deuro_savings_remove;
break;
case DEuroActionType.reinvest:
title = S.of(context).deuro_reinvest_interest;
break;
default:
title = S.of(context).confirm_transaction;
break;
}
final result = await showModalBottomSheet<bool>(
context: context,
isDismissible: false,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) => ConfirmSendingBottomSheet(
key: ValueKey('savings_page_confirm_sending_dialog_key'),
titleText: S.of(bottomSheetContext).confirm_transaction,
titleText: title,
currentTheme: currentTheme,
walletType: WalletType.ethereum,
titleIconPath: CryptoCurrency.deuro.iconPath,
currency: CryptoCurrency.deuro,
amount: S.of(bottomSheetContext).send_amount,
amountValue: tx.amountFormatted,
fiatAmountValue: _dEuroViewModel.pendingTransactionFiatAmountFormatted,
amountValue: _dEuroViewModel.actionType == DEuroActionType.reinvest
? _dEuroViewModel.accruedInterest
: tx.amountFormatted,
fiatAmountValue: _dEuroViewModel.actionType == DEuroActionType.reinvest
? _dEuroViewModel.fiatAccruedInterest
: _dEuroViewModel.pendingTransactionFiatAmountFormatted,
fee: S.of(bottomSheetContext).send_estimated_fee,
feeValue: tx.feeFormatted,
feeFiatAmount: _dEuroViewModel.pendingTransactionFeeFiatAmountFormatted,
@@ -166,6 +217,7 @@ class DEuroSavingsPage extends BasePage {
dEuroViewModel.commitApprovalTransaction();
},
change: tx.change,
explanation: S.of(context).deuro_savings_approve_app_description,
),
);
@@ -186,27 +238,29 @@ class DEuroSavingsPage extends BasePage {
contentImage: 'assets/images/birthday_cake.png',
content: S.of(bottomSheetContext).deuro_tx_commited_content,
actionButtonText: S.of(bottomSheetContext).close,
actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'),
actionButtonKey: ValueKey('savings_page_sent_dialog_ok_button_key'),
actionButton: () => Navigator.of(bottomSheetContext).pop(),
),
);
});
}
if (state is NoEtherState) {
WidgetsBinding.instance.addPostFrameCallback((_) async => await _showNoEthTooltip(context));
}
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!context.mounted) return;
await showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
alertTitle: S.of(popupContext).error,
alertContent: state.error,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop(),
);
},
builder: (BuildContext popupContext) => AlertWithOneAction(
alertTitle: S.of(popupContext).error,
alertContent: state.error,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop(),
),
);
});
}
@@ -214,4 +268,107 @@ class DEuroSavingsPage extends BasePage {
_isReactionsSet = true;
}
void _onSavingsTooltipPressed(BuildContext context) => _showTooltip(
context,
title: S.of(context).deuro_savings_balance,
content: S.of(context).deuro_savings_balance_tooltip,
key: 'savings_tooltip',
onLearnMorePressed: () {}, //ToDo
);
void _onInterestTooltipPressed(BuildContext context) => _showTooltip(
context,
title: S.of(context).deuro_savings_collect_interest,
content: S.of(context).deuro_savings_collect_interest_tooltip,
key: 'interest_tooltip',
onLearnMorePressed: () {}, //ToDo
);
void _showTooltip(
BuildContext context, {
required String title,
required String content,
required String key,
required VoidCallback onLearnMorePressed,
}) {
if (!context.mounted) return;
showModalBottomSheet<void>(
context: context,
builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
currentTheme: currentTheme,
titleText: title,
titleIconPath: CryptoCurrency.deuro.iconPath,
content: content,
isTwoAction: true,
rightButtonText: S.of(context).close,
rightActionButtonKey: ValueKey('deuro_page_tooltip_dialog_${key}_ok_button_key'),
actionRightButton: () => Navigator.of(bottomSheetContext).pop(),
leftButtonText: S.of(context).learn_more,
leftActionButtonKey: ValueKey('deuro_page_tooltip_dialog_${key}_learn_more_button_key'),
actionLeftButton: onLearnMorePressed,
),
);
}
Future<void> _showWelcomeTooltip(BuildContext context) async {
if (!context.mounted) return;
return showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
currentTheme: currentTheme,
height: 350,
titleText: "dEURO",
titleIconPath: CryptoCurrency.deuro.iconPath,
contentImage: 'assets/images/deuro_hero.png',
contentImageSize: 200,
content: S.of(context).deuro_savings_welcome_description,
isTwoAction: true,
rightButtonText: S.of(context).close,
rightActionButtonKey: ValueKey('deuro_page_tooltip_dialog_welcome_ok_button_key'),
actionRightButton: () => Navigator.of(bottomSheetContext).pop(),
leftButtonText: S.of(context).learn_more,
leftActionButtonKey: ValueKey('deuro_page_tooltip_dialog_welcome_learn_more_button_key'),
actionLeftButton: () => Navigator.of(bottomSheetContext).pop(), // ToDo
),
);
}
Future<void> _showNoEthTooltip(BuildContext context) async {
if (!context.mounted) return;
return showModalBottomSheet<void>(
context: context,
builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
currentTheme: currentTheme,
titleText: title,
titleIconPath: CryptoCurrency.deuro.iconPath,
contentImage: 'assets/images/deuro_not_enough_eth.png',
content: S.of(context).deuro_tooltip_no_eth,
actionButtonKey: ValueKey('deuro_page_tooltip_dialog_no_eth_ok_button_key'),
actionButtonText: S.of(context).close,
actionButton: () => Navigator.of(bottomSheetContext).pop(),
),
);
}
Future<String?> _showEditBottomSheet(BuildContext context, {bool isAdding = false}) async {
if (!context.mounted) return null;
return showModalBottomSheet<String?>(
context: context,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) => SavingsEditSheet(
titleText: isAdding ? S.of(context).deuro_savings_add : S.of(context).deuro_savings_remove,
titleIconPath: CryptoCurrency.deuro.iconPath,
balanceTitle: isAdding
? S.of(context).deuro_savings_available_to_add
: S.of(context).deuro_savings_available_to_remove,
balance: isAdding ? _dEuroViewModel.accountBalance : _dEuroViewModel.savingsBalance,
),
);
}
}
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
class InfoChip extends StatelessWidget {
final String label;
final IconData icon;
final VoidCallback onPressed;
const InfoChip({
super.key,
required this.label,
required this.icon,
required this.onPressed,
});
@override
Widget build(BuildContext context) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Theme.of(context).colorScheme.surfaceContainer,
),
padding: const EdgeInsets.fromLTRB(12, 6, 12, 6),
child: InkWell(
onTap: onPressed,
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 5),
child: Icon(
icon,
color: Theme.of(context).colorScheme.onSurface,
),
),
Text(
label,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
);
}
@@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_card_widget.dart';
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
@@ -5,63 +6,91 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
class InterestCardWidget extends StatelessWidget {
InterestCardWidget({
const InterestCardWidget({
required this.title,
required this.collectedInterest,
super.key,
required this.accruedInterest,
required this.isDarkTheme,
required this.isEnabled,
required this.onCollectInterest,
required this.onReinvestInterest,
required this.onTooltipPressed,
this.fiatAccruedInterest,
this.fiatCurrency,
});
final String title;
final String collectedInterest;
final String accruedInterest;
final String? fiatAccruedInterest;
final FiatCurrency? fiatCurrency;
final bool isDarkTheme;
final bool isEnabled;
final VoidCallback onCollectInterest;
final VoidCallback onReinvestInterest;
final VoidCallback onTooltipPressed;
@override
Widget build(BuildContext context) {
return Stack(children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 16),
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
colors: [
isDarkTheme
? CustomThemeColors.cardGradientColorPrimaryDark
: CustomThemeColors.cardGradientColorPrimaryLight,
isDarkTheme
? CustomThemeColors.cardGradientColorSecondaryDark
: CustomThemeColors.cardGradientColorSecondaryLight,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
Widget build(BuildContext context) => Stack(children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 16),
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
colors: [
isDarkTheme
? CustomThemeColors.cardGradientColorPrimaryDark
: CustomThemeColors.cardGradientColorPrimaryLight,
isDarkTheme
? CustomThemeColors.cardGradientColorSecondaryDark
: CustomThemeColors.cardGradientColorSecondaryLight,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
children: [
SavingsCard.getAssetBalanceRow(
context,
title: title,
amount: accruedInterest,
fiatAmount: fiatAccruedInterest,
currency: CryptoCurrency.deuro,
fiatCurrency: fiatCurrency,
hideSymbol: false,
onTooltipPressed: onTooltipPressed
),
SizedBox(height: 10),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(
child: SavingsCard.getButton(
context,
imagePath: 'assets/images/collect_interest.png',
label: S.of(context).deuro_collect_interest,
onPressed: onCollectInterest,
backgroundColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
enabled: isEnabled,
),
),
SizedBox(width: 12),
Expanded(
child: SavingsCard.getButton(
context,
label: S.of(context).deuro_reinvest_interest,
icon: Icons.account_balance_outlined,
onPressed: onReinvestInterest,
backgroundColor: Theme.of(context).colorScheme.surface,
color: Theme.of(context).colorScheme.onSecondaryContainer,
enabled: isEnabled,
),
),
]),
],
),
),
),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
children: [
SavingsCard.getAssetBalanceRow(
context,
title: title,
subtitle: collectedInterest,
currency: CryptoCurrency.deuro,
hideSymbol: true,
),
SizedBox(height: 10),
SavingsCard.getButton(
context,
label: S.of(context).deuro_collect_interest,
onPressed: onCollectInterest,
backgroundColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
),
],
),
),
),
]);
}
]);
}
@@ -1,21 +1,27 @@
import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SavingsCard extends StatelessWidget {
final bool isDarkTheme;
final bool isEnabled;
final bool isLoading;
final String interestRate;
final String savingsBalance;
final String? fiatSavingsBalance;
final FiatCurrency? fiatCurrency;
final CryptoCurrency currency;
final VoidCallback onAddSavingsPressed;
final VoidCallback onRemoveSavingsPressed;
final VoidCallback onApproveSavingsPressed;
final VoidCallback onTooltipPressed;
const SavingsCard({
super.key,
@@ -26,162 +32,187 @@ class SavingsCard extends StatelessWidget {
required this.onAddSavingsPressed,
required this.onRemoveSavingsPressed,
required this.onApproveSavingsPressed,
required this.onTooltipPressed,
this.isEnabled = true,
this.isLoading = false,
this.fiatSavingsBalance,
this.fiatCurrency,
});
@override
Widget build(BuildContext context) => Container(
margin: const EdgeInsets.all(15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
colors: [
isDarkTheme
? CustomThemeColors.cardGradientColorPrimaryDark
: CustomThemeColors.cardGradientColorPrimaryLight,
isDarkTheme
? CustomThemeColors.cardGradientColorSecondaryDark
: CustomThemeColors.cardGradientColorSecondaryLight,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
margin: const EdgeInsets.all(15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Theme.of(context).colorScheme.surfaceContainerLowest,
),
),
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
children: [
getAssetBalanceRow(context,
title: S.of(context).deuro_savings_balance,
subtitle: savingsBalance,
currency: currency),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Text(
'Current APR',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color:
Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
softWrap: true,
),
),
Text(
interestRate,
child: Column(children: [
Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10, left: 20, right: 20),
child: Row(
children: [
Expanded(
child: Text(
'Current APR', // ToDo: Localize
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
softWrap: true,
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: isEnabled
? [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_add,
imagePath: 'assets/images/received.png',
onPressed: onAddSavingsPressed,
backgroundColor:
Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
),
),
SizedBox(width: 12),
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_remove,
imagePath: 'assets/images/upload.png',
onPressed: onRemoveSavingsPressed,
backgroundColor:
Theme.of(context).colorScheme.surface,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
),
]
: [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_set_approval,
onPressed: onApproveSavingsPressed,
backgroundColor:
Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
),
)
],
),
],
),
));
static Widget getButton(
BuildContext context, {
required String label,
String? imagePath,
required VoidCallback onPressed,
required Color backgroundColor,
required Color color,
}) =>
Semantics(
label: label,
child: OutlinedButton(
onPressed: onPressed,
style: OutlinedButton.styleFrom(
backgroundColor: backgroundColor,
side: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant.withAlpha(0),
width: 0,
),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (imagePath != null) ...[
Image.asset(
imagePath,
height: 30,
width: 30,
color: color,
),
const SizedBox(width: 8),
],
),
Text(
label,
interestRate,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w800,
),
softWrap: true,
),
],
),
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
colors: [
isDarkTheme
? CustomThemeColors.cardGradientColorPrimaryDark
: CustomThemeColors.cardGradientColorPrimaryLight,
isDarkTheme
? CustomThemeColors.cardGradientColorSecondaryDark
: CustomThemeColors.cardGradientColorSecondaryLight,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
padding: const EdgeInsets.all(20),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: getAssetBalanceRow(
context,
title: S.of(context).deuro_savings_balance,
amount: savingsBalance,
fiatAmount: fiatSavingsBalance,
currency: currency,
fiatCurrency: fiatCurrency,
hideSymbol: false,
onTooltipPressed: onTooltipPressed,
),
),
isLoading
? CupertinoActivityIndicator(color: Theme.of(context).colorScheme.onSurface)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: isEnabled
? [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_add,
imagePath: 'assets/images/received.png',
onPressed: onAddSavingsPressed,
backgroundColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
),
),
SizedBox(width: 12),
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_remove,
imagePath: 'assets/images/upload.png',
onPressed: onRemoveSavingsPressed,
backgroundColor: Theme.of(context).colorScheme.surface,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
]
: [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_approve_app,
onPressed: onApproveSavingsPressed,
backgroundColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
icon: Icons.check,
),
)
],
),
],
),
),
]),
);
static Widget getButton(
BuildContext context, {
required String label,
required VoidCallback onPressed,
required Color backgroundColor,
required Color color,
String? imagePath,
IconData? icon,
bool enabled = true,
double iconSize = 24,
}) {
backgroundColor = enabled ? backgroundColor : backgroundColor.withAlpha(130);
color = enabled ? color : color.withAlpha(130);
return Semantics(
label: label,
child: OutlinedButton(
onPressed: enabled ? onPressed : null,
style: OutlinedButton.styleFrom(
backgroundColor: backgroundColor,
side: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant.withAlpha(0),
width: 0,
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (imagePath != null) ...[
Image.asset(imagePath, height: iconSize, width: iconSize, color: color),
const SizedBox(width: 8),
],
if (icon != null) ...[
Icon(icon, size: iconSize, color: color),
const SizedBox(width: 8),
],
Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
);
}
static Widget getAssetBalanceRow(
BuildContext context, {
required String title,
required String subtitle,
required String amount,
required CryptoCurrency currency,
bool hideSymbol = true,
VoidCallback? onTooltipPressed,
FiatCurrency? fiatCurrency,
String? fiatAmount,
}) =>
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -190,16 +221,32 @@ class SavingsCard extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
Row(
children: [
Text(
title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
if (onTooltipPressed != null)
Padding(
padding: EdgeInsets.only(left: 4),
child: InkWell(
onTap: onTooltipPressed,
child: Icon(
CupertinoIcons.question_circle,
size: 13,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
SizedBox(height: 6),
AutoSizeText(
subtitle,
amount,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w900,
@@ -209,10 +256,21 @@ class SavingsCard extends StatelessWidget {
maxLines: 1,
textAlign: TextAlign.start,
),
if (fiatCurrency != null)
AutoSizeText(
"${fiatCurrency.title} $fiatAmount",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
fontSize: 16,
height: 1.5,
),
maxLines: 1,
textAlign: TextAlign.start,
),
],
),
SizedBox(
//width: min(MediaQuery.of(context).size.width * 0.2, 100),
child: Center(
child: Column(
children: [
@@ -225,15 +283,11 @@ class SavingsCard extends StatelessWidget {
width: 30.0,
child: Center(
child: Text(
currency.title
.substring(0, min(currency.title.length, 2)),
style:
Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 11,
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
),
currency.title.substring(0, min(currency.title.length, 2)),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
decoration: BoxDecoration(
@@ -243,7 +297,7 @@ class SavingsCard extends StatelessWidget {
),
),
if (!hideSymbol) ...[
const SizedBox(height: 10),
const SizedBox(height: 3),
Text(
currency.title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
@@ -1,24 +1,39 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/numpad.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:flutter/material.dart';
class SavingEditPage extends BasePage {
final bool isAdding;
class SavingsEditSheet extends BaseBottomSheet {
final String? balance;
final String? balanceTitle;
SavingEditPage({required this.isAdding});
String get title =>
isAdding ? S.current.deuro_savings_add : S.current.deuro_savings_remove;
const SavingsEditSheet({
required super.titleText,
super.titleIconPath,
this.balance,
this.balanceTitle,
});
@override
Widget body(BuildContext context) => _SavingsEditBody();
Widget contentWidget(BuildContext context) => SizedBox(
height: 600,
child: _SavingsEditBody(
balance: balance,
balanceTitle: balanceTitle,
),
);
@override
Widget footerWidget(BuildContext context) => SizedBox.shrink();
}
class _SavingsEditBody extends StatefulWidget {
const _SavingsEditBody();
final String? balance;
final String? balanceTitle;
const _SavingsEditBody({this.balance, this.balanceTitle});
@override
State<StatefulWidget> createState() => _SavingsEditBodyState();
@@ -27,8 +42,7 @@ class _SavingsEditBody extends StatefulWidget {
class _SavingsEditBodyState extends State<_SavingsEditBody> {
@override
void initState() {
WidgetsBinding.instance
.addPostFrameCallback((_) => _numpadFocusNode.requestFocus());
WidgetsBinding.instance.addPostFrameCallback((_) => _numpadFocusNode.requestFocus());
super.initState();
}
@@ -51,31 +65,36 @@ class _SavingsEditBodyState extends State<_SavingsEditBody> {
child: AutoSizeText(
"${amount.toString()} dEuro",
maxLines: 1,
maxFontSize: 60,
maxFontSize: 32,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 60,
fontSize: 32,
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
),
)),
if (widget.balance != null && widget.balanceTitle != null) ...[
Divider(),
AssetBalanceRow(
title: widget.balanceTitle!,
amount: widget.balance!,
onAllPressed: () => setState(() => amount = widget.balance!),
),
],
NumberPad(
focusNode: _numpadFocusNode,
onNumberPressed: (i) => setState(
() => amount = amount == '0' ? i.toString() : '${amount}${i}',
),
onDeletePressed: () => setState(
() => amount = amount.length > 1
? amount.substring(0, amount.length - 1)
: '0',
() => amount = amount.length > 1 ? amount.substring(0, amount.length - 1) : '0',
),
onDecimalPressed: () =>
setState(() => amount = '${amount.replaceAll('.', '')}.'),
onDecimalPressed: () => setState(() => amount = '${amount.replaceAll('.', '')}.'),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 34),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
child: LoadingPrimaryButton(
onPressed: () => Navigator.pop(context, amount),
text: S.of(context).confirm,
@@ -88,3 +107,74 @@ class _SavingsEditBodyState extends State<_SavingsEditBody> {
]),
);
}
class AssetBalanceRow extends StatelessWidget {
final String title;
final String amount;
final VoidCallback onAllPressed;
const AssetBalanceRow({
super.key,
required this.title,
required this.amount,
required this.onAllPressed,
});
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
],
),
SizedBox(height: 6),
AutoSizeText(
amount,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w400,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.start,
),
],
),
SizedBox(
child: Center(
child: InkWell(
onTap: onAllPressed,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Theme.of(context).colorScheme.surfaceContainer,
),
padding: EdgeInsets.fromLTRB(15, 10, 15, 10),
child: Text(
S.of(context).all,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
),
),
),
),
),
],
),
);
}
@@ -3,8 +3,9 @@ import 'package:flutter/material.dart';
abstract class BaseBottomSheet extends StatelessWidget {
final String titleText;
final String? titleIconPath;
final double maxHeight;
const BaseBottomSheet({required this.titleText, this.titleIconPath});
const BaseBottomSheet({required this.titleText, this.titleIconPath, this.maxHeight = 900});
Widget headerWidget(BuildContext context) {
return Column(
@@ -59,7 +60,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: 600),
constraints: BoxConstraints(maxHeight: maxHeight),
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30.0), topRight: Radius.circular(30.0)),
@@ -22,6 +22,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
final String fee;
final String feeValue;
final String feeFiatAmount;
final String? explanation;
final List<Output> outputs;
final VoidCallback onSlideComplete;
final WalletType walletType;
@@ -46,6 +47,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
required this.onSlideComplete,
required this.walletType,
this.change,
this.explanation,
this.isOpenCryptoPay = false,
Key? key,
}) : showScrollbar = outputs.length > 3,
@@ -87,9 +89,18 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
tileBackgroundColor: tileBackgroundColor,
),
),
if (explanation != null)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: StandardInfoTile(
value: explanation!,
itemTitleTextStyle: itemTitleTextStyle,
tileBackgroundColor: tileBackgroundColor,
),
),
StandardTile(
itemTitle: amount,
itemValue: amountValue + ' ${currency.title}',
itemValue: '$amountValue ${currency.title}',
itemTitleTextStyle: itemTitleTextStyle,
itemSubTitle: fiatAmountValue,
itemSubTitleTextStyle: itemSubTitleTextStyle,
@@ -260,6 +271,34 @@ class StandardTile extends StatelessWidget {
}
}
class StandardInfoTile extends StatelessWidget {
const StandardInfoTile({
super.key,
required this.value,
required this.itemTitleTextStyle,
required this.tileBackgroundColor,
});
final String value;
final TextStyle itemTitleTextStyle;
final Color tileBackgroundColor;
@override
Widget build(BuildContext context) => Semantics(
container: true,
label: value,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: tileBackgroundColor,
),
width: double.infinity,
child: Text(value, style: itemTitleTextStyle),
),
);
}
class AddressTile extends StatelessWidget {
const AddressTile({
super.key,
@@ -39,6 +39,8 @@ class InfoBottomSheet extends BaseBottomSheet {
final VoidCallback? actionRightButton;
final Key? rightActionButtonKey;
final Key? leftActionButtonKey;
final double height;
final double? contentImageSize;
InfoBottomSheet({
required String titleText,
@@ -46,9 +48,11 @@ class InfoBottomSheet extends BaseBottomSheet {
required this.currentTheme,
this.contentImage,
this.contentImageColor,
this.contentImageSize,
this.content,
this.isTwoAction = false,
this.showDontAskMeCheckbox = false,
this.height = 200,
this.onCheckboxChanged,
this.actionButtonText,
this.actionButton,
@@ -59,18 +63,22 @@ class InfoBottomSheet extends BaseBottomSheet {
this.actionRightButton,
this.rightActionButtonKey,
this.leftActionButtonKey,
}) : super(titleText: titleText, titleIconPath: titleIconPath);
double maxHeight = 900,
}) : super(titleText: titleText, titleIconPath: titleIconPath, maxHeight: maxHeight);
@override
Widget contentWidget(BuildContext context) {
return SizedBox(
height: 200,
height: height,
child: Column(
children: [
if (contentImage != null)
Expanded(
flex: 4,
child: getImage(contentImage!, imageColor: contentImageColor),
child: SizedBox(
width: contentImageSize,
child: getImage(contentImage!, imageColor: contentImageColor),
),
)
else
Container(),