Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions lib/app/app_layout.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:routefly/routefly.dart';
import 'package:zup_app/core/cache.dart';
import 'package:zup_app/core/injections.dart';
import 'package:zup_app/widgets/app_bottom_navigation_bar.dart';
import 'package:zup_app/widgets/app_cookies_consent_widget.dart';
import 'package:zup_app/widgets/app_footer.dart';
import 'package:zup_app/widgets/app_header/app_header.dart';
import 'package:zup_core/mixins/device_info_mixin.dart';
Expand All @@ -17,11 +19,38 @@ class _AppPageState extends State<AppPage> with DeviceInfoMixin {
bool get shouldShowBottomNavigationBar => isTabletSize(context);

final double appBarHeight = 85;
final cache = inject<Cache>();

final ScrollController appScrollController = inject<ScrollController>(
instanceName: InjectInstanceNames.appScrollController,
);

@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
late OverlayEntry overlayEntry;

overlayEntry = OverlayEntry(
builder: (context) {
return Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(20),
child: SelectionArea(
child: AppCookieConsentWidget(
onAccept: () => overlayEntry.remove(),
),
),
),
);
},
);

if (cache.getCookiesConsentStatus() == null) Overlay.of(context).insert(overlayEntry);
});
}

@override
Widget build(BuildContext context) {
return SelectionArea(
Expand Down
9 changes: 9 additions & 0 deletions lib/core/cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ enum CacheKey {
hidingClosedPositions,
depositSettings,
poolSearchSettings,
areCookiesConsented,
isTestnetMode;

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

Future<void> saveCookiesConsentStatus({required bool status}) async {
await _cache.setBool(CacheKey.areCookiesConsented.key, status);
}

bool? getCookiesConsentStatus() {
return _cache.getBool(CacheKey.areCookiesConsented.key);
}

PoolSearchSettingsDto getPoolSearchSettings() {
final cache = _cache.getString(CacheKey.poolSearchSettings.key) ?? "{}";

Expand Down
4 changes: 3 additions & 1 deletion lib/l10n/en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
"@@locale": "en",
"twentyFourHours": "24h",
"appFooterTermsOfUse": "Terms of Use",
"appFooterPrivacyPolicy": "Privacy Policy",
"privacyPolicy": "Privacy Policy",
"appFooterContactUs": "Contact Us",
"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",
"appFooterDocs": "Docs",
"appFooterFAQ": "FAQ",
"understood": "Understood",
"depositPageShowingOnlyPoolsWithMoreThan": "Showing only liquidity pools with more than {minLiquidity}.",
"@depositPageShowingOnlyPoolsWithMoreThan": {
"placeholders": {
Expand Down
16 changes: 14 additions & 2 deletions lib/l10n/gen/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,24 @@ abstract class S {
/// **'Terms of Use'**
String get appFooterTermsOfUse;

/// No description provided for @appFooterPrivacyPolicy.
/// No description provided for @privacyPolicy.
///
/// In en, this message translates to:
/// **'Privacy Policy'**
String get appFooterPrivacyPolicy;
String get privacyPolicy;

/// No description provided for @appFooterContactUs.
///
/// In en, this message translates to:
/// **'Contact Us'**
String get appFooterContactUs;

/// No description provided for @appCookiesConsentWidgetDescription.
///
/// In en, this message translates to:
/// **'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'**
String get appCookiesConsentWidgetDescription;

/// No description provided for @appFooterDocs.
///
/// In en, this message translates to:
Expand All @@ -129,6 +135,12 @@ abstract class S {
/// **'FAQ'**
String get appFooterFAQ;

/// No description provided for @understood.
///
/// In en, this message translates to:
/// **'Understood'**
String get understood;

/// No description provided for @depositPageShowingOnlyPoolsWithMoreThan.
///
/// In en, this message translates to:
Expand Down
9 changes: 8 additions & 1 deletion lib/l10n/gen/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,24 @@ class SEn extends S {
String get appFooterTermsOfUse => 'Terms of Use';

@override
String get appFooterPrivacyPolicy => 'Privacy Policy';
String get privacyPolicy => 'Privacy Policy';

@override
String get appFooterContactUs => 'Contact Us';

@override
String get 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';

@override
String get appFooterDocs => 'Docs';

@override
String get appFooterFAQ => 'FAQ';

@override
String get understood => 'Understood';

@override
String depositPageShowingOnlyPoolsWithMoreThan(
{required String minLiquidity}) {
Expand Down
84 changes: 84 additions & 0 deletions lib/widgets/app_cookies_consent_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:zup_app/core/cache.dart';
import 'package:zup_app/core/injections.dart';
import 'package:zup_app/core/zup_links.dart';
import 'package:zup_app/l10n/gen/app_localizations.dart';
import 'package:zup_ui_kit/zup_ui_kit.dart';

class AppCookieConsentWidget extends StatelessWidget {
AppCookieConsentWidget({super.key, required this.onAccept});

final void Function() onAccept;

final zupLinks = inject<ZupLinks>();
final cache = inject<Cache>();

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: ZupColors.white,
border: Border.all(color: ZupColors.gray5),
borderRadius: BorderRadius.circular(12),
),
width: 300,
child: Material(
color: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text.rich(
TextSpan(children: [
TextSpan(
text: S.of(context).appCookiesConsentWidgetDescription,
style: const TextStyle(color: ZupColors.gray, fontSize: 14),
),
const TextSpan(text: " "),
WidgetSpan(
child: SizedBox(
height: 17,
child: TextButton(
key: const Key("privacy-policy-button"),
onPressed: () {
zupLinks.launchPrivacyPolicy();
},
style: ButtonStyle(
visualDensity: VisualDensity.compact,
minimumSize: WidgetStateProperty.all(Size.zero),
splashFactory: NoSplash.splashFactory,
backgroundColor: WidgetStateProperty.all(Colors.transparent),
overlayColor: WidgetStateProperty.all(Colors.transparent),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
child: Text(
S.of(context).privacyPolicy,
style: const TextStyle(decoration: TextDecoration.underline, fontSize: 14),
),
),
),
style: const TextStyle(color: ZupColors.black, fontSize: 14),
),
]),
),
const SizedBox(height: 20),
ZupPrimaryButton(
key: const Key("accept-cookies-button"),
height: 40,
title: S.of(context).understood,
hoverElevation: 0,
backgroundColor: ZupColors.brand6,
foregroundColor: ZupColors.brand,
onPressed: () {
onAccept();
cache.saveCookiesConsentStatus(status: true);
},
alignCenter: true,
width: double.maxFinite,
),
],
),
),
);
}
}
2 changes: 1 addition & 1 deletion lib/widgets/app_footer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ enum _AppFooterButton {
_AppFooterButton.twitter => "",
_AppFooterButton.telegram => "",
_AppFooterButton.termsOfUse => S.of(context).appFooterTermsOfUse,
_AppFooterButton.privacyPolicy => S.of(context).appFooterPrivacyPolicy,
_AppFooterButton.privacyPolicy => S.of(context).privacyPolicy,
_AppFooterButton.docs => S.of(context).appFooterDocs,
_AppFooterButton.faq => S.of(context).appFooterFAQ,
_AppFooterButton.contactUs => S.of(context).appFooterContactUs
Expand Down
23 changes: 23 additions & 0 deletions test/app/app_layout_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:routefly/routefly.dart';
import 'package:web3kit/web3kit.dart';
import 'package:zup_app/app/app_cubit/app_cubit.dart';
import 'package:zup_app/app/app_layout.dart';
import 'package:zup_app/core/cache.dart';
import 'package:zup_app/core/enums/networks.dart';
import 'package:zup_app/core/enums/zup_navigator_paths.dart';
import 'package:zup_app/core/injections.dart';
Expand All @@ -20,15 +21,18 @@ import '../mocks.dart';

void main() {
late AppCubit appCubit;
late Cache cache;

setUp(() async {
await Web3Kit.initializeForTest();

appCubit = AppCubitMock();
cache = CacheMock();

inject.registerFactory<ZupLinks>(() => ZupLinksMock());
inject.registerFactory<ZupNavigator>(() => ZupNavigator());
inject.registerFactory<AppCubit>(() => appCubit);
inject.registerFactory<Cache>(() => cache);
inject.registerFactory<ScrollController>(
() => ScrollController(),
instanceName: InjectInstanceNames.appScrollController,
Expand All @@ -38,6 +42,7 @@ void main() {
when(() => appCubit.state).thenReturn(const AppState.standard());
when(() => appCubit.isTestnetMode).thenReturn(false);
when(() => appCubit.stream).thenAnswer((_) => const Stream.empty());
when(() => cache.getCookiesConsentStatus()).thenReturn(true);
});

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

await tester.drag(find.byKey(const Key("screen")).first, const Offset(0, -500));
});

zGoldenTest(
"When initializing, and the cookies consent is not saved in the cache, it should display a cookie consent overlay",
goldenFileName: "app_layout_cookie_consent_null",
(tester) async {
when(() => cache.getCookiesConsentStatus()).thenReturn(null);
await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper());
},
);

zGoldenTest(
"When initializing, and a cookies consent is saved in the cache (either true or false), it should not display a cookie consent overlay",
goldenFileName: "app_layout_cookie_consent_not_null",
(tester) async {
when(() => cache.getCookiesConsentStatus()).thenReturn(false);
await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper());
},
);
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions test/core/cache_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,22 @@ void main() {
expect(result, result);
verify(() => sharedPreferencesWithCache.getString(CacheKey.poolSearchSettings.key)).called(1);
});

test("when calling 'saveCookiesConsentStatus' it should save under the correct key", () {
when(() => sharedPreferencesWithCache.setBool(any(), any())).thenAnswer((_) async => true);

const status = true;
sut.saveCookiesConsentStatus(status: status);

verify(() => sharedPreferencesWithCache.setBool(CacheKey.areCookiesConsented.key, status)).called(1);
});

test("when calling 'getCookiesConsentStatus' it should get under the correct key", () {
when(() => sharedPreferencesWithCache.getBool(any())).thenReturn(true);

final result = sut.getCookiesConsentStatus();

expect(result, true);
verify(() => sharedPreferencesWithCache.getBool(CacheKey.areCookiesConsented.key)).called(1);
});
}
Loading
Loading