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,421 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../constants/app_constants.dart';
import './widgets.dart';
import '../../home/home-load.dart';
/// 时间: 2026-03-26
/// 功能: 功能设置页面
/// 介绍: 应用功能开关和设置项管理
/// 最新变化: 添加自动刷新功能
class AppFunSettingsPage extends StatefulWidget {
const AppFunSettingsPage({super.key});
@override
State<AppFunSettingsPage> createState() => _AppFunSettingsPageState();
}
class _AppFunSettingsPageState extends State<AppFunSettingsPage> {
bool _autoRefreshEnabled = false;
bool _debugInfoEnabled = false;
bool _soundEnabled = true;
bool _vibrationEnabled = true;
bool _darkModeEnabled = false;
bool _notificationEnabled = true;
int _cacheSize = 128;
static const String _autoRefreshKey = 'auto_refresh_enabled';
static const String _debugInfoKey = 'debug_info_enabled';
@override
void initState() {
super.initState();
_loadSettings();
}
Future<void> _loadSettings() async {
final prefs = await SharedPreferences.getInstance();
if (mounted) {
setState(() {
_autoRefreshEnabled = prefs.getBool(_autoRefreshKey) ?? false;
_debugInfoEnabled = prefs.getBool(_debugInfoKey) ?? false;
});
}
}
Future<void> _setAutoRefresh(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_autoRefreshKey, value);
await AutoRefreshManager().setEnabled(value);
if (mounted) {
setState(() {
_autoRefreshEnabled = value;
});
}
}
Future<void> _setDebugInfo(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_debugInfoKey, value);
await DebugInfoManager().setEnabled(value);
if (mounted) {
setState(() {
_debugInfoEnabled = value;
});
}
}
@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: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildSettingsGroup('基础功能', [
_buildSwitchItem(
'自动刷新',
'首页诗句自动刷新5s ',
Icons.refresh,
_autoRefreshEnabled,
_setAutoRefresh,
),
_buildSwitchItem(
'调式信息',
'开启后可加载更多信息',
Icons.bug_report,
_debugInfoEnabled,
_setDebugInfo,
),
_buildSwitchItem(
'预加载',
'开启后优先使用本地缓存,\n减少与服务器的通信次数',
//关闭后,优先使用云端数据,无延迟,但刷新缓慢
Icons.notifications_active,
_notificationEnabled,
(value) => setState(() => _notificationEnabled = value),
),
]),
const SizedBox(height: 16),
_buildSettingsGroup('显示设置', [
_buildSwitchItem(
'Tap沉浸光感',
'开启后底栏显示类iOS26 风格\nbeta实验功能',
Icons.dark_mode,
_darkModeEnabled,
(value) => setState(() => _darkModeEnabled = value),
),
_buildFontSliderItem(),
]),
const SizedBox(height: 16),
_buildSettingsGroup('交互反馈', [
_buildSwitchItem(
'全局Tips开关',
'显示一些使用技巧',
Icons.volume_up,
_soundEnabled,
(value) => setState(() => _soundEnabled = value),
),
_buildSwitchItem(
'声音反馈',
'操作时播放提示音',
Icons.volume_up,
_soundEnabled,
(value) => setState(() => _soundEnabled = value),
),
_buildSwitchItem(
'震动反馈',
'操作时震动提示',
Icons.vibration,
_vibrationEnabled,
(value) => setState(() => _vibrationEnabled = value),
),
]),
const SizedBox(height: 16),
_buildSettingsGroup('高级设置', [
_buildActionItem(
'Beta开关 关闭软件',
'开发者选项',
Icons.developer_mode,
() => _showSnackBar('调试模式开发中'),
),
_buildActionItem(
'重置设置',
'恢复默认设置',
Icons.restore,
() => _showResetDialog(),
),
_buildActionItem(
'调试模式',
'开发者选项',
Icons.developer_mode,
() => _showSnackBar('调试模式开发中'),
),
]),
const SizedBox(height: 32),
_buildVersionInfo(),
],
),
);
}
Widget _buildSettingsGroup(String title, List<Widget> items) {
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.fromLTRB(16, 16, 16, 8),
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppConstants.primaryColor,
),
),
),
...items,
],
),
);
}
Widget _buildSwitchItem(
String title,
String subtitle,
IconData icon,
bool value,
ValueChanged<bool> onChanged,
) {
return ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: AppConstants.primaryColor, size: 20),
),
title: Text(
title,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
subtitle: Text(
subtitle,
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
trailing: Switch(
value: value,
onChanged: onChanged,
activeThumbColor: AppConstants.primaryColor,
),
);
}
Widget _buildFontSliderItem() {
return ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.widgets, color: AppConstants.primaryColor, size: 20),
),
title: const Text(
'桌面卡片',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
subtitle: Text(
'使用帮助',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
trailing: Icon(Icons.chevron_right, color: Colors.grey[400]),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const WidgetsPage()),
);
},
);
}
Widget _buildCacheItem() {
return ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.storage, color: AppConstants.primaryColor, size: 20),
),
title: const Text(
'缓存大小',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
subtitle: Text(
'$_cacheSize MB',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
trailing: Icon(Icons.chevron_right, color: Colors.grey[400]),
);
}
Widget _buildActionItem(
String title,
String subtitle,
IconData icon,
VoidCallback onTap,
) {
return ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: AppConstants.primaryColor, size: 20),
),
title: Text(
title,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
subtitle: Text(
subtitle,
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
trailing: Icon(Icons.chevron_right, color: Colors.grey[400]),
onTap: onTap,
);
}
Widget _buildVersionInfo() {
return Container(
padding: const EdgeInsets.all(20),
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(
children: [
Icon(
Icons.settings_suggest,
size: 40,
color: AppConstants.primaryColor,
),
const SizedBox(height: 12),
Text(
AppConstants.appName,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(
'版本 ${AppConstants.appVersion}',
style: TextStyle(fontSize: 13, color: Colors.grey[600]),
),
],
),
);
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
}
void _showClearCacheDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('清除缓存'),
content: const Text('确定要清除所有缓存数据吗?这不会影响您的笔记和收藏。'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
setState(() {
_cacheSize = 0;
});
_showSnackBar('缓存已清除');
},
child: Text('确定', style: TextStyle(color: Colors.red[400])),
),
],
),
);
}
void _showResetDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('重置设置'),
content: const Text('确定要恢复默认设置吗?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('取消'),
),
TextButton(
onPressed: () async {
Navigator.of(context).pop();
await _setAutoRefresh(false);
await _setDebugInfo(false);
setState(() {
_soundEnabled = true;
_vibrationEnabled = true;
_darkModeEnabled = false;
_notificationEnabled = true;
});
_showSnackBar('已恢复默认设置');
},
child: Text('确定', style: TextStyle(color: Colors.red[400])),
),
],
),
);
}
}

