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>

Konstantin Ullrich committed Jul 11, 2025 at 02:29 UTC 342269a4969b2db5cecdfdd17bc282960603a3e6
10 files changed +189 -14
assets/text/disclaimer_deuro.txt new
+18
@@ -0,0 +1,18 @@
1 +Last Modified: July 10, 2025
2 +
3 +Third-Party Protocol Disclaimer
4 +==============================
5 +
6 +You are now accessing a third-party protocol for savings and earnings in dEURO.
7 +
8 +This protocol is not developed, maintained, or controlled by Cake Wallet. Cake Wallet is not responsible for any third-party protocols or associated third party content accessed through the Services.
9 +
10 +Third party protocols are not investigated, monitored or managed by us. If you decide to access a third-party protocol or to access or use any associated third party content, you do so at your own risk and you should be aware that our terms and policies no longer govern. You should review the applicable third party's terms and policies, including privacy and data gathering practices.
11 +
12 +By proceeding, you acknowledge and agree that:
13 +
14 + - You are using this third-party service at your own risk.
15 + - Cake Wallet does not provide any guarantees, warranties, or support for the use of this protocol.
16 + - Cake Wallet is not responsible for any loss of funds, technical issues, or changes to the third-party protocol.
17 +
18 +Please do your own research and exercise caution accordingly.
lib/entities/preferences_key.dart
+1
@@ -62,6 +62,7 @@ class PreferencesKey {
62 static const mwebNodeUri = 'mwebNodeUri';
63 static const shouldShowReceiveWarning = 'should_show_receive_warning';
64 static const shouldShowYatPopup = 'should_show_yat_popup';
65 + static const shouldShowDEuroDisclaimer = 'should_show_deuro_disclaimer';
66 static const shouldShowRepWarning = 'should_show_rep_warning';
67 static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
68 static const syncModeKey = 'sync_mode';
lib/router.dart
+4
@@ -43,6 +43,7 @@ import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
43 import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
44 import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
45 import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
46 +import 'package:cake_wallet/src/screens/disclaimer/third_party_disclaimer_page.dart';
47 import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
48 import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
49 import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
@@ -418,6 +419,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
419 case Routes.readDisclaimer:
420 return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage(isReadOnly: true));
421
422 + case Routes.readThirdPartyDisclaimer:
423 + return CupertinoPageRoute<void>(builder: (_) => ThirdPartyDisclaimerPage());
424 +
425 case Routes.changeRep:
426 return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
427
lib/routes.dart
+1
@@ -22,6 +22,7 @@ class Routes {
22 static const walletEdit = '/walletEdit';
23 static const disclaimer = '/disclaimer';
24 static const readDisclaimer = '/read_disclaimer';
25 + static const readThirdPartyDisclaimer = '/disclaimer/third_party';
26 static const changeRep = '/change_representative';
27 static const walletList = '/view_model.wallet_list';
28 static const auth = '/auth';
lib/src/screens/disclaimer/disclaimer_page.dart
+8 -12
@@ -34,10 +34,10 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
34 bool _checked = false;
35 String _fileText = '';
36
37 - Future<void> getFileLines() async {
38 - _fileText = await rootBundle.loadString(
39 - isMoneroOnly ? 'assets/text/Monerocom_Terms_of_Use.txt' : 'assets/text/Terms_of_Use.txt');
40 - setState(() {});
37 + void getFileLines() {
38 + final fileName =
39 + isMoneroOnly ? 'assets/text/Monerocom_Terms_of_Use.txt' : 'assets/text/Terms_of_Use.txt';
40 + rootBundle.loadString(fileName).then((text) => setState(() => _fileText = text));
41 }
42
43 @override
@@ -48,8 +48,8 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
48
49 @override
50 Widget build(BuildContext context) {
51 - return WillPopScope(
52 - onWillPop: () async => widget.isReadOnly,
51 + return PopScope(
52 + canPop: widget.isReadOnly,
53 child: Container(
54 color: Theme.of(context).colorScheme.surface,
55 child: Column(
@@ -121,7 +121,7 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
121 decoration: BoxDecoration(
122 gradient: LinearGradient(
123 colors: [
124 - Theme.of(context).colorScheme.surface.withOpacity(0.0),
124 + Theme.of(context).colorScheme.surface.withAlpha(0),
125 Theme.of(context).colorScheme.surface,
126 ],
127 begin: FractionalOffset.topCenter,
@@ -144,11 +144,7 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
144 EdgeInsets.only(left: 24.0, top: 10.0, right: 24.0, bottom: 10.0),
145 child: InkWell(
146 key: ValueKey('disclaimer_check_key'),
147 - onTap: () {
148 - setState(() {
149 - _checked = !_checked;
150 - });
151 - },
147 + onTap: () => setState(() =>_checked = !_checked),
148 child: Row(
149 mainAxisAlignment: MainAxisAlignment.start,
150 children: <Widget>[
lib/src/screens/disclaimer/third_party_disclaimer_page.dart new
+97
@@ -0,0 +1,97 @@
1 +import 'dart:ui';
2 +
3 +import 'package:cake_wallet/src/screens/base_page.dart';
4 +import 'package:flutter/material.dart';
5 +import 'package:flutter/services.dart';
6 +
7 +class ThirdPartyDisclaimerPage extends BasePage {
8 + ThirdPartyDisclaimerPage({this.title = "Disclaimer"});
9 +
10 + @override
11 + final String title;
12 +
13 + final String filePath = 'assets/text/disclaimer_deuro.txt';
14 +
15 + @override
16 + Widget body(BuildContext context) => _ThirdPartyDisclaimerPageBody(filePath: filePath);
17 +}
18 +
19 +class _ThirdPartyDisclaimerPageBody extends StatefulWidget {
20 + _ThirdPartyDisclaimerPageBody({required this.filePath});
21 +
22 + final String filePath;
23 +
24 + @override
25 + _ThirdPartyDisclaimerPageBodyState createState() => _ThirdPartyDisclaimerPageBodyState();
26 +}
27 +
28 +class _ThirdPartyDisclaimerPageBodyState extends State<_ThirdPartyDisclaimerPageBody> {
29 + String _fileText = '';
30 +
31 + void getFileLines() =>
32 + rootBundle.loadString(widget.filePath).then((text) => setState(() => _fileText = text));
33 +
34 + @override
35 + void initState() {
36 + super.initState();
37 + getFileLines();
38 + }
39 +
40 + @override
41 + Widget build(BuildContext context) => Container(
42 + color: Theme.of(context).colorScheme.surface,
43 + child: Column(
44 + children: <Widget>[
45 + SizedBox(height: 10.0),
46 + Expanded(
47 + child: Stack(
48 + children: <Widget>[
49 + SingleChildScrollView(
50 + padding: EdgeInsets.only(left: 24.0, right: 24.0),
51 + child: Column(
52 + children: <Widget>[
53 + Row(
54 + children: <Widget>[
55 + Expanded(
56 + child: Text(
57 + _fileText,
58 + style: Theme.of(context).textTheme.bodySmall,
59 + ))
60 + ],
61 + ),
62 + SizedBox(
63 + height: 16.0,
64 + ),
65 + ],
66 + ),
67 + ),
68 + Container(
69 + alignment: Alignment.bottomCenter,
70 + child: Container(
71 + height: 12,
72 + child: ClipRect(
73 + child: BackdropFilter(
74 + filter: ImageFilter.blur(sigmaX: 0.7, sigmaY: 0.7),
75 + child: Container(
76 + decoration: BoxDecoration(
77 + gradient: LinearGradient(
78 + colors: [
79 + Theme.of(context).colorScheme.surface.withAlpha(0),
80 + Theme.of(context).colorScheme.surface,
81 + ],
82 + begin: FractionalOffset.topCenter,
83 + end: FractionalOffset.bottomCenter,
84 + ),
85 + ),
86 + ),
87 + ),
88 + ),
89 + ),
90 + )
91 + ],
92 + ),
93 + ),
94 + ],
95 + ),
96 + );
97 +}
lib/src/screens/integrations/deuro/savings_page.dart
+5 -1
@@ -139,6 +139,7 @@ class DEuroSavingsPage extends BasePage {
139
140 void _setReactions(BuildContext context, DEuroViewModel dEuroViewModel) {
141 if (_isReactionsSet) return;
142 + if (_dEuroViewModel.isFistTime) _showWelcomeTooltip(context);
143
144 reaction((_) => dEuroViewModel.transaction, (PendingTransaction? tx) async {
145 if (tx == null) return;
@@ -315,7 +316,7 @@ class DEuroSavingsPage extends BasePage {
316 Future<void> _showWelcomeTooltip(BuildContext context) async {
317 if (!context.mounted) return;
318
318 - return showModalBottomSheet<void>(
319 + await showModalBottomSheet<void>(
320 context: context,
321 isScrollControlled: true,
322 builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
@@ -333,8 +334,11 @@ class DEuroSavingsPage extends BasePage {
334 leftButtonText: S.of(context).learn_more,
335 leftActionButtonKey: ValueKey('deuro_page_tooltip_dialog_welcome_learn_more_button_key'),
336 actionLeftButton: () => Navigator.of(bottomSheetContext).pop(), // ToDo
337 + showDisclaimerText: _dEuroViewModel.isFistTime,
338 ),
339 );
340 +
341 + if (_dEuroViewModel.isFistTime) _dEuroViewModel.acceptDisclaimer();
342 }
343
344 Future<void> _showNoEthTooltip(BuildContext context) async {
lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart
+36 -1
@@ -1,6 +1,8 @@
1 import 'package:auto_size_text/auto_size_text.dart';
2 +import 'package:cake_wallet/routes.dart';
3 import 'package:cake_wallet/src/widgets/primary_button.dart';
4 import 'package:cake_wallet/themes/core/material_base_theme.dart';
5 +import 'package:flutter/gestures.dart';
6 import 'package:flutter/material.dart';
7 import 'package:flutter_svg/svg.dart';
8
@@ -29,6 +31,7 @@ class InfoBottomSheet extends BaseBottomSheet {
31 final String? content;
32 final bool isTwoAction;
33 final bool showDontAskMeCheckbox;
34 + final bool showDisclaimerText;
35 final Function(bool)? onCheckboxChanged;
36 final String? actionButtonText;
37 final VoidCallback? actionButton;
@@ -52,6 +55,7 @@ class InfoBottomSheet extends BaseBottomSheet {
55 this.content,
56 this.isTwoAction = false,
57 this.showDontAskMeCheckbox = false,
58 + this.showDisclaimerText = false,
59 this.height = 200,
60 this.onCheckboxChanged,
61 this.actionButtonText,
@@ -106,9 +110,40 @@ class InfoBottomSheet extends BaseBottomSheet {
110 ],
111 ),
112 ),
113 + if (showDisclaimerText)
114 + Padding(
115 + padding: const EdgeInsets.only(top: 20, bottom: 10),
116 + child: RichText(
117 + textAlign: TextAlign.center,
118 + text: TextSpan(
119 + style: Theme.of(context).textTheme.bodyMedium?.copyWith(
120 + color: Theme.of(context).colorScheme.onSurfaceVariant,
121 + ),
122 + children: [
123 + TextSpan(
124 + text: 'By continuing you agree to this ',
125 + style: Theme.of(context).textTheme.bodyMedium?.copyWith(
126 + color: Theme.of(context).colorScheme.onSurfaceVariant,
127 + fontWeight: FontWeight.w500,
128 + ),
129 + ),
130 + TextSpan(
131 + text: 'disclaimer',
132 + style: Theme.of(context).textTheme.bodyMedium?.copyWith(
133 + color: Theme.of(context).colorScheme.onSurfaceVariant,
134 + decoration: TextDecoration.underline,
135 + fontWeight: FontWeight.w700,
136 + ),
137 + recognizer: TapGestureRecognizer()
138 + ..onTap = () => Navigator.pushNamed(context, Routes.readThirdPartyDisclaimer),
139 + ),
140 + ],
141 + ),
142 + ),
143 + ),
144 if (showDontAskMeCheckbox)
145 Padding(
111 - padding: const EdgeInsets.only(left: 34.0),
146 + padding: const EdgeInsets.only(left: 34),
147 child: Row(
148 children: [
149 SimpleCheckbox(onChanged: onCheckboxChanged),
lib/store/settings_store.dart
+13
@@ -87,6 +87,7 @@ abstract class SettingsStoreBase with Store {
87 required Map<WalletType, Node> nodes,
88 required Map<WalletType, Node> powNodes,
89 required this.shouldShowYatPopup,
90 + required this.shouldShowDEuroDisclaimer,
91 required this.shouldShowRepWarning,
92 required this.isBitcoinBuyEnabled,
93 required this.actionlistDisplayMode,
@@ -251,6 +252,11 @@ abstract class SettingsStoreBase with Store {
252 (bool shouldShowYatPopup) =>
253 sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup));
254
255 + reaction(
256 + (_) => shouldShowDEuroDisclaimer,
257 + (bool shouldShowDEuroDisclaimer) =>
258 + sharedPreferences.setBool(PreferencesKey.shouldShowDEuroDisclaimer, shouldShowDEuroDisclaimer));
259 +
260 reaction((_) => shouldShowRepWarning,
261 (bool val) => sharedPreferences.setBool(PreferencesKey.shouldShowRepWarning, val));
262
@@ -648,6 +654,9 @@ abstract class SettingsStoreBase with Store {
654 @observable
655 bool shouldShowYatPopup;
656
657 + @observable
658 + bool shouldShowDEuroDisclaimer;
659 +
660 @observable
661 bool shouldShowRepWarning;
662
@@ -1092,6 +1101,7 @@ abstract class SettingsStoreBase with Store {
1101 final packageInfo = await PackageInfo.fromPlatform();
1102 final deviceName = await _getDeviceName() ?? '';
1103 final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
1104 + final shouldShowDEuroDisclaimer = sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? true;
1105 final shouldShowRepWarning =
1106 sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? true;
1107
@@ -1363,6 +1373,7 @@ abstract class SettingsStoreBase with Store {
1373 initialSyncMode: savedSyncMode,
1374 initialSyncAll: savedSyncAll,
1375 shouldShowYatPopup: shouldShowYatPopup,
1376 + shouldShowDEuroDisclaimer: shouldShowDEuroDisclaimer,
1377 shouldShowRepWarning: shouldShowRepWarning,
1378 initialBuiltinTor: builtinTor,
1379 );
@@ -1489,6 +1500,8 @@ abstract class SettingsStoreBase with Store {
1500 languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
1501 shouldShowYatPopup =
1502 sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
1503 + shouldShowDEuroDisclaimer =
1504 + sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? shouldShowDEuroDisclaimer;
1505 shouldShowRepWarning =
1506 sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? shouldShowRepWarning;
1507 sortBalanceBy = SortBalanceBy
lib/view_model/integrations/deuro_view_model.dart
+6
@@ -38,6 +38,12 @@ abstract class DEuroViewModelBase with Store {
38 @computed
39 bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
40
41 + @computed
42 + bool get isFistTime => _settingsStore.shouldShowDEuroDisclaimer;
43 +
44 + @action
45 + void acceptDisclaimer() => _settingsStore.shouldShowDEuroDisclaimer = false;
46 +
47 @computed
48 String get pendingTransactionFiatAmountFormatted =>
49 isFiatDisabled ? '' : '$pendingTransactionFiatAmount ${fiat.title}';