703 lines
23 KiB
Dart
703 lines
23 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import '../../../constants/app_constants.dart';
|
||
|
||
/// 时间: 2026-03-26
|
||
/// 功能: 用户体验计划页面
|
||
/// 介绍: 展示用户体验计划的收集信息、权益说明,支持加入/退出
|
||
/// 最新变化: 新建页面
|
||
|
||
class UserPlanPage extends StatefulWidget {
|
||
const UserPlanPage({super.key});
|
||
|
||
@override
|
||
State<UserPlanPage> createState() => _UserPlanPageState();
|
||
}
|
||
|
||
class _UserPlanPageState extends State<UserPlanPage> {
|
||
bool _isJoined = false;
|
||
bool _isLoading = true;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_loadUserPlanStatus();
|
||
}
|
||
|
||
Future<void> _loadUserPlanStatus() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
setState(() {
|
||
_isJoined = prefs.getBool('user_plan_joined') ?? false;
|
||
_isLoading = false;
|
||
});
|
||
}
|
||
|
||
Future<void> _toggleUserPlan(bool value) async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setBool('user_plan_joined', value);
|
||
setState(() {
|
||
_isJoined = value;
|
||
});
|
||
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(value ? '已加入用户体验计划' : '已退出用户体验计划'),
|
||
backgroundColor: value ? AppConstants.primaryColor : Colors.grey[600],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
void _showJoinDialog() {
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||
title: Row(
|
||
children: [
|
||
Icon(Icons.volunteer_activism, color: AppConstants.primaryColor),
|
||
const SizedBox(width: 8),
|
||
const Text('加入用户体验计划'),
|
||
],
|
||
),
|
||
content: const Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'加入后我们将收集以下信息以改善产品体验:',
|
||
style: TextStyle(fontWeight: FontWeight.w500),
|
||
),
|
||
SizedBox(height: 12),
|
||
Text('• 软件使用时长'),
|
||
Text('• 页面停留时间'),
|
||
Text('• 个人喜爱诗词'),
|
||
Text('• 分享次数'),
|
||
Text('• 设备信息(IP归属地、设备型号)'),
|
||
SizedBox(height: 12),
|
||
Text(
|
||
'所有数据将匿名化保存,不会公开。',
|
||
style: TextStyle(color: Colors.grey, fontSize: 12),
|
||
),
|
||
],
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context),
|
||
child: const Text('取消'),
|
||
),
|
||
ElevatedButton(
|
||
onPressed: () {
|
||
Navigator.pop(context);
|
||
_toggleUserPlan(true);
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: AppConstants.primaryColor,
|
||
foregroundColor: Colors.white,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
),
|
||
child: const Text('同意并加入'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showInfoPopup(BuildContext context) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||
title: Row(
|
||
children: [
|
||
Icon(Icons.info, color: AppConstants.primaryColor),
|
||
const SizedBox(width: 8),
|
||
const Text('温馨提示'),
|
||
],
|
||
),
|
||
content: const Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'即使不加入用户体验计划,部分信息仍可能被收集:',
|
||
style: TextStyle(fontWeight: FontWeight.w500),
|
||
),
|
||
SizedBox(height: 12),
|
||
Text('• 运营商可能收集网络连接信息'),
|
||
Text('• 手机厂商可能收集软件使用数据'),
|
||
Text('• 应用商店可能收集下载安装信息'),
|
||
Text('• VPN、抓包等软件可能收集匿名统计数据'),
|
||
SizedBox(height: 12),
|
||
Text(
|
||
'未加入用户体验计划,部分联网数据也会被动上传到软件服务器。',
|
||
style: TextStyle(color: Colors.grey, fontSize: 12),
|
||
),
|
||
],
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context),
|
||
child: const Text('我知道了'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showExitDialog() {
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||
title: const Row(
|
||
children: [
|
||
Icon(Icons.exit_to_app, color: Colors.red),
|
||
SizedBox(width: 8),
|
||
Text('退出用户体验计划'),
|
||
],
|
||
),
|
||
content: const Text('退出后将不再收集您的使用数据,部分功能可能会受限。确定要退出吗?'),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context),
|
||
child: const Text('取消'),
|
||
),
|
||
ElevatedButton(
|
||
onPressed: () {
|
||
Navigator.pop(context);
|
||
_toggleUserPlan(false);
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Colors.red,
|
||
foregroundColor: Colors.white,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
),
|
||
child: const Text('确定退出'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
backgroundColor: const Color(0xFFF5F5F5),
|
||
appBar: AppBar(
|
||
title: Text(
|
||
'用户体验计划',
|
||
style: TextStyle(
|
||
color: AppConstants.primaryColor,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
backgroundColor: Colors.white,
|
||
elevation: 0,
|
||
centerTitle: true,
|
||
leading: IconButton(
|
||
icon: Icon(Icons.arrow_back, color: AppConstants.primaryColor),
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
),
|
||
),
|
||
body: _isLoading
|
||
? const Center(child: CircularProgressIndicator())
|
||
: ListView(
|
||
padding: const EdgeInsets.all(16),
|
||
children: [
|
||
_buildStatusCard(),
|
||
const SizedBox(height: 16),
|
||
_buildCollectInfoSection(),
|
||
const SizedBox(height: 16),
|
||
_buildBenefitsSection(),
|
||
const SizedBox(height: 16),
|
||
_buildPrivacyNotice(),
|
||
const SizedBox(height: 24),
|
||
_buildActionButton(),
|
||
const SizedBox(height: 16),
|
||
_buildBottomIndicator(),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildStatusCard() {
|
||
return Container(
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
colors: [
|
||
AppConstants.primaryColor,
|
||
AppConstants.primaryColor.withBlue(180),
|
||
],
|
||
begin: Alignment.topLeft,
|
||
end: Alignment.bottomRight,
|
||
),
|
||
borderRadius: BorderRadius.circular(16),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: AppConstants.primaryColor.withValues(alpha: 0.3),
|
||
blurRadius: 12,
|
||
offset: const Offset(0, 4),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Icon(
|
||
_isJoined ? Icons.verified : Icons.volunteer_activism_outlined,
|
||
size: 48,
|
||
color: Colors.white,
|
||
),
|
||
const SizedBox(height: 12),
|
||
Text(
|
||
_isJoined ? '已加入用户体验计划' : '加入用户体验计划',
|
||
style: const TextStyle(
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.white,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
_isJoined ? '感谢您的支持,让我们一起变得更好' : '参与产品改进,享受更多权益',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
color: Colors.white.withValues(alpha: 0.9),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white.withValues(alpha: 0.2),
|
||
borderRadius: BorderRadius.circular(20),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(
|
||
_isJoined ? Icons.check_circle : Icons.info_outline,
|
||
size: 16,
|
||
color: Colors.white,
|
||
),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
_isJoined ? '当前状态:已加入' : '当前状态:未加入',
|
||
style: const TextStyle(fontSize: 13, color: Colors.white),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildCollectInfoSection() {
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(16),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withValues(alpha: 0.05),
|
||
blurRadius: 10,
|
||
offset: const Offset(0, 2),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
padding: const EdgeInsets.all(8),
|
||
decoration: BoxDecoration(
|
||
color: Colors.orange.withValues(alpha: 0.1),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Icon(
|
||
Icons.data_usage,
|
||
color: Colors.orange[700],
|
||
size: 20,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
const Text(
|
||
'收集的信息',
|
||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Divider(height: 1),
|
||
_buildInfoItem(Icons.timer, '软件使用时长', '统计每日使用时长及时段'),
|
||
_buildInfoItem(Icons.visibility, '不同页面停留时间', '了解用户关注的页面'),
|
||
_buildInfoItem(Icons.favorite, '个人喜爱诗词', '推荐更精准的内容'),
|
||
_buildInfoItem(Icons.error, '错误信息', '优化软件功能体验'),
|
||
_buildInfoItem(Icons.location_on, 'IP归属地', '群体用户画像生成分析'),
|
||
_buildInfoItem(Icons.devices, '设备信息', '适配更多设备型号'),
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
color: Colors.blue.withValues(alpha: 0.05),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Icon(Icons.security, size: 16, color: Colors.blue[700]),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
'所有数据匿名化保存,不会公开,且定期删除',
|
||
style: TextStyle(fontSize: 12, color: Colors.blue[700]),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildInfoItem(IconData icon, String title, String subtitle) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||
child: Row(
|
||
children: [
|
||
Icon(icon, size: 20, color: Colors.grey[600]),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
title,
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
subtitle,
|
||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildBenefitsSection() {
|
||
final benefits = [
|
||
{'icon': Icons.how_to_vote, 'title': '参与投票', 'desc': '对产品功能进行投票'},
|
||
{'icon': Icons.bar_chart, 'title': '查看统计数据', 'desc': '查看全站使用统计'},
|
||
{'icon': Icons.edit_note, 'title': '开放投稿', 'desc': '投稿您的诗词作品'},
|
||
{'icon': Icons.science, 'title': '体验Beta功能', 'desc': '内测版本抢先体验'},
|
||
{'icon': Icons.bug_report, 'title': '刷新次数', 'desc': '获得更多api调用次数'},
|
||
{'icon': Icons.card_giftcard, 'title': '专属显示', 'desc': '获得不同的显示效果'},
|
||
];
|
||
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(16),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withValues(alpha: 0.05),
|
||
blurRadius: 10,
|
||
offset: const Offset(0, 2),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
padding: const EdgeInsets.all(8),
|
||
decoration: BoxDecoration(
|
||
color: Colors.green.withValues(alpha: 0.1),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Icon(
|
||
Icons.card_membership,
|
||
color: Colors.green[700],
|
||
size: 20,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text(
|
||
'加入后的权益',
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Row(
|
||
children: [
|
||
Text(
|
||
'体验更多受限制的功能',
|
||
style: TextStyle(
|
||
fontSize: 11,
|
||
color: Colors.grey[500],
|
||
),
|
||
),
|
||
const SizedBox(width: 4),
|
||
GestureDetector(
|
||
onTap: () => _showInfoPopup(context),
|
||
child: Icon(
|
||
Icons.info_outline,
|
||
size: 14,
|
||
color: Colors.grey[500],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Divider(height: 1),
|
||
Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: GridView.builder(
|
||
shrinkWrap: true,
|
||
physics: const NeverScrollableScrollPhysics(),
|
||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||
crossAxisCount: 2,
|
||
childAspectRatio: 2.5,
|
||
crossAxisSpacing: 8,
|
||
mainAxisSpacing: 8,
|
||
),
|
||
itemCount: benefits.length,
|
||
itemBuilder: (context, index) {
|
||
final benefit = benefits[index];
|
||
return Container(
|
||
padding: const EdgeInsets.all(8),
|
||
decoration: BoxDecoration(
|
||
color: Colors.grey[50],
|
||
borderRadius: BorderRadius.circular(8),
|
||
border: Border.all(
|
||
color: Colors.grey.withValues(alpha: 0.2),
|
||
),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Icon(
|
||
benefit['icon'] as IconData,
|
||
size: 18,
|
||
color: AppConstants.primaryColor,
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(
|
||
benefit['title'] as String,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
Text(
|
||
benefit['desc'] as String,
|
||
style: TextStyle(
|
||
fontSize: 10,
|
||
color: Colors.grey[500],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildPrivacyNotice() {
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(16),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withValues(alpha: 0.05),
|
||
blurRadius: 10,
|
||
offset: const Offset(0, 2),
|
||
),
|
||
],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Container(
|
||
padding: const EdgeInsets.all(8),
|
||
decoration: BoxDecoration(
|
||
color: Colors.purple.withValues(alpha: 0.1),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Icon(
|
||
Icons.privacy_tip,
|
||
color: Colors.purple[700],
|
||
size: 20,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
const Text(
|
||
'隐私保护说明',
|
||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
_buildPrivacyItem('🔒 数据匿名化处理,无法追溯到个人'),
|
||
_buildPrivacyItem('🛡️ 数据仅用于产品改进,不会出售'),
|
||
_buildPrivacyItem('📋 您可以随时退出计划'),
|
||
_buildPrivacyItem('🗑️ 部分数据将被删除'),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildPrivacyItem(String text) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||
child: Row(
|
||
children: [
|
||
const SizedBox(width: 4),
|
||
Expanded(
|
||
child: Text(
|
||
text,
|
||
style: TextStyle(
|
||
fontSize: 13,
|
||
color: Colors.grey[700],
|
||
height: 1.4,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildActionButton() {
|
||
return Container(
|
||
width: double.infinity,
|
||
height: 50,
|
||
decoration: BoxDecoration(
|
||
gradient: _isJoined
|
||
? null
|
||
: LinearGradient(
|
||
colors: [
|
||
AppConstants.primaryColor,
|
||
AppConstants.primaryColor.withBlue(180),
|
||
],
|
||
),
|
||
color: _isJoined ? Colors.grey[300] : null,
|
||
borderRadius: BorderRadius.circular(12),
|
||
boxShadow: _isJoined
|
||
? null
|
||
: [
|
||
BoxShadow(
|
||
color: AppConstants.primaryColor.withValues(alpha: 0.3),
|
||
blurRadius: 8,
|
||
offset: const Offset(0, 4),
|
||
),
|
||
],
|
||
),
|
||
child: ElevatedButton(
|
||
onPressed: () {
|
||
if (_isJoined) {
|
||
_showExitDialog();
|
||
} else {
|
||
_showJoinDialog();
|
||
}
|
||
},
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: Colors.transparent,
|
||
shadowColor: Colors.transparent,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(
|
||
_isJoined ? Icons.exit_to_app : Icons.volunteer_activism,
|
||
color: _isJoined ? Colors.grey[600] : Colors.white,
|
||
),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
_isJoined ? '退出用户体验计划' : '加入用户体验计划',
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: _isJoined ? Colors.grey[600] : Colors.white,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildBottomIndicator() {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Container(width: 40, height: 1, color: Colors.grey[300]),
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: Text(
|
||
'到底了',
|
||
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
|
||
),
|
||
),
|
||
Container(width: 40, height: 1, color: Colors.grey[300]),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|