feat: first-launch data disclosure dialog (v3.2.1+24)
This commit is contained in:
1 parent
54f949ca8e
commit
61c72b2200
4 files changed
+128
-2
No files matched your search
@@ -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
|
## [3.2.0] — 2026-08-08
|
||||||
|
|
||||||
### DPI hardening
|
### DPI hardening
|
||||||
|
|||||||
@@ -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<void> 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'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:revoltvpn/logic/app_colors.dart';
|
import 'package:revoltvpn/logic/app_colors.dart';
|
||||||
import 'package:revoltvpn/components/connect_button.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/sidebar_drawer.dart';
|
||||||
import 'package:revoltvpn/components/timer_box.dart';
|
import 'package:revoltvpn/components/timer_box.dart';
|
||||||
import 'package:revoltvpn/components/speed_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/rain.dart';
|
||||||
import 'package:revoltvpn/components/lightning.dart';
|
import 'package:revoltvpn/components/lightning.dart';
|
||||||
|
|
||||||
class MainScreen extends StatelessWidget {
|
class MainScreen extends StatefulWidget {
|
||||||
const MainScreen({super.key});
|
const MainScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MainScreen> createState() => _MainScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MainScreenState extends State<MainScreen> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Show data disclosure on first launch — Google Play Data safety requirement.
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
DataDisclosureDialog.showIfFirstLaunch(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
+1
-1
@@ -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.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 3.2.0+23
|
version: 3.2.1+24
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|||||||
Reference in new issue
Block a user