flutter架 构项目基础
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:new_learn/core/providers/privacy_provider.dart';
|
||||
import 'package:new_learn/core/storage/privacy_storage.dart';
|
||||
import 'package:new_learn/features/auth/legal_documents.dart';
|
||||
|
||||
/// ============================================================
|
||||
/// 【PrivacyConsentPage】首次启动隐私协议页
|
||||
///
|
||||
/// 作用:
|
||||
/// 1. 用户首次打开 App 时,先展示隐私说明;
|
||||
/// 2. 用户点击"同意并继续"后,保存本地状态并放行进入主流程;
|
||||
/// 3. 用户点击"暂不同意"时,维持拦截状态(Android 可直接退出应用)。
|
||||
///
|
||||
/// 这里是"应用启动级"协议页,不等同于登录页里的协议勾选:
|
||||
/// - 启动协议:未登录前就要先拦;
|
||||
/// - 登录页协议:属于业务表单校验,发生在点击登录时。
|
||||
/// ============================================================
|
||||
class PrivacyConsentPage extends StatefulWidget {
|
||||
const PrivacyConsentPage({super.key});
|
||||
|
||||
@override
|
||||
State<PrivacyConsentPage> createState() => _PrivacyConsentPageState();
|
||||
}
|
||||
|
||||
class _PrivacyConsentPageState extends State<PrivacyConsentPage> {
|
||||
bool _submitting = false;
|
||||
_LegalDocumentData? _activeDocument;
|
||||
|
||||
Future<void> _openLegalDocument({
|
||||
required String title,
|
||||
required String content,
|
||||
}) async {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_activeDocument = _LegalDocumentData(
|
||||
title: title,
|
||||
content: content,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _accept() async {
|
||||
if (_submitting) return;
|
||||
setState(() => _submitting = true);
|
||||
await PrivacyStorage.instance.saveAccepted(true);
|
||||
// 更新全局门禁状态:启动 Gate 和 go_router 守卫都会同步放行。
|
||||
privacyAcceptedNotifier.value = true;
|
||||
if (mounted) {
|
||||
setState(() => _submitting = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _decline() async {
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('若您不同意隐私政策,将无法使用我们的服务'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(true),
|
||||
child: const Text('重新考虑'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||
child: const Text('退出'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// 选"重新考虑"或直接关闭弹窗,都继续停留在当前隐私页。
|
||||
if (result != false) return;
|
||||
|
||||
await PrivacyStorage.instance.saveAccepted(false);
|
||||
privacyAcceptedNotifier.value = false;
|
||||
// Android 上按常见做法直接退出应用;其他平台调用也安全,无法退出时只是不生效。
|
||||
await SystemNavigator.pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final activeDocument = _activeDocument;
|
||||
|
||||
if (activeDocument != null) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (didPop) return;
|
||||
setState(() => _activeDocument = null);
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F7FA),
|
||||
appBar: AppBar(
|
||||
title: Text(activeDocument.title),
|
||||
leading: IconButton(
|
||||
onPressed: () => setState(() => _activeDocument = null),
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: SelectableText(
|
||||
activeDocument.content,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
height: 1.8,
|
||||
color: Color(0xFF323233),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'欢迎使用爱学蝶变',
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'为保障你的个人信息安全,在继续使用前,请先阅读并同意《用户协议》和《隐私政策》。同意后我们才会继续进入 App 的登录和业务流程。',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: const Color(0xFF4B5563),
|
||||
height: 1.7,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
'1. 我们会在必要范围内使用账号信息、设备信息,用于登录鉴权、学习记录同步、功能服务提供。\n\n'
|
||||
'2. 未经你的明确同意,我们不会将你的个人信息用于与学习无关的用途。\n\n'
|
||||
'3. 你可以通过《用户协议》和《隐私政策》了解信息收集、使用、存储与保护方式。\n\n'
|
||||
'4. 若你不同意本协议内容,将无法继续进入 App 主流程。',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: const Color(0xFF475569),
|
||||
height: 1.8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
'点击“同意并继续”即表示你已阅读并同意',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: const Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => _openLegalDocument(
|
||||
title: kUserAgreementTitle,
|
||||
content: kUserAgreementContent,
|
||||
),
|
||||
child: const Text(
|
||||
'《用户协议》',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF2563EB),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'和',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: const Color(0xFF64748B),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => _openLegalDocument(
|
||||
title: kPrivacyPolicyTitle,
|
||||
content: kPrivacyPolicyContent,
|
||||
),
|
||||
child: const Text(
|
||||
'《隐私政策》',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF2563EB),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _submitting ? null : _decline,
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size.fromHeight(48),
|
||||
),
|
||||
child: const Text('暂不同意'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: FilledButton(
|
||||
onPressed: _submitting ? null : _accept,
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size.fromHeight(48),
|
||||
),
|
||||
child: _submitting
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: const Text('同意并继续'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LegalDocumentData {
|
||||
const _LegalDocumentData({
|
||||
required this.title,
|
||||
required this.content,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String content;
|
||||
}
|
||||
Reference in New Issue
Block a user