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
+18
View File
@@ -0,0 +1,18 @@
Last Modified: July 10, 2025
Third-Party Protocol Disclaimer
==============================
You are now accessing a third-party protocol for savings and earnings in dEURO.
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.
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.
By proceeding, you acknowledge and agree that:
- You are using this third-party service at your own risk.
- Cake Wallet does not provide any guarantees, warranties, or support for the use of this protocol.
- Cake Wallet is not responsible for any loss of funds, technical issues, or changes to the third-party protocol.
Please do your own research and exercise caution accordingly.
+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}';