// 首启个人信息保护同意闸(个保法 + 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 createState() => _PrivacyConsentGateState(); } class _PrivacyConsentGateState extends State { bool _needConsent = false; bool _declinedHint = false; @override void initState() { super.initState(); if (needsPrivacyConsent) _check(); } Future _check() async { final prefs = await SharedPreferences.getInstance(); if (mounted && !(prefs.getBool(_kConsentKey) ?? false)) { setState(() => _needConsent = true); } } Future _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)), ]), ]), ), ), ), ]); } }