3rd-party-disclaimer (#2372)

* 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

* feat: add third-party disclaimer support and UI enhancements

* feat: add dEuro disclaimer functionality

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Konstantin Ullrich
2025-07-11 03:29:54 +03:00
committed by GitHub
co-authored by Omar Hatem
parent 3ba2e4283e
commit 342269a496
10 changed files with 189 additions and 14 deletions
+1
View File
@@ -62,6 +62,7 @@ class PreferencesKey {
static const mwebNodeUri = 'mwebNodeUri';
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
static const shouldShowDEuroDisclaimer = 'should_show_deuro_disclaimer';
static const shouldShowRepWarning = 'should_show_rep_warning';
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
static const syncModeKey = 'sync_mode';
+4
View File
@@ -43,6 +43,7 @@ import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
import 'package:cake_wallet/src/screens/disclaimer/third_party_disclaimer_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
@@ -418,6 +419,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.readDisclaimer:
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage(isReadOnly: true));
case Routes.readThirdPartyDisclaimer:
return CupertinoPageRoute<void>(builder: (_) => ThirdPartyDisclaimerPage());
case Routes.changeRep:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
+1
View File
@@ -22,6 +22,7 @@ class Routes {
static const walletEdit = '/walletEdit';
static const disclaimer = '/disclaimer';
static const readDisclaimer = '/read_disclaimer';
static const readThirdPartyDisclaimer = '/disclaimer/third_party';
static const changeRep = '/change_representative';
static const walletList = '/view_model.wallet_list';
static const auth = '/auth';
@@ -34,10 +34,10 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
bool _checked = false;
String _fileText = '';
Future<void> getFileLines() async {
_fileText = await rootBundle.loadString(
isMoneroOnly ? 'assets/text/Monerocom_Terms_of_Use.txt' : 'assets/text/Terms_of_Use.txt');
setState(() {});
void getFileLines() {
final fileName =
isMoneroOnly ? 'assets/text/Monerocom_Terms_of_Use.txt' : 'assets/text/Terms_of_Use.txt';
rootBundle.loadString(fileName).then((text) => setState(() => _fileText = text));
}
@override
@@ -48,8 +48,8 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => widget.isReadOnly,
return PopScope(
canPop: widget.isReadOnly,
child: Container(
color: Theme.of(context).colorScheme.surface,
child: Column(
@@ -121,7 +121,7 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context).colorScheme.surface.withOpacity(0.0),
Theme.of(context).colorScheme.surface.withAlpha(0),
Theme.of(context).colorScheme.surface,
],
begin: FractionalOffset.topCenter,
@@ -144,11 +144,7 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
EdgeInsets.only(left: 24.0, top: 10.0, right: 24.0, bottom: 10.0),
child: InkWell(
key: ValueKey('disclaimer_check_key'),
onTap: () {
setState(() {
_checked = !_checked;
});
},
onTap: () => setState(() =>_checked = !_checked),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
@@ -0,0 +1,97 @@
import 'dart:ui';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ThirdPartyDisclaimerPage extends BasePage {
ThirdPartyDisclaimerPage({this.title = "Disclaimer"});
@override
final String title;
final String filePath = 'assets/text/disclaimer_deuro.txt';
@override
Widget body(BuildContext context) => _ThirdPartyDisclaimerPageBody(filePath: filePath);
}
class _ThirdPartyDisclaimerPageBody extends StatefulWidget {
_ThirdPartyDisclaimerPageBody({required this.filePath});
final String filePath;
@override
_ThirdPartyDisclaimerPageBodyState createState() => _ThirdPartyDisclaimerPageBodyState();
}
class _ThirdPartyDisclaimerPageBodyState extends State<_ThirdPartyDisclaimerPageBody> {
String _fileText = '';
void getFileLines() =>
rootBundle.loadString(widget.filePath).then((text) => setState(() => _fileText = text));
@override
void initState() {
super.initState();
getFileLines();
}
@override
Widget build(BuildContext context) => Container(
color: Theme.of(context).colorScheme.surface,
child: Column(
children: <Widget>[
SizedBox(height: 10.0),
Expanded(
child: Stack(
children: <Widget>[
SingleChildScrollView(
padding: EdgeInsets.only(left: 24.0, right: 24.0),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Text(
_fileText,
style: Theme.of(context).textTheme.bodySmall,
))
],
),
SizedBox(
height: 16.0,
),
],
),
),
Container(
alignment: Alignment.bottomCenter,
child: Container(
height: 12,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 0.7, sigmaY: 0.7),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context).colorScheme.surface.withAlpha(0),
Theme.of(context).colorScheme.surface,
],
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
),
),
),
),
),
),
)
],
),
),
],
),
);
}
@@ -139,6 +139,7 @@ class DEuroSavingsPage extends BasePage {
void _setReactions(BuildContext context, DEuroViewModel dEuroViewModel) {
if (_isReactionsSet) return;
if (_dEuroViewModel.isFistTime) _showWelcomeTooltip(context);
reaction((_) => dEuroViewModel.transaction, (PendingTransaction? tx) async {
if (tx == null) return;
@@ -315,7 +316,7 @@ class DEuroSavingsPage extends BasePage {
Future<void> _showWelcomeTooltip(BuildContext context) async {
if (!context.mounted) return;
return showModalBottomSheet<void>(
await showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
@@ -333,8 +334,11 @@ class DEuroSavingsPage extends BasePage {
leftButtonText: S.of(context).learn_more,
leftActionButtonKey: ValueKey('deuro_page_tooltip_dialog_welcome_learn_more_button_key'),
actionLeftButton: () => Navigator.of(bottomSheetContext).pop(), // ToDo
showDisclaimerText: _dEuroViewModel.isFistTime,
),
);
if (_dEuroViewModel.isFistTime) _dEuroViewModel.acceptDisclaimer();
}
Future<void> _showNoEthTooltip(BuildContext context) async {
@@ -1,6 +1,8 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
@@ -29,6 +31,7 @@ class InfoBottomSheet extends BaseBottomSheet {
final String? content;
final bool isTwoAction;
final bool showDontAskMeCheckbox;
final bool showDisclaimerText;
final Function(bool)? onCheckboxChanged;
final String? actionButtonText;
final VoidCallback? actionButton;
@@ -52,6 +55,7 @@ class InfoBottomSheet extends BaseBottomSheet {
this.content,
this.isTwoAction = false,
this.showDontAskMeCheckbox = false,
this.showDisclaimerText = false,
this.height = 200,
this.onCheckboxChanged,
this.actionButtonText,
@@ -106,9 +110,40 @@ class InfoBottomSheet extends BaseBottomSheet {
],
),
),
if (showDisclaimerText)
Padding(
padding: const EdgeInsets.only(top: 20, bottom: 10),
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
children: [
TextSpan(
text: 'By continuing you agree to this ',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
TextSpan(
text: 'disclaimer',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
decoration: TextDecoration.underline,
fontWeight: FontWeight.w700,
),
recognizer: TapGestureRecognizer()
..onTap = () => Navigator.pushNamed(context, Routes.readThirdPartyDisclaimer),
),
],
),
),
),
if (showDontAskMeCheckbox)
Padding(
padding: const EdgeInsets.only(left: 34.0),
padding: const EdgeInsets.only(left: 34),
child: Row(
children: [
SimpleCheckbox(onChanged: onCheckboxChanged),
+13
View File
@@ -87,6 +87,7 @@ abstract class SettingsStoreBase with Store {
required Map<WalletType, Node> nodes,
required Map<WalletType, Node> powNodes,
required this.shouldShowYatPopup,
required this.shouldShowDEuroDisclaimer,
required this.shouldShowRepWarning,
required this.isBitcoinBuyEnabled,
required this.actionlistDisplayMode,
@@ -251,6 +252,11 @@ abstract class SettingsStoreBase with Store {
(bool shouldShowYatPopup) =>
sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup));
reaction(
(_) => shouldShowDEuroDisclaimer,
(bool shouldShowDEuroDisclaimer) =>
sharedPreferences.setBool(PreferencesKey.shouldShowDEuroDisclaimer, shouldShowDEuroDisclaimer));
reaction((_) => shouldShowRepWarning,
(bool val) => sharedPreferences.setBool(PreferencesKey.shouldShowRepWarning, val));
@@ -648,6 +654,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool shouldShowYatPopup;
@observable
bool shouldShowDEuroDisclaimer;
@observable
bool shouldShowRepWarning;
@@ -1092,6 +1101,7 @@ abstract class SettingsStoreBase with Store {
final packageInfo = await PackageInfo.fromPlatform();
final deviceName = await _getDeviceName() ?? '';
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
final shouldShowDEuroDisclaimer = sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? true;
final shouldShowRepWarning =
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? true;
@@ -1363,6 +1373,7 @@ abstract class SettingsStoreBase with Store {
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup,
shouldShowDEuroDisclaimer: shouldShowDEuroDisclaimer,
shouldShowRepWarning: shouldShowRepWarning,
initialBuiltinTor: builtinTor,
);
@@ -1489,6 +1500,8 @@ abstract class SettingsStoreBase with Store {
languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
shouldShowYatPopup =
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
shouldShowDEuroDisclaimer =
sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? shouldShowDEuroDisclaimer;
shouldShowRepWarning =
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? shouldShowRepWarning;
sortBalanceBy = SortBalanceBy
@@ -38,6 +38,12 @@ abstract class DEuroViewModelBase with Store {
@computed
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
@computed
bool get isFistTime => _settingsStore.shouldShowDEuroDisclaimer;
@action
void acceptDisclaimer() => _settingsStore.shouldShowDEuroDisclaimer = false;
@computed
String get pendingTransactionFiatAmountFormatted =>
isFiatDisabled ? '' : '$pendingTransactionFiatAmount ${fiat.title}';