Files
wushu/lib/views/profile/profile_page.dart
2026-03-31 20:15:16 +08:00

932 lines
27 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 时间: 2025.03.21
/// 功能: 个人页面(类似朋友圈布局)
/// 介绍: 展示用户头像、个性签名、昵称、统计信息和设置列表,支持左右滑动切换页面
/// 最新变化: 重新设计布局,实现朋友圈风格的个人页面
import 'dart:convert';
import 'dart:math' show Random;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../constants/app_constants.dart';
import '../../controllers/history_controller.dart';
import '../../controllers/shared_preferences_storage_controller.dart';
import 'history_page.dart';
import 'per_card.dart';
import 'settings/app_fun.dart';
import 'settings/user-plan.dart';
import 'settings/offline-data.dart';
import 'settings/privacy.dart';
import 'settings/learn-us.dart';
import 'app-info.dart';
import 'level/poetry.dart';
import 'guide/permission.dart';
import 'guide/app-data.dart';
import 'guide/tongji.dart';
import 'theme/app-diy.dart';
import 'expand/vote.dart';
import 'expand/manu-script.dart';
import 'components/bug_list_page.dart';
import 'components/pop-menu.dart';
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage>
with TickerProviderStateMixin, WidgetsBindingObserver {
late PageController _pageController;
late TabController _tabController;
int _currentPage = 1; // 默认显示第2页设置
bool _isCardExpanded = false; // 个人卡片展开状态
bool _isStatsHidden = false; // 统计数据隐藏状态
double _startY = 0.0;
// 历史记录相关
List<Map<String, dynamic>> _poetryHistory = [];
// PersonalCard 的 Key用于调用其刷新方法
final GlobalKey<PersonalCardState> _personalCardKey =
GlobalKey<PersonalCardState>();
// 答题统计数据
int _correctAnswers = 0;
int _weekQuestions = 0;
// 统计数据
int _todayViews = 0;
int _weekViews = 0;
String _firstUseTime = '未记录';
int _useDays = 1;
String _dataSize = '0 B';
int _noteCount = 0;
int _totalQuestions = 0;
int _todayQuestions = 0;
int _todayLikes = 0;
// 可爱的emoji列表
final List<String> _avatars = [
'👤',
'😊',
'🎉',
'🌟',
'🔥',
'💎',
'🌈',
'🦋',
'🌸',
'🐱',
'🐶',
'🐼',
'🐨',
'🐵',
'🦄',
'🐸',
'🐹',
'🐰',
'🦊',
'🐻',
];
// 模拟用户数据
final Map<String, dynamic> _userData = {
'avatar': '👤', // 使用emoji代替网络图片
'nickname': '诗词爱好者',
'signature': '人生如诗,岁月如歌',
'level': 'Lv.12',
'vip': true,
'posts': 156,
'followers': 1280,
'following': 89,
'likes': 2560,
'favorites': 128,
'views': 3560,
};
// 更换头像
void _changeAvatar() {
final random = Random();
setState(() {
_userData['avatar'] = _avatars[random.nextInt(_avatars.length)];
});
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_pageController = PageController(initialPage: 1);
_tabController = TabController(length: 3, vsync: this);
_loadPoetryHistory();
_loadPoetryStatistics();
_loadStatistics();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_pageController.dispose();
_tabController.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// 应用恢复时刷新数据
refreshData();
}
}
// 刷新所有数据(公共方法,供外部调用)
Future<void> refreshData() async {
await _loadPoetryStatistics();
await _loadStatistics();
// 同时刷新个人卡片的数据
final personalCardState = _personalCardKey.currentState;
if (personalCardState != null && personalCardState.mounted) {
await personalCardState.refreshData();
}
}
void _onPageChanged(int page) {
setState(() {
_currentPage = page;
});
HapticFeedback.lightImpact();
}
// === 历史记录相关方法 ===
Future<void> _loadPoetryHistory() async {
try {
final history = await HistoryController.getHistory();
setState(() {
_poetryHistory = history;
});
} catch (e) {}
}
// === 答题统计相关方法 ===
Future<void> _loadPoetryStatistics() async {
try {
// 加载总体统计
_correctAnswers = await SharedPreferencesStorageController.getInt(
'correctAnswers',
defaultValue: 0,
);
// 加载答题记录列表来计算今日和本周答题数
List<String> records =
await SharedPreferencesStorageController.getStringList(
'poetryAnswerRecords',
defaultValue: [],
);
final now = DateTime.now();
final todayStart = DateTime(now.year, now.month, now.day);
final weekStart = todayStart.subtract(
Duration(days: todayStart.weekday - 1),
);
_todayQuestions = 0;
_weekQuestions = 0;
for (String recordStr in records) {
try {
final record = jsonDecode(recordStr) as Map<String, dynamic>;
final answerTime = record['answerTime'];
if (answerTime != null) {
final time = DateTime.parse(answerTime);
if (time.isAfter(todayStart)) {
_todayQuestions++;
}
if (time.isAfter(weekStart)) {
_weekQuestions++;
}
}
} catch (e) {
continue;
}
}
setState(() {});
} catch (e) {
// 加载失败
}
}
// === 加载统计数据 ===
Future<void> _loadStatistics() async {
try {
// 加载浏览统计
_todayViews = await StatisticsManager().getTodayViews();
_weekViews = await StatisticsManager().getWeekViews();
_firstUseTime = await StatisticsManager().getFirstUseTime();
_useDays = await StatisticsManager().getUseDays();
_dataSize = await StatisticsManager().getDataSize();
_todayLikes = await StatisticsManager().getTodayLikes();
_todayQuestions = await StatisticsManager().getTodayQuestions();
// 加载笔记总数
_noteCount = await HistoryController.getNotesCount();
// 加载累计答题数
_totalQuestions = await SharedPreferencesStorageController.getInt(
'totalQuestions',
defaultValue: 0,
);
setState(() {});
} catch (e) {
// 加载失败
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: _buildAppBar(),
body: GestureDetector(
behavior: HitTestBehavior.opaque, // 确保手势检测能够捕获整个区域的事件
onVerticalDragStart: (details) {
_startY = details.globalPosition.dy;
},
onVerticalDragUpdate: (details) {
double currentY = details.globalPosition.dy;
double deltaY = currentY - _startY;
if (deltaY > 20) {
// 下拉超过20像素张开卡片降低阈值提高灵敏度
if (!_isCardExpanded) {
setState(() {
_isCardExpanded = true;
});
}
} else if (deltaY < -60) {
// 上滑超过60像素收起卡片降低阈值提高灵敏度
if (_isCardExpanded) {
setState(() {
_isCardExpanded = false;
});
}
}
},
child: Column(
children: [
_buildProfileHeader(),
Expanded(
child: PageView(
controller: _pageController,
onPageChanged: _onPageChanged,
children: [
_buildPage1(), // 个人信息卡片
_buildPage2(), // 设置列表
_buildPage3(), // 其他功能
],
),
),
],
),
),
);
}
PreferredSizeWidget _buildAppBar() {
// === 顶部导航栏:显示页面标题 ===
return AppBar(
title: Text(
'个人',
style: TextStyle(
color: AppConstants.primaryColor,
fontWeight: FontWeight.bold,
),
),
backgroundColor: Colors.white,
elevation: 0,
centerTitle: true,
actions: [
// 右上角更多按钮
IconButton(
icon: Icon(Icons.more_horiz, color: AppConstants.primaryColor),
onPressed: _showMoreOptions,
),
],
);
}
Widget _buildProfileHeader() {
// === 个人信息头部:可收起/张开的个人卡片 ===
return PersonalCard(
key: _personalCardKey,
userData: _userData,
isExpanded: _isCardExpanded,
onExpandChanged: (value) {
setState(() {
_isCardExpanded = value;
});
},
currentPage: _currentPage,
onPageChanged: (page) {
// 添加 mounted 和 hasClients 检查,避免 dispose 后调用导致黑屏
if (mounted && _pageController.hasClients) {
_pageController.animateToPage(
page,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
},
onAvatarTap: _changeAvatar,
);
}
Widget _buildPage1() {
// === 第1页个人信息卡片展示 ===
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// 个人信息卡片
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// === 卡片标题 ===
Row(
children: [
Icon(
Icons.person_outline,
color: AppConstants.primaryColor,
size: 20,
),
const SizedBox(width: 8),
Text(
'统计',
style: TextStyle(
color: AppConstants.primaryColor,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
IconButton(
icon: Icon(
_isStatsHidden
? Icons.visibility_off_outlined
: Icons.visibility_outlined,
color: Colors.grey[600],
size: 20,
),
onPressed: () {
setState(() {
_isStatsHidden = !_isStatsHidden;
});
HapticFeedback.lightImpact();
},
tooltip: _isStatsHidden ? '显示数据' : '隐藏数据',
),
],
),
const SizedBox(height: 16),
// === 信息列表 ===
if (!_isStatsHidden) ...[
_buildInfoItem('今日浏览', '$_todayViews'),
_buildInfoItem('本周浏览', '$_weekViews'),
_buildInfoItem('天数', '$_useDays'),
_buildInfoItem('数据', _dataSize),
_buildInfoItem('笔记', '$_noteCount'),
_buildInfoItem('今日答题', '$_todayQuestions'),
_buildInfoItem('今日点赞', '$_todayLikes'),
] else
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Text(
'数据已隐藏',
style: TextStyle(color: Colors.grey[500], fontSize: 14),
),
),
),
],
),
),
const SizedBox(height: 16),
// === 互动统计卡片 ===
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.analytics_outlined,
color: AppConstants.primaryColor,
size: 20,
),
const SizedBox(width: 8),
Text(
'诗词挑战',
style: TextStyle(
color: AppConstants.primaryColor,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
if (!_isStatsHidden)
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildInteractionItem('今日答题', '$_todayQuestions'),
_buildInteractionItem('本周答题', '$_weekQuestions'),
_buildInteractionItem('答对次数', '$_correctAnswers'),
],
)
else
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Text(
'数据已隐藏',
style: TextStyle(color: Colors.grey[500], fontSize: 14),
),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const PoetryLevelPage(),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppConstants.primaryColor,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('开始诗词答题', style: TextStyle(fontSize: 16)),
),
),
],
),
),
],
),
);
}
Widget _buildPage2() {
// === 第2页设置列表 ===
return ListView(
padding: const EdgeInsets.all(16),
children: [
// === 账户设置组 ===
_buildSettingsGroup('软件设置', [
_buildSettingsItem(
'离线使用',
Icons.volunteer_activism,
() => _navigateToOfflineDataPage(),
),
_buildSettingsItem(
'历史记录',
Icons.history,
() => _navigateToHistoryPage(),
),
_buildSettingsItem(
'功能设置',
Icons.verified_user,
() => _navigateToAppFunSettings(),
),
_buildSettingsItem(
'主题风格',
Icons.palette,
() => _navigateToAppDiyPage(),
),
]),
const SizedBox(height: 16),
// === 隐私设置组 ===
_buildSettingsGroup('隐私设置', [
_buildSettingsItem(
'权限管理',
Icons.block,
() => _navigateToPermissionPage(),
),
_buildSettingsItem(
'应用数据',
Icons.security,
() => _navigateToAppDataPage(),
),
//撤回同意
_buildSettingsItem(
'软件协议',
Icons.description,
() => _navigateToPrivacyPage(),
),
]),
const SizedBox(height: 16),
// === 通用设置组 ===
_buildSettingsGroup('软件信息', [
_buildSettingsItem(
'应用信息',
Icons.phone_android,
() => _navigateToAppInfoPage(),
),
_buildSettingsItem(
'了解我们',
Icons.info,
() => _navigateToLearnUsPage(),
),
_buildSettingsItem(
'已知bug',
Icons.cleaning_services,
() => showBugListBottomSheet(context),
),
_buildSettingsItem(
'关闭退出',
Icons.palette,
() => _showSnackBar('重启 关闭?'),
),
]),
],
);
}
Widget _buildPage3() {
// === 第3页其他功能列表 ===
return ListView(
padding: const EdgeInsets.all(16),
children: [
// === 功能服务组 ===
_buildSettingsGroup('用户体验计划(限免)', [
_buildSettingsItem(
'加入体验',
Icons.edit,
() => _navigateToUserPlanPage(),
),
_buildSettingsItem(
'参与投票',
Icons.how_to_vote,
() => _navigateToVotePage(),
),
_buildSettingsItem(
'查看全站统计',
Icons.history,
() => _showSnackBar('查看全站统计'),
),
_buildSettingsItem(
'开发计划',
Icons.analytics,
() => _showSnackBar('软件开发进度'),
),
_buildSettingsItem('去投稿', Icons.edit_note, () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ManuscriptPage()),
);
}),
]),
const SizedBox(height: 16),
// === 帮助支持组 ===
// _buildSettingsGroup('帮助支持', [
// _buildSettingsItem(
// '使用教程',
// Icons.help_outline,
// () => _navigateToSpGuidePage(),
// ),
// _buildSettingsItem(
// '意见反馈',
// Icons.feedback,
// () => _showSnackBar('意见反馈'),
// ),
// _buildSettingsItem(
// '联系客服',
// Icons.support_agent,
// () => _showSnackBar('联系客服'),
// ),
// _buildSettingsItem(
// '商务合作',
// Icons.info_outline,
// () => _showSnackBar('商务合作'),
// ),
// ]),
// const SizedBox(height: 16),
// === 关于信息组 ===
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.info, color: AppConstants.primaryColor, size: 20),
const SizedBox(width: 8),
Text(
'关于应用',
style: TextStyle(
color: AppConstants.primaryColor,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
Text(
AppConstants.appName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'版本 ${AppConstants.appVersion}',
style: const TextStyle(fontSize: 14, color: Colors.grey),
),
const SizedBox(height: 16),
const Text(
'基于Flutter开发的现代化诗词应用致力于为用户提供优质的诗词阅读和创作体验。',
style: TextStyle(
fontSize: 14,
color: Colors.black87,
height: 1.5,
),
textAlign: TextAlign.center,
),
],
),
),
],
);
}
Widget _buildInfoItem(String label, String value) {
// === 信息项:显示标签和值 ===
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF9E9E9E), // 使用具体颜色值代替Colors.grey[600]
),
),
Text(
value,
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
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.08),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 组标题
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(
Icons.settings_outlined,
color: AppConstants.primaryColor,
size: 20,
),
const SizedBox(width: 8),
Text(
title,
style: TextStyle(
color: AppConstants.primaryColor,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
),
const Divider(height: 1),
// 设置项列表
...items,
],
),
);
}
Widget _buildSettingsItem(String title, IconData icon, VoidCallback onTap) {
// === 设置项:显示图标、标题和箭头 ===
return ListTile(
leading: Icon(icon, color: AppConstants.primaryColor, size: 20),
title: Text(
title,
style: const TextStyle(fontSize: 14, color: Colors.black87),
),
trailing: const Icon(Icons.chevron_right, color: Colors.grey, size: 20),
onTap: () {
HapticFeedback.lightImpact();
onTap();
},
);
}
Widget _buildInteractionItem(String label, String value) {
// === 互动统计项:显示标签和数值 ===
return Expanded(
child: Column(
children: [
Text(
value,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppConstants.primaryColor,
),
),
const SizedBox(height: 4),
Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)),
],
),
);
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: AppConstants.primaryColor,
duration: const Duration(seconds: 2),
),
);
}
// === 导航到历史记录页面 ===
void _navigateToHistoryPage() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const HistoryPage()),
);
}
void _navigateToAppFunSettings() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AppFunSettingsPage()),
);
}
void _navigateToUserPlanPage() {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const UserPlanPage()),
);
}
void _navigateToOfflineDataPage() {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const OfflineDataPage()),
);
}
void _navigateToPermissionPage() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const PermissionPage()),
);
}
void _navigateToAppDataPage() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AppDataPage()),
);
}
void _navigateToPrivacyPage() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const PrivacyPage()),
);
}
void _navigateToLearnUsPage() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const LearnUsPage()),
);
}
void _navigateToAppInfoPage() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AppInfoPage()),
);
}
void _navigateToAppDiyPage() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AppDiyPage()),
);
}
void _navigateToVotePage() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const VotePage()),
);
}
void _showMoreOptions() {
PopMenu.show(
context,
onRefresh: () => refreshData(),
onEdit: () => _showSnackBar('编辑资料'),
onScanQr: () => _showSnackBar('扫描二维码'),
onDarkMode: () => _showSnackBar('夜间模式'),
onSettings: () {
Future.delayed(const Duration(milliseconds: 100), () {
if (mounted && _pageController.hasClients) {
_pageController.animateToPage(
1,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
});
},
);
}
}