/// 时间: 2025.03.21 /// 功能: 个人页面(类似朋友圈布局) /// 介绍: 展示用户头像、个性签名、昵称、统计信息和设置列表,支持左右滑动切换页面 /// 最新变化: 重新设计布局,实现朋友圈风格的个人页面,添加底部内边距适配液态玻璃导航栏 import 'dart:io' as io; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:get/get.dart'; import '../../constants/app_constants.dart'; import '../../config/app_config.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 'theme/app-diy.dart'; import 'expand/vote.dart'; import 'expand/manu-script.dart'; import 'components/bug_list_page.dart'; import 'components/pop-menu.dart'; import 'components/entire_page.dart'; import '../../services/get/profile_controller.dart'; import '../../services/get/theme_controller.dart'; class ProfilePage extends StatelessWidget { const ProfilePage({super.key}); @override Widget build(BuildContext context) { final controller = Get.put(ProfileController()); final pageController = PageController(initialPage: 1); final themeController = Get.find(); return Obx(() { final isDark = themeController.isDarkMode; return GetBuilder( builder: (controller) { return Scaffold( backgroundColor: isDark ? const Color(0xFF1A1A1A) : const Color(0xFFF5F5F5), appBar: _buildAppBar(controller, isDark), body: GestureDetector( behavior: HitTestBehavior.opaque, onVerticalDragStart: (details) { controller.startY.value = details.globalPosition.dy; }, onVerticalDragEnd: (details) { double currentY = details.globalPosition.dy; double deltaY = currentY - controller.startY.value; if (deltaY > 50) { if (!controller.isCardExpanded.value) { controller.isCardExpanded.value = true; } } else if (deltaY < -50) { if (controller.isCardExpanded.value) { controller.isCardExpanded.value = false; } } }, child: Column( children: [ _buildProfileHeader(controller, pageController), Expanded( child: PageView( controller: pageController, onPageChanged: controller.onPageChanged, children: [ _buildPage1(controller, isDark), _buildPage2(controller, isDark), _buildPage3(controller, isDark), ], ), ), ], ), ), ); }, ); }); } PreferredSizeWidget _buildAppBar(ProfileController controller, bool isDark) { return AppBar( title: Text( '个人', style: TextStyle( color: AppConstants.primaryColor, fontWeight: FontWeight.bold, ), ), backgroundColor: isDark ? const Color(0xFF2A2A2A) : Colors.white, elevation: 0, centerTitle: true, actions: [ IconButton( icon: Icon(Icons.more_horiz, color: AppConstants.primaryColor), onPressed: () => _showMoreOptions(controller), ), ], ); } Widget _buildProfileHeader( ProfileController controller, PageController pageController, ) { // === 个人信息头部:可收起/张开的个人卡片 === return Obx( () => PersonalCard( userData: controller.userData, isExpanded: controller.isCardExpanded.value, onExpandChanged: (value) { controller.isCardExpanded.value = value; }, currentPage: controller.currentPage.value, onPageChanged: (page) { // 添加 hasClients 检查,避免 dispose 后调用导致黑屏 if (pageController.hasClients) { pageController.animateToPage( page, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } }, onAvatarTap: controller.changeAvatar, ), ); } Widget _buildPage1(ProfileController controller, bool isDark) { return SingleChildScrollView( // 添加底部内边距,让内容延伸到导航栏下方,实现玻璃效果 padding: EdgeInsets.only( left: 16, right: 16, top: 16, bottom: AppConfig.liquidGlassTotalHeight + 16, ), child: Column( children: [ Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: isDark ? Colors.black.withValues(alpha: 0.3) : 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), Obx(() { if (!controller.isStatsHidden.value) { return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildInteractionItem( '今日答题', '${controller.todayQuestions.value}', isDark, ), _buildInteractionItem( '本周答题', '${controller.weekQuestions.value}', isDark, ), _buildInteractionItem( '答对次数', '${controller.correctAnswers.value}', isDark, ), ], ); } else { return Center( child: Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Text( '数据已隐藏', style: TextStyle( color: isDark ? Colors.grey[500] : Colors.grey[500], fontSize: 14, ), ), ), ); } }), const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { Get.to(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, color: Colors.white), ), ), ), ], ), ), const SizedBox(height: 16), Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: isDark ? Colors.black.withValues(alpha: 0.3) : 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( controller.isStatsHidden.value ? Icons.visibility_off_outlined : Icons.visibility_outlined, color: isDark ? Colors.grey[400] : Colors.grey[600], size: 20, ), onPressed: controller.toggleStatsHidden, tooltip: controller.isStatsHidden.value ? '显示数据' : '隐藏数据', ), ], ), const SizedBox(height: 16), if (!controller.isStatsHidden.value) ...[ _buildInfoItem( '今日浏览', '${controller.todayViews.value} 条', isDark, ), _buildInfoItem( '今日点赞', '${controller.todayLikes.value} 个', isDark, ), _buildInfoItem( '今日答题', '${controller.todayQuestions.value} 题', isDark, ), _buildInfoItem( '本周浏览', '${controller.weekViews.value} 条', isDark, ), _buildInfoItem('数据', controller.dataSize.value, isDark), _buildInfoItem( '笔记', '${controller.noteCount.value} 个', isDark, ), _buildInfoItem('已用', '${controller.useDays.value} 天', isDark), ] else Center( child: Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Text( '数据已隐藏', style: TextStyle( color: isDark ? Colors.grey[500] : Colors.grey[500], fontSize: 14, ), ), ), ), ], ), ), ], ), ); } Widget _buildPage2(ProfileController controller, bool isDark) { return ListView( // 添加底部内边距,让内容延伸到导航栏下方,实现玻璃效果 padding: EdgeInsets.only( left: 16, right: 16, top: 16, bottom: AppConfig.liquidGlassTotalHeight + 16, ), children: [ _buildSettingsGroup('软件设置', [ _buildSettingsItem( '功能设置', Icons.verified_user, () => Get.to(const AppFunSettingsPage()), isDark, ), _buildSettingsItem( '离线使用', Icons.volunteer_activism, () => Get.to(const OfflineDataPage()), isDark, ), _buildSettingsItem( '历史记录', Icons.history, () => Get.to(const HistoryPage()), isDark, ), _buildSettingsItem( '主题风格', Icons.palette, () => Get.to(const AppDiyPage()), isDark, ), ], isDark), const SizedBox(height: 16), _buildSettingsGroup('隐私设置', [ _buildSettingsItem( '权限管理', Icons.block, () => Get.to(const PermissionPage()), isDark, ), _buildSettingsItem( '应用数据', Icons.security, () => Get.to(const AppDataPage()), isDark, ), _buildSettingsItem( '软件协议', Icons.description, () => Get.to(const PrivacyPage()), isDark, ), ], isDark), const SizedBox(height: 16), _buildSettingsGroup('软件信息', [ _buildSettingsItem( '应用信息', Icons.phone_android, () => Get.to(const AppInfoPage()), isDark, ), _buildSettingsItem( '了解我们', Icons.info, () => Get.to(const LearnUsPage()), isDark, ), _buildSettingsItem( '已知bug', Icons.cleaning_services, () => showBugListBottomSheet(Get.context!), isDark, ), _buildScreenWakeItem(controller, isDark), ], isDark), ], ); } Widget _buildPage3(ProfileController controller, bool isDark) { return ListView( // 添加底部内边距,让内容延伸到导航栏下方,实现玻璃效果 padding: EdgeInsets.only( left: 16, right: 16, top: 16, bottom: AppConfig.liquidGlassTotalHeight + 16, ), children: [ _buildSettingsGroup('用户体验计划(限免)', [ _buildSettingsItem( '加入体验', Icons.edit, () => Get.to(const UserPlanPage()), isDark, ), _buildSettingsItem( '参与投票', Icons.how_to_vote, () => Get.to(const VotePage()), isDark, ), _buildSettingsItem( '查看全站统计', Icons.history, () => Get.to(const EntirePage()), isDark, ), _buildSettingsItem( '开发计划', Icons.analytics, () => controller.showSnackBar('首个上线版本暂无计划,待收集整理用户建议'), isDark, ), _buildSettingsItem( '去投稿', Icons.edit_note, () => Get.to(const ManuscriptPage()), isDark, ), ], isDark), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: isDark ? Colors.black.withValues(alpha: 0.3) : 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: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: isDark ? Colors.white : Colors.black, ), ), const SizedBox(height: 8), Text( '版本 ${AppConstants.appVersion}', style: TextStyle( fontSize: 14, color: isDark ? Colors.grey[400] : Colors.grey, ), ), const SizedBox(height: 16), Text( '基于Flutter开发的现代化诗词应用,致力于为用户提供优质的诗词阅读和创作体验。', style: TextStyle( fontSize: 14, color: isDark ? Colors.grey[300] : Colors.black87, height: 1.5, ), textAlign: TextAlign.center, ), ], ), ), ], ); } Widget _buildInfoItem(String label, String value, bool isDark) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontSize: 14, color: isDark ? Colors.grey[400] : const Color(0xFF9E9E9E), ), ), Text( value, style: TextStyle( fontSize: 14, color: isDark ? Colors.white : Colors.black87, fontWeight: FontWeight.w500, ), ), ], ), ); } Widget _buildSettingsGroup(String title, List items, bool isDark) { return Container( decoration: BoxDecoration( color: isDark ? const Color(0xFF2A2A2A) : Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: isDark ? Colors.black.withValues(alpha: 0.3) : 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, ), ), ], ), ), Divider(height: 1, color: isDark ? Colors.grey[800] : null), ...items, ], ), ); } Widget _buildSettingsItem( String title, IconData icon, VoidCallback onTap, bool isDark, ) { return ListTile( leading: Icon(icon, color: AppConstants.primaryColor, size: 20), title: Text( title, style: TextStyle( fontSize: 14, color: isDark ? Colors.white : Colors.black87, ), ), trailing: Icon( Icons.chevron_right, color: isDark ? Colors.grey[600] : Colors.grey, size: 20, ), onTap: () { HapticFeedback.lightImpact(); onTap(); }, ); } Widget _buildScreenWakeItem(ProfileController controller, bool isDark) { if (kIsWeb) { return const SizedBox.shrink(); } return ListTile( leading: Icon( Icons.screen_lock_rotation, color: AppConstants.primaryColor, size: 20, ), title: Text( '屏幕常亮', style: TextStyle( fontSize: 14, color: isDark ? Colors.white : Colors.black87, ), ), trailing: Obx( () => Switch( value: controller.isScreenWakeEnabled.value, onChanged: (value) async { final originalValue = controller.isScreenWakeEnabled.value; controller.isScreenWakeEnabled.value = value; if (value) { await Get.dialog( AlertDialog( backgroundColor: isDark ? const Color(0xFF2A2A2A) : Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), title: Text( '⚠️ 屏幕常亮提示', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppConstants.primaryColor, ), ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8), _buildTipItem('OLED屏幕长时间显示相同内容可能会造成不可逆老化', isDark), const SizedBox(height: 6), _buildTipItem('开启常亮后,配合自动刷新内容的设置,实现常亮自动加载诗句', isDark), const SizedBox(height: 6), _buildTipItem('挂起常亮后,需保持该软件在屏幕中显示,记得关闭常亮哦!', isDark), ], ), actions: [ TextButton( onPressed: () { controller.isScreenWakeEnabled.value = originalValue; Get.back(result: false); }, style: TextButton.styleFrom( foregroundColor: isDark ? Colors.grey[400] : Colors.grey[600], ), child: const Text('取消'), ), ElevatedButton( onPressed: () { Get.back(result: true); }, style: ElevatedButton.styleFrom( backgroundColor: AppConstants.primaryColor, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text('确定开启'), ), ], ), ).then((result) async { if (result == true) { await controller.toggleScreenWake(value); } else { controller.isScreenWakeEnabled.value = originalValue; } }); } else { await controller.toggleScreenWake(value); } }, activeColor: AppConstants.primaryColor, ), ), ); } Widget _buildInteractionItem(String label, String value, bool isDark) { 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: TextStyle( fontSize: 12, color: isDark ? Colors.grey[400] : Colors.grey, ), ), ], ), ); } void _showMoreOptions(ProfileController controller) { PopMenu.show( Get.context!, onRefresh: () => controller.refreshData(), onEdit: () => controller.showSnackBar('编辑资料'), onScanQr: () => controller.showSnackBar('了解软件功能'), onDarkMode: () => controller.showSnackBar('夜间模式'), onSettings: () { Future.delayed(const Duration(milliseconds: 100), () { final pageController = PageController(initialPage: 1); if (pageController.hasClients) { pageController.animateToPage( 1, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } }); }, ); } Widget _buildTipItem(String text, bool isDark) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '• ', style: TextStyle( fontSize: 14, color: isDark ? Colors.grey[300] : Colors.black87, ), ), Expanded( child: Text( text, style: TextStyle( fontSize: 14, color: isDark ? Colors.grey[300] : Colors.black87, ), ), ), ], ); } }