import 'package:flutter/material.dart'; import 'dart:ui'; import 'dart:math'; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../constants/app_constants.dart'; import '../../services/network_listener_service.dart'; import 'guide/tongji.dart'; /// 时间: 2026-03-31 /// 功能: 个人信息卡片组件 /// 介绍: 可收起/张开的个人信息卡片,支持下拉张开,上滑收起 /// 最新变化: 重新设计为苹果风格,优化动画效果,改进布局结构,提升用户体验 class PersonalCard extends StatefulWidget { final Map userData; final bool? isExpanded; final ValueChanged? onExpandChanged; final int currentPage; final ValueChanged? onPageChanged; final VoidCallback? onAvatarTap; const PersonalCard({ super.key, required this.userData, this.isExpanded, this.onExpandChanged, this.currentPage = 0, this.onPageChanged, this.onAvatarTap, }); @override State createState() => PersonalCardState(); } class PersonalCardState extends State { late bool _isExpanded; late String _currentTip; bool _isOnline = true; bool _isEditingNickname = false; bool _isUserPlanJoined = false; late TextEditingController _nicknameController; // 累计数据 int _totalViews = 0; int _totalLikes = 0; int _totalQuestions = 0; @override void initState() { super.initState(); _isExpanded = widget.isExpanded ?? false; _currentTip = _getRandomTip(); _nicknameController = TextEditingController( text: widget.userData['nickname'] ?? '诗词爱好者', ); _loadOnlineStatus(); _loadUserPlanStatus(); _loadTotalStats(); } @override void dispose() { _nicknameController.dispose(); super.dispose(); } Future _loadTotalStats() async { final views = await StatisticsManager().getTotalViews(); final likes = await StatisticsManager().getTotalLikes(); final questions = await StatisticsManager().getTotalQuestions(); setState(() { _totalViews = views; _totalLikes = likes; _totalQuestions = questions; }); } // 刷新数据(公共方法,供外部调用) Future refreshData() async { await _loadTotalStats(); await _loadUserPlanStatus(); } Future _loadOnlineStatus() async { final prefs = await SharedPreferences.getInstance(); var isOnline = prefs.getBool('personal_card_online') ?? true; // 检查网络连接状态 try { final networkStatus = NetworkListenerService().currentStatus; // 如果网络处于错误状态,自动调整为离线状态 if (networkStatus == NetworkStatus.error) { isOnline = false; await prefs.setBool('personal_card_online', false); } } catch (e) { // 网络检查失败时保持原状态 } setState(() { _isOnline = isOnline; }); } Future _loadUserPlanStatus() async { final prefs = await SharedPreferences.getInstance(); setState(() { _isUserPlanJoined = prefs.getBool('user_plan_joined') ?? false; }); } Future _toggleOnlineStatus() async { // 如果当前是离线状态,要切换到在线状态,需要先检测网络 if (!_isOnline) { // 检测网络状态 try { final networkStatus = NetworkListenerService().currentStatus; if (networkStatus == NetworkStatus.error) { // 无网络,弹窗阻止用户打开 if (mounted) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('网络不可用'), content: const Text('当前无网络连接,无法切换到在线状态。请检查网络设置后重试。'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('知道了'), ), ], ); }, ); } return; } } catch (e) { // 网络检测失败,阻止切换 if (mounted) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('网络检测失败'), content: const Text('无法检测网络状态,请稍后重试。'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('知道了'), ), ], ); }, ); } return; } } setState(() { _isOnline = !_isOnline; }); final prefs = await SharedPreferences.getInstance(); await prefs.setBool('personal_card_online', _isOnline); // 显示气泡消息 ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(_isOnline ? '已切换到在线状态' : '已切换到离线状态'), duration: const Duration(seconds: 2), backgroundColor: _isOnline ? AppConstants.successColor : Colors.red, ), ); } @override void didUpdateWidget(covariant PersonalCard oldWidget) { super.didUpdateWidget(oldWidget); final newIsExpanded = widget.isExpanded ?? false; if (oldWidget.isExpanded != newIsExpanded) { setState(() { _isExpanded = newIsExpanded; }); } } void _toggleExpand() { setState(() { _isExpanded = !_isExpanded; widget.onExpandChanged?.call(_isExpanded); }); } final List _tips = [ "🐴年大🍊", "🌙安", "🧧到福到", "🍎🍎安安", "💪健康", "🐟🐟有余", "🍊🍈吉利", "🐲🐴精神", "💰满钵满", "😴好梦", "🐴年好☁️,大🍊大🍈,心想事🍊,万事🍅意!", "🌙了,早点🛏️,明天再战,加油💪!", "今天吃了🍎,平平安安;吃了🍊,大吉大利;喝了🥛,白白胖胖👶!", "祝你🐴到成功,💰源滚滚,步步高升📈,生活甜如蜜🍯!", "☀️记得吃饭🍳,元气满满一整天⭐!", "工作辛苦了,喝杯☕,听首歌🎵 放松一下🧘,烦恼全消☁️!", "愿你永远年轻🎉,早日上岸🤔!", "加油💪,认真📖,实现✅!", "旅途愉快✈️,注意安全🛡️,多拍美照📷,一路顺风💨!", "每天笑口常开😊,烦恼抛到九霄云外☁️,好运自然来🍀!", "今晚月色真美🌙,一起散步🚶,吹吹晚风💨,享受宁静时光。", "勇敢🐮🐮,不怕困难!", "天气转凉,注意保暖🧣,多喝热水💧,别感冒🤧!", "有空了? 约上朋友👬,吃顿火锅🍲,聊聊近况,快乐加倍🎉!", "新的一年,愿你拥有🐲🐴精神,事业如🐲头腾飞,家庭和睦🏠,幸福美满❤️!", ]; String _getRandomTip() { final random = Random(); return _tips[random.nextInt(_tips.length)]; } void _showRandomTip() { setState(() { _currentTip = _getRandomTip(); }); } @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppConstants.primaryColor.withOpacity(0.95), AppConstants.primaryColor.withOpacity(0.85), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: GestureDetector( onTap: _toggleExpand, behavior: HitTestBehavior.opaque, child: AnimatedContainer( duration: AppConstants.animationDurationMedium, curve: Curves.easeOut, padding: _isExpanded ? const EdgeInsets.all(20) : const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Stack( children: [ // 毛玻璃效果层 BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container(color: Colors.transparent), ), // 内容层 _isExpanded ? _buildExpandedContent() : _buildCollapsedContent(), ], ), ), ), ), ), ); } Widget _buildCollapsedContent() { return Row( children: [ // 头像 GestureDetector( onTap: widget.onAvatarTap, child: Stack( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Center( child: Text( widget.userData['avatar'] ?? '👤', style: const TextStyle(fontSize: 32), ), ), ), // 在线状态指示器 Positioned( bottom: 0, right: 0, child: Container( width: 16, height: 16, decoration: BoxDecoration( color: _isOnline ? AppConstants.successColor : Colors.red, border: Border.all(color: Colors.white, width: 2), borderRadius: BorderRadius.circular(8), ), ), ), ], ), ), const SizedBox(width: 16), // 名称和签名 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( widget.userData['nickname'] ?? '诗词爱好者', style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( widget.userData['signature'] ?? '人生如诗,岁月如歌', style: const TextStyle(color: Colors.white70, fontSize: 14), overflow: TextOverflow.ellipsis, ), ], ), ), // 页面切换提示 Row( children: [ // 选项提示 Container( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( children: [ _buildCollapsedTabLabel(0, '卡片'), const SizedBox(width: 12), _buildCollapsedTabLabel(1, '设置'), const SizedBox(width: 12), _buildCollapsedTabLabel(2, '更多'), ], ), ), // 滑动指示箭头 Container( padding: const EdgeInsets.all(8), child: Row( children: [ AnimatedRotation( turns: _isExpanded ? 0.5 : 0, duration: AppConstants.animationDurationMedium, child: const Icon( Icons.keyboard_arrow_down, color: Colors.white, size: 20, ), ), const SizedBox(width: 4), const Icon(Icons.swap_horiz, color: Colors.white70, size: 16), ], ), ), ], ), ], ); } Widget _buildExpandedContent() { return Column( children: [ // 头部信息 Row( children: [ // 头像 GestureDetector( onTap: widget.onAvatarTap, child: Stack( children: [ Container( width: 64, height: 64, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 6, offset: const Offset(0, 3), ), ], ), child: Center( child: Text( widget.userData['avatar'] ?? '👤', style: const TextStyle(fontSize: 40), ), ), ), // 在线状态指示器 Positioned( bottom: 2, right: 2, child: Container( width: 20, height: 20, decoration: BoxDecoration( color: _isOnline ? AppConstants.successColor : Colors.red, border: Border.all(color: Colors.white, width: 2), borderRadius: BorderRadius.circular(10), ), ), ), ], ), ), const SizedBox(width: 16), // 个人信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 昵称和等级 Row( children: [ Expanded( child: _isEditingNickname ? TextField( controller: _nicknameController, style: const TextStyle( color: AppConstants.primaryColor, fontSize: 18, fontWeight: FontWeight.bold, ), decoration: const InputDecoration( border: InputBorder.none, focusedBorder: InputBorder.none, enabledBorder: InputBorder.none, hintText: '输入昵称', hintStyle: TextStyle( color: AppConstants.primaryColor, fontSize: 18, fontWeight: FontWeight.bold, ), ), onSubmitted: (value) { setState(() { widget.userData['nickname'] = value; _isEditingNickname = false; }); }, ) : GestureDetector( onTap: () { setState(() { _isEditingNickname = true; _nicknameController.text = widget.userData['nickname'] ?? '诗词爱好者'; }); }, child: Text( widget.userData['nickname'] ?? '诗词爱好者', style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, decoration: TextDecoration.underline, ), ), ), ), const SizedBox(width: 8), GestureDetector( onTap: () { setState(() { if (_isEditingNickname) { // 保存昵称 widget.userData['nickname'] = _nicknameController.text; } else { // 进入编辑模式,初始化文本 _nicknameController.text = widget.userData['nickname'] ?? '诗词爱好者'; } _isEditingNickname = !_isEditingNickname; }); }, child: Icon( _isEditingNickname ? Icons.check : Icons.edit, color: Colors.white70, size: 18, ), ), const SizedBox(width: 8), if (_isUserPlanJoined) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: Colors.amber.withOpacity(0.8), borderRadius: BorderRadius.circular(12), ), child: const Text( 'UEP', style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 4), // 个性签名 Text( widget.userData['signature'] ?? '人生如诗,岁月如歌', style: const TextStyle( color: Colors.white70, fontSize: 14, height: 1.3, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), const SizedBox(height: 10), // 统计数据卡片 Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(16), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildStatItem('浏览', _totalViews), Container( width: 1, height: 40, color: Colors.white.withOpacity(0.2), ), _buildStatItem('点赞', _totalLikes), Container( width: 1, height: 40, color: Colors.white.withOpacity(0.2), ), _buildStatItem('答题', _totalQuestions), ], ), ], ), ), const SizedBox(height: 8), // 祝福语卡片 GestureDetector( onTap: () { HapticFeedback.lightImpact(); _showRandomTip(); }, child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( '💡 $_currentTip', style: const TextStyle( color: AppConstants.primaryColor, fontSize: 14, fontWeight: FontWeight.w600, height: 1.3, ), maxLines: 2, ), ), const SizedBox(width: 12), // 在线状态开关 Switch( value: _isOnline, onChanged: (value) { _toggleOnlineStatus(); }, activeColor: AppConstants.primaryColor, inactiveTrackColor: Colors.grey.shade300, inactiveThumbColor: Colors.grey, ), ], ), ), ), const SizedBox(height: 16), // 页面标签 Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildTabLabel(0, '卡片', Icons.credit_card), const SizedBox(width: 32), _buildTabLabel(1, '设置', Icons.settings), const SizedBox(width: 32), _buildTabLabel(2, '更多', Icons.more_horiz), ], ), const SizedBox(height: 8), // 页面指示器 Container( height: 3, margin: const EdgeInsets.symmetric(horizontal: 40), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(1.5), ), child: Stack( children: [ AnimatedContainer( duration: AppConstants.animationDurationMedium, curve: Curves.easeOut, width: (MediaQuery.of(context).size.width - 160) / 3, height: 3, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(1.5), ), margin: EdgeInsets.only( left: ((MediaQuery.of(context).size.width - 160) / 3) * widget.currentPage, ), ), ], ), ), ], ); } Widget _buildStatItem(String label, int value) { return Column( children: [ Text( value.toString(), style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( label, style: const TextStyle(color: Colors.white70, fontSize: 12), ), ], ); } Widget _buildTabLabel(int index, String label, IconData icon) { final isSelected = widget.currentPage == index; return GestureDetector( onTap: () { widget.onPageChanged?.call(index); }, child: Column( children: [ Icon( icon, color: isSelected ? Colors.white : Colors.white70, size: 20, ), const SizedBox(height: 4), Text( label, style: TextStyle( color: isSelected ? Colors.white : Colors.white70, fontSize: 12, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), ], ), ); } Widget _buildCollapsedTabLabel(int index, String label) { final isSelected = widget.currentPage == index; return GestureDetector( onTap: () { widget.onPageChanged?.call(index); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: isSelected ? Colors.white.withOpacity(0.2) : Colors.transparent, borderRadius: BorderRadius.circular(12), ), child: Text( label, style: TextStyle( color: isSelected ? Colors.white : Colors.white70, fontSize: 12, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), ), ); } }