Skip to content

Commit 59ddfa6

Browse files
authored
feat: add cookie consent (#35)
1 parent 76cd915 commit 59ddfa6

14 files changed

+271
-6
lines changed

lib/app/app_layout.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'package:flutter/material.dart';
22
import 'package:routefly/routefly.dart';
3+
import 'package:zup_app/core/cache.dart';
34
import 'package:zup_app/core/injections.dart';
45
import 'package:zup_app/widgets/app_bottom_navigation_bar.dart';
6+
import 'package:zup_app/widgets/app_cookies_consent_widget.dart';
57
import 'package:zup_app/widgets/app_footer.dart';
68
import 'package:zup_app/widgets/app_header/app_header.dart';
79
import 'package:zup_core/mixins/device_info_mixin.dart';
@@ -17,11 +19,38 @@ class _AppPageState extends State<AppPage> with DeviceInfoMixin {
1719
bool get shouldShowBottomNavigationBar => isTabletSize(context);
1820

1921
final double appBarHeight = 85;
22+
final cache = inject<Cache>();
2023

2124
final ScrollController appScrollController = inject<ScrollController>(
2225
instanceName: InjectInstanceNames.appScrollController,
2326
);
2427

28+
@override
29+
void initState() {
30+
super.initState();
31+
WidgetsBinding.instance.addPostFrameCallback((_) {
32+
late OverlayEntry overlayEntry;
33+
34+
overlayEntry = OverlayEntry(
35+
builder: (context) {
36+
return Align(
37+
alignment: Alignment.bottomRight,
38+
child: Padding(
39+
padding: const EdgeInsets.all(20),
40+
child: SelectionArea(
41+
child: AppCookieConsentWidget(
42+
onAccept: () => overlayEntry.remove(),
43+
),
44+
),
45+
),
46+
);
47+
},
48+
);
49+
50+
if (cache.getCookiesConsentStatus() == null) Overlay.of(context).insert(overlayEntry);
51+
});
52+
}
53+
2554
@override
2655
Widget build(BuildContext context) {
2756
return SelectionArea(

lib/core/cache.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ enum CacheKey {
88
hidingClosedPositions,
99
depositSettings,
1010
poolSearchSettings,
11+
areCookiesConsented,
1112
isTestnetMode;
1213

1314
String get key => name;
@@ -50,6 +51,14 @@ class Cache {
5051
await _cache.setString(CacheKey.poolSearchSettings.key, jsonEncode(settings.toJson()));
5152
}
5253

54+
Future<void> saveCookiesConsentStatus({required bool status}) async {
55+
await _cache.setBool(CacheKey.areCookiesConsented.key, status);
56+
}
57+
58+
bool? getCookiesConsentStatus() {
59+
return _cache.getBool(CacheKey.areCookiesConsented.key);
60+
}
61+
5362
PoolSearchSettingsDto getPoolSearchSettings() {
5463
final cache = _cache.getString(CacheKey.poolSearchSettings.key) ?? "{}";
5564

lib/l10n/en.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
"@@locale": "en",
33
"twentyFourHours": "24h",
44
"appFooterTermsOfUse": "Terms of Use",
5-
"appFooterPrivacyPolicy": "Privacy Policy",
5+
"privacyPolicy": "Privacy Policy",
66
"appFooterContactUs": "Contact Us",
7+
"appCookiesConsentWidgetDescription": "We use cookies to ensure that we give you the best experience on our app. By continuing to use Zup Protocol, you agree to our",
78
"appFooterDocs": "Docs",
89
"appFooterFAQ": "FAQ",
10+
"understood": "Understood",
911
"depositPageShowingOnlyPoolsWithMoreThan": "Showing only liquidity pools with more than {minLiquidity}.",
1012
"@depositPageShowingOnlyPoolsWithMoreThan": {
1113
"placeholders": {

lib/l10n/gen/app_localizations.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,24 @@ abstract class S {
105105
/// **'Terms of Use'**
106106
String get appFooterTermsOfUse;
107107

108-
/// No description provided for @appFooterPrivacyPolicy.
108+
/// No description provided for @privacyPolicy.
109109
///
110110
/// In en, this message translates to:
111111
/// **'Privacy Policy'**
112-
String get appFooterPrivacyPolicy;
112+
String get privacyPolicy;
113113

114114
/// No description provided for @appFooterContactUs.
115115
///
116116
/// In en, this message translates to:
117117
/// **'Contact Us'**
118118
String get appFooterContactUs;
119119

120+
/// No description provided for @appCookiesConsentWidgetDescription.
121+
///
122+
/// In en, this message translates to:
123+
/// **'We use cookies to ensure that we give you the best experience on our app. By continuing to use Zup Protocol, you agree to our'**
124+
String get appCookiesConsentWidgetDescription;
125+
120126
/// No description provided for @appFooterDocs.
121127
///
122128
/// In en, this message translates to:
@@ -129,6 +135,12 @@ abstract class S {
129135
/// **'FAQ'**
130136
String get appFooterFAQ;
131137

138+
/// No description provided for @understood.
139+
///
140+
/// In en, this message translates to:
141+
/// **'Understood'**
142+
String get understood;
143+
132144
/// No description provided for @depositPageShowingOnlyPoolsWithMoreThan.
133145
///
134146
/// In en, this message translates to:

lib/l10n/gen/app_localizations_en.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,24 @@ class SEn extends S {
1515
String get appFooterTermsOfUse => 'Terms of Use';
1616

1717
@override
18-
String get appFooterPrivacyPolicy => 'Privacy Policy';
18+
String get privacyPolicy => 'Privacy Policy';
1919

2020
@override
2121
String get appFooterContactUs => 'Contact Us';
2222

23+
@override
24+
String get appCookiesConsentWidgetDescription =>
25+
'We use cookies to ensure that we give you the best experience on our app. By continuing to use Zup Protocol, you agree to our';
26+
2327
@override
2428
String get appFooterDocs => 'Docs';
2529

2630
@override
2731
String get appFooterFAQ => 'FAQ';
2832

33+
@override
34+
String get understood => 'Understood';
35+
2936
@override
3037
String depositPageShowingOnlyPoolsWithMoreThan(
3138
{required String minLiquidity}) {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:zup_app/core/cache.dart';
3+
import 'package:zup_app/core/injections.dart';
4+
import 'package:zup_app/core/zup_links.dart';
5+
import 'package:zup_app/l10n/gen/app_localizations.dart';
6+
import 'package:zup_ui_kit/zup_ui_kit.dart';
7+
8+
class AppCookieConsentWidget extends StatelessWidget {
9+
AppCookieConsentWidget({super.key, required this.onAccept});
10+
11+
final void Function() onAccept;
12+
13+
final zupLinks = inject<ZupLinks>();
14+
final cache = inject<Cache>();
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
return Container(
19+
padding: const EdgeInsets.all(15),
20+
decoration: BoxDecoration(
21+
color: ZupColors.white,
22+
border: Border.all(color: ZupColors.gray5),
23+
borderRadius: BorderRadius.circular(12),
24+
),
25+
width: 300,
26+
child: Material(
27+
color: Colors.transparent,
28+
child: Column(
29+
mainAxisSize: MainAxisSize.min,
30+
children: [
31+
Text.rich(
32+
TextSpan(children: [
33+
TextSpan(
34+
text: S.of(context).appCookiesConsentWidgetDescription,
35+
style: const TextStyle(color: ZupColors.gray, fontSize: 14),
36+
),
37+
const TextSpan(text: " "),
38+
WidgetSpan(
39+
child: SizedBox(
40+
height: 17,
41+
child: TextButton(
42+
key: const Key("privacy-policy-button"),
43+
onPressed: () {
44+
zupLinks.launchPrivacyPolicy();
45+
},
46+
style: ButtonStyle(
47+
visualDensity: VisualDensity.compact,
48+
minimumSize: WidgetStateProperty.all(Size.zero),
49+
splashFactory: NoSplash.splashFactory,
50+
backgroundColor: WidgetStateProperty.all(Colors.transparent),
51+
overlayColor: WidgetStateProperty.all(Colors.transparent),
52+
padding: WidgetStateProperty.all(EdgeInsets.zero),
53+
),
54+
child: Text(
55+
S.of(context).privacyPolicy,
56+
style: const TextStyle(decoration: TextDecoration.underline, fontSize: 14),
57+
),
58+
),
59+
),
60+
style: const TextStyle(color: ZupColors.black, fontSize: 14),
61+
),
62+
]),
63+
),
64+
const SizedBox(height: 20),
65+
ZupPrimaryButton(
66+
key: const Key("accept-cookies-button"),
67+
height: 40,
68+
title: S.of(context).understood,
69+
hoverElevation: 0,
70+
backgroundColor: ZupColors.brand6,
71+
foregroundColor: ZupColors.brand,
72+
onPressed: () {
73+
onAccept();
74+
cache.saveCookiesConsentStatus(status: true);
75+
},
76+
alignCenter: true,
77+
width: double.maxFinite,
78+
),
79+
],
80+
),
81+
),
82+
);
83+
}
84+
}

lib/widgets/app_footer.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ enum _AppFooterButton {
3232
_AppFooterButton.twitter => "",
3333
_AppFooterButton.telegram => "",
3434
_AppFooterButton.termsOfUse => S.of(context).appFooterTermsOfUse,
35-
_AppFooterButton.privacyPolicy => S.of(context).appFooterPrivacyPolicy,
35+
_AppFooterButton.privacyPolicy => S.of(context).privacyPolicy,
3636
_AppFooterButton.docs => S.of(context).appFooterDocs,
3737
_AppFooterButton.faq => S.of(context).appFooterFAQ,
3838
_AppFooterButton.contactUs => S.of(context).appFooterContactUs

test/app/app_layout_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:routefly/routefly.dart';
77
import 'package:web3kit/web3kit.dart';
88
import 'package:zup_app/app/app_cubit/app_cubit.dart';
99
import 'package:zup_app/app/app_layout.dart';
10+
import 'package:zup_app/core/cache.dart';
1011
import 'package:zup_app/core/enums/networks.dart';
1112
import 'package:zup_app/core/enums/zup_navigator_paths.dart';
1213
import 'package:zup_app/core/injections.dart';
@@ -20,15 +21,18 @@ import '../mocks.dart';
2021

2122
void main() {
2223
late AppCubit appCubit;
24+
late Cache cache;
2325

2426
setUp(() async {
2527
await Web3Kit.initializeForTest();
2628

2729
appCubit = AppCubitMock();
30+
cache = CacheMock();
2831

2932
inject.registerFactory<ZupLinks>(() => ZupLinksMock());
3033
inject.registerFactory<ZupNavigator>(() => ZupNavigator());
3134
inject.registerFactory<AppCubit>(() => appCubit);
35+
inject.registerFactory<Cache>(() => cache);
3236
inject.registerFactory<ScrollController>(
3337
() => ScrollController(),
3438
instanceName: InjectInstanceNames.appScrollController,
@@ -38,6 +42,7 @@ void main() {
3842
when(() => appCubit.state).thenReturn(const AppState.standard());
3943
when(() => appCubit.isTestnetMode).thenReturn(false);
4044
when(() => appCubit.stream).thenAnswer((_) => const Stream.empty());
45+
when(() => cache.getCookiesConsentStatus()).thenReturn(true);
4146
});
4247

4348
Future<DeviceBuilder> goldenBuilder({bool isMobile = false}) async => await goldenDeviceBuilder(
@@ -84,4 +89,22 @@ void main() {
8489

8590
await tester.drag(find.byKey(const Key("screen")).first, const Offset(0, -500));
8691
});
92+
93+
zGoldenTest(
94+
"When initializing, and the cookies consent is not saved in the cache, it should display a cookie consent overlay",
95+
goldenFileName: "app_layout_cookie_consent_null",
96+
(tester) async {
97+
when(() => cache.getCookiesConsentStatus()).thenReturn(null);
98+
await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper());
99+
},
100+
);
101+
102+
zGoldenTest(
103+
"When initializing, and a cookies consent is saved in the cache (either true or false), it should not display a cookie consent overlay",
104+
goldenFileName: "app_layout_cookie_consent_not_null",
105+
(tester) async {
106+
when(() => cache.getCookiesConsentStatus()).thenReturn(false);
107+
await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper());
108+
},
109+
);
87110
}
38.2 KB
Loading
56.6 KB
Loading

0 commit comments

Comments
 (0)