View File

@@ -0,0 +1,574 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../constants/app_constants.dart';
/// 时间: 2026-03-26
/// 功能: 了解我们页面
/// 介绍: 展示开发者信息、团队信息、官网和备案号
/// 最新变化: 新建页面
class LearnUsPage extends StatelessWidget {
const LearnUsPage({super.key});
@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: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildHeaderCard(),
const SizedBox(height: 16),
_buildOfficialSiteCard(),
const SizedBox(height: 16),
_buildDeveloperCard(),
const SizedBox(height: 16),
_buildTeamCard(),
const SizedBox(height: 16),
_buildIcpCard(context),
const SizedBox(height: 24),
_buildBottomIndicator(),
],
),
);
}
Widget _buildHeaderCard() {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: const [
Color.fromARGB(255, 143, 73, 228),
Color(0xFF6200EE),
Color(0xFF3700B3),
],
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: Row(
children: [
Container(
width: 70,
height: 70,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(18),
),
child: const Center(
child: Text('📖', style: TextStyle(fontSize: 36)),
),
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'情景诗词',
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 6),
Text(
'在诗词里旅行,在文化中生长',
style: TextStyle(
fontSize: 13,
color: Colors.white.withValues(alpha: 0.85),
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.code,
size: 14,
color: Colors.white.withValues(alpha: 0.9),
),
const SizedBox(width: 6),
Text(
'版本 ${AppConstants.appVersion}',
style: const TextStyle(
fontSize: 12,
color: Colors.white,
),
),
],
),
),
],
),
),
],
),
);
}
Widget _buildOfficialSiteCard() {
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.blue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.language,
color: Colors.blue[700],
size: 20,
),
),
const SizedBox(width: 12),
const Text(
'官方网站',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
),
const Divider(height: 1),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'访问我们的官方网站了解更多信息',
style: TextStyle(fontSize: 13, color: Colors.grey),
),
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.grey.withValues(alpha: 0.2),
),
),
child: Row(
children: [
Icon(
Icons.link,
size: 16,
color: AppConstants.primaryColor,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'https://*****.github.io',
style: TextStyle(
fontSize: 14,
color: AppConstants.primaryColor,
),
),
),
],
),
),
],
),
),
],
),
);
}
Widget _buildDeveloperCard() {
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.business,
color: Colors.green[700],
size: 20,
),
),
const SizedBox(width: 12),
const Text(
'开发者',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
),
const Divider(height: 1),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor.withValues(alpha: 0.1),
AppConstants.primaryColor.withValues(alpha: 0.05),
],
),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text('🏢', style: TextStyle(fontSize: 24)),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'微风暴工作室',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'专注诗词文化传播',
style: TextStyle(fontSize: 13, color: Colors.grey[600]),
),
],
),
),
],
),
),
],
),
);
}
Widget _buildTeamCard() {
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.purple.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.group, color: Colors.purple[700], size: 20),
),
const SizedBox(width: 12),
const Text(
'团队信息(Team)',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
),
const Divider(height: 1),
_buildTeamMember('💻', '程序设计', '无书的书🤡', '尽毕生所学,取天下之诗集,只为逗她一笑'),
_buildTeamMember('🎨', 'UI/UX/Testing', 'Ayk', '....'),
_buildTeamMember('⚙️', '后端', '伯乐不相马', '真的吗,还是做不到吗?'),
_buildTeamMember('🔧', '技术支持', '闲言app', '闲言app原班人马打造'),
],
),
);
}
Widget _buildTeamMember(
String emoji,
String role,
String name,
String signature,
) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(10),
),
child: Center(
child: Text(emoji, style: const TextStyle(fontSize: 20)),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
role,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
name,
style: TextStyle(
fontSize: 11,
color: AppConstants.primaryColor,
),
),
),
],
),
const SizedBox(height: 4),
Text(
signature,
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
),
],
),
);
}
Widget _buildIcpCard(BuildContext context) {
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.verified_user,
color: Colors.orange[700],
size: 20,
),
),
const SizedBox(width: 12),
const Text(
'ICP备案信息',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
),
const Divider(height: 1),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'APP核准备案号',
style: TextStyle(fontSize: 13, color: Colors.grey),
),
const SizedBox(height: 12),
InkWell(
onTap: () => _copyIcpNumber(context),
borderRadius: BorderRadius.circular(8),
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppConstants.primaryColor.withValues(alpha: 0.3),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.content_copy,
size: 16,
color: AppConstants.primaryColor,
),
const SizedBox(width: 8),
Text(
'滇ICP备2022000863号-15A',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppConstants.primaryColor,
),
),
],
),
),
),
const SizedBox(height: 8),
Text(
'💡 点击备案号可复制',
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
),
],
),
),
],
),
);
}
void _copyIcpNumber(BuildContext context) {
Clipboard.setData(const ClipboardData(text: '滇ICP备2022000863号-15A'));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Row(
children: [
Icon(Icons.check_circle, color: Colors.white, size: 20),
SizedBox(width: 8),
Text('备案号已复制到剪贴板'),
],
),
backgroundColor: AppConstants.primaryColor,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
duration: const Duration(seconds: 2),
),
);
}
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]),
],
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,532 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../constants/app_constants.dart';
/// 时间: 2026-03-26
/// 功能: 隐私政策与软件协议页面
/// 介绍: 展示应用的隐私政策和用户协议
/// 最新变化: 将协议内容抽取成公共组件,支持其他页面调用
/// 公共协议内容组件 - 隐私政策
class PrivacyPolicyContent extends StatelessWidget {
const PrivacyPolicyContent({super.key});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle('关于情景诗词与隐私的声明'),
_buildUpdateDate('2026.3.26'),
const SizedBox(height: 24),
_buildParagraph(
'情景诗词 是由 *****工作室 (以下简称"我们")为您提供的,用于在诗词里旅行,在文化中生长的应用。本隐私声明由我们为处理您的个人信息而制定。',
),
const SizedBox(height: 16),
_buildParagraph(
'我们非常重视您的个人信息和隐私保护,将会按照法律要求和业界成熟的安全标准,为您的个人信息提供相应的安全保护措施。',
),
const SizedBox(height: 24),
_buildSectionTitle('1. 我们如何收集和使用您的个人信息'),
const SizedBox(height: 16),
_buildParagraph(
'我们仅在有合法性基础的情形下才会使用您的个人信息。根据适用的法律,我们可能会基于您的同意、为履行/订立您与我们的合同所必需、履行法定义务所必需等合法性基础,使用您的个人信息。',
),
const SizedBox(height: 16),
_buildSubSectionTitle('1.1 基于履行法定义务或其他法律法规规定的情形'),
const SizedBox(height: 12),
_buildParagraph('为了实现应用功能,在获取您的同意后我们需要收集您的以下信息:'),
const SizedBox(height: 12),
_buildBulletPoint('本地笔记数据,用于保存您创建的诗词笔记'),
_buildBulletPoint('点赞记录,用于展示您收藏的诗词'),
_buildBulletPoint('历史记录,用于记录您浏览过的诗词'),
const SizedBox(height: 24),
_buildSectionTitle('2. 设备权限调用'),
const SizedBox(height: 16),
_buildPermissionItem('存储权限', '用于保存和读取您的笔记、收藏等本地数据'),
_buildPermissionItem('网络权限', '用于获取诗词内容和更新应用信息'),
const SizedBox(height: 24),
_buildSectionTitle('3. 管理您的个人信息'),
const SizedBox(height: 16),
_buildParagraph(
'如您对您的数据主体权利有进一步要求或存在任何疑问、意见或建议,可通过本声明中"如何联系我们"章节中所述方式与我们取得联系,并行使您的相关权利。',
),
const SizedBox(height: 24),
_buildSectionTitle('4. 信息存储地点及期限'),
const SizedBox(height: 16),
_buildParagraph('4.1 我们承诺,除法律法规另有规定外,我们对您的信息的保存期限应当为实现处理目的所必要的最短时间。'),
const SizedBox(height: 12),
_buildParagraph('4.2 上述信息将会传输并保存至中国境内的服务器。'),
const SizedBox(height: 24),
_buildSectionTitle('5. 如何联系我们'),
const SizedBox(height: 16),
_buildParagraph('您可通过以下方式联系我们,并行使您的相关权利,我们会尽快回复。'),
const SizedBox(height: 12),
_buildContactInfo('开发者', '*****工作室'),
_buildContactInfo('地址', '云南昆明'),
_buildContactInfo('邮箱', '********@outlook.com'),
const SizedBox(height: 16),
_buildParagraph(
'如果您对我们的回复不满意,特别是当个人信息处理行为损害了您的合法权益时,您还可以通过向有管辖权的人民法院提起诉讼、向行业自律协会或政府相关管理机构投诉等外部途径进行解决。您也可以向我们了解可能适用的相关投诉途径的信息。',
),
const SizedBox(height: 24),
_buildEffectiveDate('2026年3月26日'),
_buildBottomIndicator(),
],
),
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
);
}
Widget _buildSubSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
);
}
Widget _buildParagraph(String text) {
return Text(
text,
style: TextStyle(fontSize: 14, color: Colors.grey[700], height: 1.6),
);
}
Widget _buildBulletPoint(String text) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 6,
height: 6,
margin: const EdgeInsets.only(top: 6, left: 8, right: 12),
decoration: BoxDecoration(
color: AppConstants.primaryColor,
shape: BoxShape.circle,
),
),
Expanded(
child: Text(
text,
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
height: 1.5,
),
),
),
],
),
);
}
Widget _buildPermissionItem(String title, String desc) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.withValues(alpha: 0.2)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(desc, style: TextStyle(fontSize: 13, color: Colors.grey[600])),
],
),
);
}
Widget _buildContactInfo(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
SizedBox(
width: 80,
child: Text(
'$label',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
),
Expanded(
child: Text(
value,
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
),
),
],
),
);
}
Widget _buildUpdateDate(String date) {
return Text(
'更新日期:$date',
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
);
}
Widget _buildEffectiveDate(String date) {
return Text(
'生效日期:$date',
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
);
}
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]),
],
),
);
}
}
/// 公共协议内容组件 - 用户协议
class UserAgreementContent extends StatelessWidget {
const UserAgreementContent({super.key});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle('用户协议'),
_buildEffectiveDate('2026-03-26'),
const SizedBox(height: 24),
_buildParagraph(
'欢迎使用 情景诗词(以下简称"本App")。本用户协议由个人开发者 *****工作室 制定。用户在下载、安装、注册、登录、使用本App服务前应当仔细阅读并充分理解本协议内容。用户开始使用本App即视为同意本协议全部条款。',
),
const SizedBox(height: 24),
_buildSectionTitle('一、协议适用范围'),
const SizedBox(height: 16),
_buildParagraph(
'本协议适用于用户与开发者 *****工作室 之间,关于用户使用 情景诗词 产品及服务所建立的权利义务关系。',
),
const SizedBox(height: 24),
_buildSectionTitle('二、服务内容'),
const SizedBox(height: 16),
_buildParagraph(
'情景诗词 主要提供诗词浏览、收藏、笔记记录、历史记录等功能。具体服务内容以应用内实际展示为准,开发者可根据产品运营情况进行功能优化、升级和调整。',
),
const SizedBox(height: 24),
_buildSectionTitle('三、账号与安全'),
const SizedBox(height: 16),
_buildParagraph(
'您应当确保使用本App时遵守相关法律法规。本App目前不需要注册账号您的本地数据存储在您的设备上请妥善保管您的设备。',
),
const SizedBox(height: 24),
_buildSectionTitle('四、用户行为规范'),
const SizedBox(height: 16),
_buildParagraph(
'用户在使用本App过程中应当遵守中华人民共和国法律法规不得利用本App从事违法违规行为不得发布或传播侵犯他人合法权益的内容不得实施影响本App安全和稳定运行的行为。',
),
const SizedBox(height: 24),
_buildSectionTitle('五、知识产权'),
const SizedBox(height: 16),
_buildParagraph(
'用户知悉并认可本App含程序代码、界面设计、功能及其更新、扩展、修复版本相关权利均归开发者或合法权利人所有。',
),
const SizedBox(height: 12),
_buildParagraph(
'本条所称"知识产权"包括但不限于著作权、商标权、专利权、商业秘密及反不正当竞争法等法律法规项下的一切相关权利。',
),
const SizedBox(height: 12),
_buildParagraph(
'除法律法规另有规定或开发者书面授权外用户仅获得基于本协议的个人、非独占、不可转让、可撤销的使用许可用户不得对本App实施复制、修改、改编、翻译、出租、出借、出售、传播、反向工程、反编译、反汇编或以其他方式尝试获取源代码。',
),
const SizedBox(height: 24),
_buildSectionTitle('六、责任限制'),
const SizedBox(height: 16),
_buildParagraph(
'在法律允许范围内,对于因网络异常、设备故障、不可抗力、第三方服务异常等原因导致的服务中断或数据损失,开发者将在能力范围内及时修复或补救,但不承担超出法定范围的责任。',
),
const SizedBox(height: 24),
_buildSectionTitle('七、协议更新'),
const SizedBox(height: 16),
_buildParagraph(
'开发者有权根据业务变化、监管要求或法律法规变化更新本协议。更新后的协议将在应用内公示用户继续使用本App即视为接受更新后的协议内容。',
),
const SizedBox(height: 24),
_buildSectionTitle('八、适用法律与争议解决'),
const SizedBox(height: 16),
_buildParagraph(
'本协议适用中华人民共和国法律。因本协议引发的争议,双方应先友好协商;协商不成的,提交被告住所地有管辖权的人民法院处理。',
),
const SizedBox(height: 24),
_buildSectionTitle('九、联系方式'),
const SizedBox(height: 16),
_buildContactInfo('开发者', '*****工作室'),
_buildContactInfo('应用名称', '情景诗词'),
_buildContactInfo('联系邮箱', '********@outlook.com'),
const SizedBox(height: 24),
_buildBottomIndicator(),
],
),
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
);
}
Widget _buildParagraph(String text) {
return Text(
text,
style: TextStyle(fontSize: 14, color: Colors.grey[700], height: 1.6),
);
}
Widget _buildContactInfo(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
SizedBox(
width: 80,
child: Text(
'$label',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
),
Expanded(
child: Text(
value,
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
),
),
],
),
);
}
Widget _buildEffectiveDate(String date) {
return Text(
'生效日期:$date',
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
);
}
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]),
],
),
);
}
}
/// 隐私政策页面(完整页面)
class PrivacyPage extends StatefulWidget {
const PrivacyPage({super.key});
@override
State<PrivacyPage> createState() => _PrivacyPageState();
}
class _PrivacyPageState extends State<PrivacyPage>
with TickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@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,
bottom: TabBar(
controller: _tabController,
labelColor: AppConstants.primaryColor,
unselectedLabelColor: Colors.grey[600],
indicatorColor: AppConstants.primaryColor,
indicatorWeight: 2,
tabs: const [
Tab(text: '隐私政策'),
Tab(text: '用户协议'),
],
),
leading: IconButton(
icon: Icon(Icons.arrow_back, color: AppConstants.primaryColor),
onPressed: () => Navigator.of(context).pop(),
),
actions: [
IconButton(
icon: Icon(Icons.link, color: AppConstants.primaryColor),
onPressed: () => _showOnlineLinkDialog(),
tooltip: '在线版本',
),
],
),
body: TabBarView(
controller: _tabController,
children: const [PrivacyPolicyContent(), UserAgreementContent()],
),
);
}
void _showOnlineLinkDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: Row(
children: [
Icon(Icons.public, color: AppConstants.primaryColor),
const SizedBox(width: 8),
const Text('在线版本'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('您可以访问以下链接查看最新版本的协议:', style: TextStyle(fontSize: 14)),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[300]!),
),
child: SelectableText(
'https://*****.github.io/privacy',
style: TextStyle(
fontSize: 13,
color: AppConstants.primaryColor,
decoration: TextDecoration.underline,
),
),
),
const SizedBox(height: 12),
Text(
'💡 点击下方按钮复制链接,在浏览器中打开查看',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('关闭'),
),
ElevatedButton.icon(
onPressed: () {
Clipboard.setData(
const ClipboardData(text: 'https://*****.github.io/privacy'),
);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('链接已复制到剪贴板'),
backgroundColor: AppConstants.primaryColor,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
);
},
icon: const Icon(Icons.copy, size: 18),
label: const Text('复制链接'),
style: ElevatedButton.styleFrom(
backgroundColor: AppConstants.primaryColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
);
}
}

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]),
],
),
);
}
}

