diff --git a/assets/text/disclaimer_deuro.txt b/assets/text/disclaimer_deuro.txt new file mode 100644 index 000000000..4d03a8a38 --- /dev/null +++ b/assets/text/disclaimer_deuro.txt @@ -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. diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 0f3c1fe48..cda299ec2 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -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'; diff --git a/lib/router.dart b/lib/router.dart index 00b0c5bb9..0df583362 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -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 createRoute(RouteSettings settings) { case Routes.readDisclaimer: return CupertinoPageRoute(builder: (_) => DisclaimerPage(isReadOnly: true)); + case Routes.readThirdPartyDisclaimer: + return CupertinoPageRoute(builder: (_) => ThirdPartyDisclaimerPage()); + case Routes.changeRep: return CupertinoPageRoute(builder: (_) => getIt.get()); diff --git a/lib/routes.dart b/lib/routes.dart index 6ff083bda..e18dc07b5 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -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'; diff --git a/lib/src/screens/disclaimer/disclaimer_page.dart b/lib/src/screens/disclaimer/disclaimer_page.dart index 6358e4b32..ef7d42369 100644 --- a/lib/src/screens/disclaimer/disclaimer_page.dart +++ b/lib/src/screens/disclaimer/disclaimer_page.dart @@ -34,10 +34,10 @@ class DisclaimerBodyState extends State { bool _checked = false; String _fileText = ''; - Future 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 { @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 { 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 { 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: [ diff --git a/lib/src/screens/disclaimer/third_party_disclaimer_page.dart b/lib/src/screens/disclaimer/third_party_disclaimer_page.dart new file mode 100644 index 000000000..eb7b9966a --- /dev/null +++ b/lib/src/screens/disclaimer/third_party_disclaimer_page.dart @@ -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: [ + SizedBox(height: 10.0), + Expanded( + child: Stack( + children: [ + SingleChildScrollView( + padding: EdgeInsets.only(left: 24.0, right: 24.0), + child: Column( + children: [ + Row( + children: [ + 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, + ), + ), + ), + ), + ), + ), + ) + ], + ), + ), + ], + ), + ); +} diff --git a/lib/src/screens/integrations/deuro/savings_page.dart b/lib/src/screens/integrations/deuro/savings_page.dart index db2b3182a..e928127b2 100644 --- a/lib/src/screens/integrations/deuro/savings_page.dart +++ b/lib/src/screens/integrations/deuro/savings_page.dart @@ -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 _showWelcomeTooltip(BuildContext context) async { if (!context.mounted) return; - return showModalBottomSheet( + await showModalBottomSheet( 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 _showNoEthTooltip(BuildContext context) async { diff --git a/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart index 8b02b7fbb..1c4cdff91 100644 --- a/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart @@ -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), diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 958f682fe..a714b72d1 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -87,6 +87,7 @@ abstract class SettingsStoreBase with Store { required Map nodes, required Map 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 diff --git a/lib/view_model/integrations/deuro_view_model.dart b/lib/view_model/integrations/deuro_view_model.dart index 34376c610..2462db1a6 100644 --- a/lib/view_model/integrations/deuro_view_model.dart +++ b/lib/view_model/integrations/deuro_view_model.dart @@ -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}';