b740cc6edf
个保法/审核 5.1.1:移动端首启全屏隐私同意(链接隐私政策/用户协议, 同意持久化;不同意 Android 退出、iOS 留提示);我的页 iOS 形态新增 「注销账号」(sheet 说明 + 预填 mailto 至 support@51yanmei.com)。 关于页隐私政策/服务条款链接已有,桌面/Web 零改动。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
146 lines
5.4 KiB
Dart
146 lines
5.4 KiB
Dart
// 首启个人信息保护同意闸(个保法 + App Store 中国区审核 5.1.1)。
|
||
//
|
||
// 移动端(iOS/Android)首次启动时全屏遮罩弹出:说明收集范围 + 可点
|
||
// 《隐私政策》《用户协议》+ 同意/不同意。同意持久化(SharedPreferences)
|
||
// 后不再出现;不同意时 Android 退出应用,iOS 留在弹层并提示(苹果 HIG
|
||
// 禁止程序性退出)。桌面与 Web 完全不弹。
|
||
// 挂载点:MaterialApp.router 的 builder(覆盖全部路由,早于登录页交互)。
|
||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||
import 'dart:io' show Platform;
|
||
|
||
import 'package:flutter/gestures.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import 'package:url_launcher/url_launcher.dart';
|
||
|
||
import '../core/config/app_info.dart';
|
||
import '../core/config/store_compliance.dart';
|
||
import '../core/theme/app_dims.g.dart';
|
||
import '../core/theme/context_tokens.dart';
|
||
import 'ds/ds_atoms.dart';
|
||
|
||
const _kConsentKey = 'privacy_consent_v1';
|
||
|
||
class PrivacyConsentGate extends StatefulWidget {
|
||
final Widget child;
|
||
const PrivacyConsentGate({super.key, required this.child});
|
||
|
||
@override
|
||
State<PrivacyConsentGate> createState() => _PrivacyConsentGateState();
|
||
}
|
||
|
||
class _PrivacyConsentGateState extends State<PrivacyConsentGate> {
|
||
bool _needConsent = false;
|
||
bool _declinedHint = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
if (needsPrivacyConsent) _check();
|
||
}
|
||
|
||
Future<void> _check() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
if (mounted && !(prefs.getBool(_kConsentKey) ?? false)) {
|
||
setState(() => _needConsent = true);
|
||
}
|
||
}
|
||
|
||
Future<void> _agree() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setBool(_kConsentKey, true);
|
||
if (mounted) setState(() => _needConsent = false);
|
||
}
|
||
|
||
void _decline() {
|
||
// Android 惯例:不同意即退出;iOS 禁止程序性退出,留提示。
|
||
if (!kIsWeb && Platform.isAndroid) {
|
||
SystemNavigator.pop();
|
||
} else {
|
||
setState(() => _declinedHint = true);
|
||
}
|
||
}
|
||
|
||
void _open(String path) {
|
||
final base = AppInfo.website.isNotEmpty
|
||
? AppInfo.website
|
||
: 'https://jiu.51yanmei.com';
|
||
launchUrl(Uri.parse('$base$path'), mode: LaunchMode.externalApplication);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (!_needConsent) return widget.child;
|
||
final t = context.tokens;
|
||
final link = TextStyle(
|
||
fontSize: AppDims.fsSm, color: t.primary, fontWeight: FontWeight.w600);
|
||
return Stack(children: [
|
||
widget.child,
|
||
const ModalBarrier(
|
||
dismissible: false, color: Colors.black54), // ds-ignore 遮罩固定色
|
||
Center(
|
||
child: Material(
|
||
color: Colors.transparent,
|
||
child: Container(
|
||
width: 320,
|
||
margin: const EdgeInsets.symmetric(horizontal: 24),
|
||
padding: const EdgeInsets.fromLTRB(22, 22, 22, 18),
|
||
decoration: BoxDecoration(
|
||
color: t.surface,
|
||
borderRadius: BorderRadius.circular(AppDims.rLg),
|
||
border: Border.all(color: t.border),
|
||
),
|
||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||
Text('个人信息保护提示',
|
||
style: TextStyle(
|
||
fontSize: AppDims.fsH2,
|
||
fontWeight: FontWeight.w700,
|
||
color: t.heading)),
|
||
const SizedBox(height: 12),
|
||
Text.rich(
|
||
TextSpan(
|
||
style: TextStyle(
|
||
fontSize: AppDims.fsSm, color: t.text, height: 1.7),
|
||
children: [
|
||
const TextSpan(
|
||
text: '感谢使用岩美酒库。为向您提供门店库存管理服务,'
|
||
'我们仅收集必要的账号信息、设备信息与操作日志,'
|
||
'用于多端数据同步与账号安全保障。详情请阅读'),
|
||
TextSpan(
|
||
text: '《隐私政策》',
|
||
style: link,
|
||
recognizer: TapGestureRecognizer()
|
||
..onTap = () => _open('/privacy/')),
|
||
const TextSpan(text: '与'),
|
||
TextSpan(
|
||
text: '《用户协议》',
|
||
style: link,
|
||
recognizer: TapGestureRecognizer()
|
||
..onTap = () => _open('/terms/')),
|
||
const TextSpan(text: '。点击「同意并继续」即表示您已阅读并同意上述条款。'),
|
||
],
|
||
),
|
||
),
|
||
if (_declinedHint) ...[
|
||
const SizedBox(height: 10),
|
||
Text('需同意后方可使用岩美酒库',
|
||
style: TextStyle(fontSize: AppDims.fsXs, color: t.warn)),
|
||
],
|
||
const SizedBox(height: 16),
|
||
Row(children: [
|
||
Expanded(
|
||
child: DsButton('不同意', onPressed: _decline)),
|
||
const SizedBox(width: 10),
|
||
Expanded(
|
||
child: DsButton('同意并继续',
|
||
variant: DsBtnVariant.primary, onPressed: _agree)),
|
||
]),
|
||
]),
|
||
),
|
||
),
|
||
),
|
||
]);
|
||
}
|
||
}
|