From 61c72b22007a662c9c01a74f529f427a9dd9065a Mon Sep 17 00:00:00 2001 From: esefxdz Date: Sat, 8 Aug 2026 13:49:00 +0300 Subject: [PATCH] feat: first-launch data disclosure dialog (v3.2.1+24) --- CHANGELOG.md | 7 ++ lib/components/data_disclosure_dialog.dart | 104 +++++++++++++++++++++ lib/screens/main_screen.dart | 17 +++- pubspec.yaml | 2 +- 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 lib/components/data_disclosure_dialog.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ece6a..d318cd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ Versions follow [semver](https://semver.org/): MAJOR.MINOR.PATCH --- +## [3.2.1] — 2026-08-08 + +### Google Play compliance +- Added first-launch data disclosure dialog ("Before you connect") explaining + that connection duration and data usage are logged for quota enforcement, and + that traffic content is never monitored. + ## [3.2.0] — 2026-08-08 ### DPI hardening diff --git a/lib/components/data_disclosure_dialog.dart b/lib/components/data_disclosure_dialog.dart new file mode 100644 index 0000000..eec31b6 --- /dev/null +++ b/lib/components/data_disclosure_dialog.dart @@ -0,0 +1,104 @@ +// lib/components/data_disclosure_dialog.dart +// First-launch data disclosure dialog — Google Play Data safety section requirement. +// Shows once per install; preference persisted in SharedPreferences. + +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:revoltvpn/logic/app_colors.dart'; + +class DataDisclosureDialog { + DataDisclosureDialog._(); + + static const _key = 'data_disclosure_accepted'; + + /// Shows the disclosure dialog if the user hasn't accepted it yet. + /// Call once after the main screen is mounted (post-frame callback). + static Future showIfFirstLaunch(BuildContext context) async { + final prefs = await SharedPreferences.getInstance(); + if (prefs.getBool(_key) == true) return; // Already accepted. + + if (!context.mounted) return; + + await showDialog( + context: context, + barrierDismissible: false, // Must tap Continue — no tap-outside dismiss. + builder: (_) => _DisclosureDialog( + onContinue: () { + prefs.setBool(_key, true); + Navigator.of(context).pop(); // Dismiss the dialog. + }, + ), + ); + } +} + +class _DisclosureDialog extends StatelessWidget { + final VoidCallback onContinue; + + const _DisclosureDialog({required this.onContinue}); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: AppColors.bgCard, + insetPadding: const EdgeInsets.symmetric(horizontal: 28, vertical: 40), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 28, 24, 20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title + const Text( + 'Before you connect', + style: TextStyle( + color: AppColors.textWhite, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 16), + + // Body + const Text( + 'Revolt VPN creates a secure VPN tunnel to protect your ' + 'traffic. To enforce your data/time quota, we log your ' + 'connection duration and data usage, linked to your ' + 'account. We do not log or monitor the content of your ' + 'traffic. We do not share your data with third parties.' + 'If you have more questions, visit our website.', + style: TextStyle( + color: AppColors.textMuted, + fontSize: 14, + height: 1.5, + ), + ), + const SizedBox(height: 24), + + // Continue button — full-width, accent + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: onContinue, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.accent, + foregroundColor: AppColors.bgDeep, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + textStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + child: const Text('Continue'), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index b048bb9..38d3be3 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:revoltvpn/logic/app_colors.dart'; import 'package:revoltvpn/components/connect_button.dart'; +import 'package:revoltvpn/components/data_disclosure_dialog.dart'; import 'package:revoltvpn/components/sidebar_drawer.dart'; import 'package:revoltvpn/components/timer_box.dart'; import 'package:revoltvpn/components/speed_box.dart'; @@ -9,9 +10,23 @@ import 'package:revoltvpn/components/support_button.dart'; import 'package:revoltvpn/components/rain.dart'; import 'package:revoltvpn/components/lightning.dart'; -class MainScreen extends StatelessWidget { +class MainScreen extends StatefulWidget { const MainScreen({super.key}); + @override + State createState() => _MainScreenState(); +} + +class _MainScreenState extends State { + @override + void initState() { + super.initState(); + // Show data disclosure on first launch — Google Play Data safety requirement. + WidgetsBinding.instance.addPostFrameCallback((_) { + DataDisclosureDialog.showIfFirstLaunch(context); + }); + } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/pubspec.yaml b/pubspec.yaml index 99d3bf1..b8867c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 3.2.0+23 +version: 3.2.1+24 environment: sdk: ">=3.0.0 <4.0.0"