/// 时间: 2025.03.21 /// 功能: 个人页面(类似朋友圈布局) /// 介绍: 展示用户头像、个性签名、昵称、统计信息和设置列表,支持左右滑动切换页面 /// 最新变化: 重新设计布局,实现朋友圈风格的个人页面 import 'dart:convert'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import '../../constants/app_constants.dart'; import '../../controllers/history_controller.dart'; import '../../controllers/sqlite_storage_controller.dart'; import '../../utils/flutter_compatibility_fix.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/sp-guide.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'; class ProfilePage extends StatefulWidget { const ProfilePage({super.key}); @override State createState() => _ProfilePageState(); } class _ProfilePageState extends State with TickerProviderStateMixin { late PageController _pageController; late TabController _tabController; int _currentPage = 1; // 默认显示第2页(设置) bool _isCardExpanded = false; // 个人卡片展开状态 double _startY = 0.0; // 历史记录相关 List> _poetryHistory = []; final String _historyKey = 'poetry_history'; // 答题统计数据 int _totalQuestions = 0; int _correctAnswers = 0; int _todayQuestions = 0; int _weekQuestions = 0; // 模拟用户数据 final Map _userData = { 'avatar': '👤', // 使用emoji代替网络图片 'nickname': '诗词爱好者', 'signature': '人生如诗,岁月如歌', 'level': 'Lv.12', 'vip': true, 'posts': 156, 'followers': 1280, 'following': 89, 'likes': 2560, 'favorites': 128, 'views': 3560, }; @override void initState() { super.initState(); _pageController = PageController(initialPage: 1); _tabController = TabController(length: 3, vsync: this); _loadPoetryHistory(); _loadPoetryStatistics(); } @override void dispose() { _pageController.dispose(); _tabController.dispose(); super.dispose(); } void _handleScroll() { // 这个方法现在由手势检测处理 } void _onPageChanged(int page) { setState(() { _currentPage = page; }); HapticFeedback.lightImpact(); } // === 历史记录相关方法 === Future _loadPoetryHistory() async { try { final history = await HistoryController.getHistory(); setState(() { _poetryHistory = history; }); } catch (e) {} } Future _savePoetryToHistory(Map poetryData) async { try { final success = await HistoryController.addToHistory(poetryData); if (success) { _showSnackBar('已添加到历史记录'); } else { _showSnackBar('该诗词已在历史记录中'); } } catch (e) { _showSnackBar('保存失败'); } } Future _clearPoetryHistory() async { try { final success = await HistoryController.clearHistory(); if (success) { setState(() { _poetryHistory.clear(); }); _showSnackBar('历史记录已清空'); } else { _showSnackBar('清空失败'); } } catch (e) { _showSnackBar('清空失败'); } } // === 答题统计相关方法 === Future _loadPoetryStatistics() async { try { // 加载总体统计 _totalQuestions = await SQLiteStorageController.getInt( 'totalQuestions', defaultValue: 0, ); _correctAnswers = await SQLiteStorageController.getInt( 'correctAnswers', defaultValue: 0, ); // 加载答题记录列表来计算今日和本周答题数 List records = await SQLiteStorageController.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; 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) { // 加载失败 } } @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( 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, ); } }, ); } Widget _buildStatCard(String label, String value, IconData icon) { // === 单个统计卡片:显示统计数据和图标 === return Expanded( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 5, offset: const Offset(0, 1), ), ], ), child: Column( children: [ Icon(icon, color: AppConstants.primaryColor, size: 24), const SizedBox(height: 8), Text( value, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), const SizedBox(height: 4), Text( label, style: const TextStyle(fontSize: 12, color: Colors.grey), textAlign: TextAlign.center, ), ], ), ), ); } Widget _buildTabBar() { // === 页面切换标签栏:显示当前页面指示器 === return GestureDetector( behavior: HitTestBehavior.opaque, onVerticalDragStart: (_) {}, onVerticalDragUpdate: (_) {}, onVerticalDragEnd: (_) {}, child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppConstants.primaryColor.withValues(alpha: 0.85), AppConstants.primaryColor.withValues(alpha: 0.8), ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), child: Container( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ // 页面指示器 Expanded( child: Container( height: 3, margin: const EdgeInsets.symmetric(horizontal: 20), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(2), ), child: Stack( children: [ // 背景条 Container( height: 3, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(2), ), ), // 滑动指示器 AnimatedContainer( duration: const Duration(milliseconds: 300), width: MediaQuery.of(context).size.width / 3 - 40, height: 3, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2), ), margin: EdgeInsets.only( left: (MediaQuery.of(context).size.width / 3 - 40) * _currentPage, ), ), ], ), ), ), // 页面标签 Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ _buildTabLabel(0, '卡片'), _buildTabLabel(1, '设置'), _buildTabLabel(2, '更多'), ], ), ), ], ), ), ), ); } Widget _buildTabLabel(int index, String label) { // === 单个页面标签:可点击切换到对应页面 === final isSelected = _currentPage == index; return GestureDetector( onTap: () { // 添加 mounted 和 hasClients 检查,避免 dispose 后调用导致黑屏 if (mounted && _pageController.hasClients) { _pageController.animateToPage( index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( label, style: TextStyle( color: isSelected ? Colors.white : Colors.white70, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontSize: 14, ), ), ), ); } 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 SizedBox(height: 16), // === 信息列表 === _buildInfoItem('用户ID', '123123'), _buildInfoItem('注册时间', '2023-03-21'), _buildInfoItem('会员等级', 'VIP会员'), _buildInfoItem('积分余额', '2,580'), _buildInfoItem('创作诗词', '156首'), _buildInfoItem('获赞总数', '2,560'), ], ), ), 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), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildInteractionItem('今日答题', '$_todayQuestions'), _buildInteractionItem('本周答题', '$_weekQuestions'), _buildInteractionItem('答对次数', '$_correctAnswers'), ], ), 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( '标签', 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 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 _navigateToSpGuidePage() { Navigator.push( context, MaterialPageRoute( builder: (context) => const SpGuidePage(fromSettings: true), ), ); } 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 _showHistoryDialog() { showDialog( context: context, builder: (context) => Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( width: MediaQuery.of(context).size.width * 0.9, height: MediaQuery.of(context).size.height * 0.7, padding: const EdgeInsets.all(20), child: Column( children: [ // 对话框标题 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '历史记录', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppConstants.primaryColor, ), ), Row( children: [ TextButton( onPressed: _poetryHistory.isEmpty ? null : () { Navigator.pop(context); _clearPoetryHistory(); }, child: const Text('清空'), ), IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close), ), ], ), ], ), const SizedBox(height: 16), // 历史记录列表 Expanded( child: _poetryHistory.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.history, size: 64, color: Colors.grey[400], ), const SizedBox(height: 16), Text( '暂无历史记录', style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), ], ), ) : ListView.builder( itemCount: _poetryHistory.length, itemBuilder: (context, index) { final poetry = _poetryHistory[index]; return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: CircleAvatar( radius: 20, backgroundColor: AppConstants.primaryColor .withValues(alpha: 0.1), child: Text( '${index + 1}', style: TextStyle( fontSize: 12, color: AppConstants.primaryColor, fontWeight: FontWeight.bold, ), ), ), title: Text( poetry['name'] ?? '未知诗词', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), subtitle: Text( '${poetry['alias'] ?? '未知朝代'} • ${poetry['date'] ?? ''}', style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), trailing: IconButton( icon: const Icon(Icons.favorite_border), onPressed: () { Navigator.pop(context); _showSnackBar('已添加到收藏'); }, ), ), ); }, ), ), ], ), ), ), ); } Widget? _showMoreOptions() { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (context) => Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 顶部拖拽条 Container( width: 40, height: 4, margin: const EdgeInsets.only(top: 8), decoration: BoxDecoration( color: Colors.grey[300]!, borderRadius: BorderRadius.circular(2), ), ), // 选项列表 _buildBottomSheetItem( '分享主页', Icons.share, () => _showSnackBar('分享主页'), ), _buildBottomSheetItem( '编辑资料', Icons.edit, () => _showSnackBar('编辑资料'), ), _buildBottomSheetItem( '扫描二维码', Icons.qr_code_scanner, () => _showSnackBar('扫描二维码'), ), _buildBottomSheetItem( '夜间模式', Icons.dark_mode, () => _showSnackBar('夜间模式'), ), const SizedBox(height: 20), _buildBottomSheetItem('设置', Icons.settings, () { // 延迟执行,确保底部弹窗完全关闭 Future.delayed(const Duration(milliseconds: 100), () { if (mounted && _pageController.hasClients) { _pageController.animateToPage( 1, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } }); }), ], ), ), ); } Widget _buildBottomSheetItem( String title, IconData icon, VoidCallback onTap, ) { // === 底部弹窗选项项 === return ListTile( leading: Icon(icon, color: AppConstants.primaryColor), title: Text(title), onTap: () { Navigator.pop(context); HapticFeedback.lightImpact(); onTap(); }, ); } }