Initial commit: Flutter 无书应用项目

This commit is contained in:
Developer
2026-03-30 02:35:31 +08:00
commit 9175ff9905
566 changed files with 103261 additions and 0 deletions

View File

@@ -0,0 +1,702 @@
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': '参与内测版本体验'},
{'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]),
],
),
);
}
}