View File

@@ -0,0 +1,309 @@
import 'package:flutter/material.dart';
import '../../../constants/app_constants.dart';
/// 时间: 2026-03-27
/// 功能: 桌面小卡片设置页面
/// 介绍: 用于配置和管理桌面小卡片
class WidgetsPage extends StatefulWidget {
const WidgetsPage({super.key});
@override
State<WidgetsPage> createState() => _WidgetsPageState();
}
class _WidgetsPageState extends State<WidgetsPage> {
bool _enableWidget = true;
bool _showWeather = true;
bool _showQuote = true;
bool _showTime = true;
int _updateInterval = 5; // 分钟
final List<int> _intervalOptions = [1, 5, 10, 30, 60];
@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: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildSettingsGroup('卡片状态', [
_buildSwitchItem(
'启用桌面卡片',
'在桌面上显示诗词卡片',
Icons.widgets,
_enableWidget,
(value) => setState(() => _enableWidget = value),
),
]),
const SizedBox(height: 16),
_buildSettingsGroup('显示内容', [
_buildSwitchItem(
'显示天气',
'在卡片上显示当前天气',
Icons.cloud,
_showWeather,
(value) => setState(() => _showWeather = value),
),
_buildSwitchItem(
'显示诗句',
'在卡片上显示随机诗句',
Icons.edit_document,
_showQuote,
(value) => setState(() => _showQuote = value),
),
_buildSwitchItem(
'显示时间',
'在卡片上显示当前时间',
Icons.access_time,
_showTime,
(value) => setState(() => _showTime = value),
),
]),
const SizedBox(height: 16),
_buildSettingsGroup('更新设置', [_buildIntervalItem()]),
const SizedBox(height: 16),
_buildSettingsGroup('预览', [_buildPreviewCard()]),
const SizedBox(height: 32),
_buildActionButton(),
],
),
);
}
Widget _buildSettingsGroup(String title, List<Widget> items) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(5),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppConstants.primaryColor,
),
),
),
...items,
],
),
);
}
Widget _buildSwitchItem(
String title,
String subtitle,
IconData icon,
bool value,
ValueChanged<bool> onChanged,
) {
return ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(10),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: AppConstants.primaryColor, size: 20),
),
title: Text(
title,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
subtitle: Text(
subtitle,
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
trailing: Switch(
value: value,
onChanged: onChanged,
activeThumbColor: AppConstants.primaryColor,
),
);
}
Widget _buildIntervalItem() {
return ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(10),
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.refresh, color: AppConstants.primaryColor, size: 20),
),
title: const Text(
'更新间隔',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
subtitle: Text(
'$_updateInterval 分钟更新一次',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
trailing: SizedBox(
width: 150,
child: SegmentedButton<int>(
segments: _intervalOptions.map((interval) {
return ButtonSegment(value: interval, label: Text('$interval'));
}).toList(),
selected: {_updateInterval},
onSelectionChanged: (Set<int> selection) {
setState(() {
_updateInterval = selection.first;
});
},
style: ButtonStyle(
visualDensity: VisualDensity.compact,
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return AppConstants.primaryColor;
}
return Colors.grey[200];
}),
foregroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return Colors.white;
}
return Colors.black87;
}),
),
),
),
);
}
Widget _buildPreviewCard() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(90),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(10),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (_showTime) ...[
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
'${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}',
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 12),
],
if (_showQuote) ...[
Text(
'床前明月光,疑是地上霜。',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'举头望明月,低头思故乡。',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
const SizedBox(height: 8),
Text(
'— 李白《静夜思》',
style: const TextStyle(color: Colors.white70, fontSize: 12),
),
],
if (_showWeather) ...[
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(Icons.sunny, color: Colors.white, size: 16),
const SizedBox(width: 4),
Text(
'25°C',
style: const TextStyle(color: Colors.white, fontSize: 14),
),
],
),
],
],
),
);
}
Widget _buildActionButton() {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('桌面卡片设置已保存'),
backgroundColor: AppConstants.primaryColor,
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppConstants.primaryColor,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: const Text(
'应用设置',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
);
}
}