Files
wushu/lib/views/profile/per_card.dart
2026-04-02 22:30:49 +08:00

831 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.
import 'package:flutter/material.dart';
import 'dart:ui';
import 'dart:math';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:get/get.dart';
import '../../constants/app_constants.dart';
import '../../models/colors/theme_colors.dart';
import '../../services/get/theme_controller.dart';
import '../../services/network_listener_service.dart';
import 'guide/tongji.dart';
/// 时间: 2026-03-31
/// 功能: 个人信息卡片组件
/// 介绍: 可收起/张开的个人信息卡片,支持下拉张开,上滑收起
/// 最新变化: 重新设计为苹果风格,优化动画效果,改进布局结构,提升用户体验
class PersonalCard extends StatefulWidget {
final Map<String, dynamic> userData;
final bool? isExpanded;
final ValueChanged<bool>? onExpandChanged;
final int currentPage;
final ValueChanged<int>? onPageChanged;
final VoidCallback? onAvatarTap;
const PersonalCard({
super.key,
required this.userData,
this.isExpanded,
this.onExpandChanged,
this.currentPage = 0,
this.onPageChanged,
this.onAvatarTap,
});
@override
State<PersonalCard> createState() => PersonalCardState();
}
class PersonalCardState extends State<PersonalCard> {
late bool _isExpanded;
late String _currentTip;
bool _isOnline = true;
bool _isEditingNickname = false;
bool _isUserPlanJoined = false;
late TextEditingController _nicknameController;
int _currentAvatarIndex = 0;
final ThemeController _themeController = Get.find<ThemeController>();
// 累计数据
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<void> _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<void> refreshData() async {
await _loadTotalStats();
await _loadUserPlanStatus();
}
Future<void> _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<void> _loadUserPlanStatus() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_isUserPlanJoined = prefs.getBool('user_plan_joined') ?? false;
});
}
Future<void> _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;
});
}
// 监听currentPage变化触发UI更新
if (oldWidget.currentPage != widget.currentPage) {
setState(() {
// currentPage变化触发UI重新构建
});
}
}
void _toggleExpand() {
setState(() {
_isExpanded = !_isExpanded;
widget.onExpandChanged?.call(_isExpanded);
});
}
final List<String> _tips = [
"🐴年大🍊",
"🌙安",
"🧧到福到",
"🍎🍎安安",
"💪健康",
"🐟🐟有余",
"🍊🍈吉利",
"🐲🐴精神",
"💰满钵满",
"😴好梦",
"🐴年好☁️,大🍊大🍈,心想事🍊,万事🍅意!",
"🌙了,早点🛏️,明天再战,加油💪!",
"今天吃了🍎,平平安安;吃了🍊,大吉大利;喝了🥛,白白胖胖👶!",
"祝你🐴到成功,💰源滚滚,步步高升📈,生活甜如蜜🍯!",
"☀️记得吃饭🍳,元气满满一整天⭐!",
"工作辛苦了,喝杯☕,听首歌🎵 放松一下🧘,烦恼全消☁️!",
"愿你永远年轻🎉,早日上岸🤔!",
"加油💪,认真📖,实现✅!",
"旅途愉快✈️,注意安全🛡️,多拍美照📷,一路顺风💨!",
"每天笑口常开😊,烦恼抛到九霄云外☁️,好运自然来🍀!",
"今晚月色真美🌙,一起散步🚶,吹吹晚风💨,享受宁静时光。",
"勇敢🐮🐮,不怕困难!",
"天气转凉,注意保暖🧣,多喝热水💧,别感冒🤧!",
"有空了? 约上朋友👬,吃顿火锅🍲,聊聊近况,快乐加倍🎉!",
"新的一年,愿你拥有🐲🐴精神,事业如🐲头腾飞,家庭和睦🏠,幸福美满❤️!",
];
final List<String> _avatarEmojis = [
'👤',
'😊',
'🎨',
'🌟',
'🦋',
'🌺',
'🍀',
'🎯',
'🚀',
'💎',
'🌈',
'🎭',
'🦊',
'🐼',
'🦁',
'🐨',
'🦄',
'🐉',
'🔥',
'',
];
String _getRandomTip() {
final random = Random();
return _tips[random.nextInt(_tips.length)];
}
void _showRandomTip() {
setState(() {
_currentTip = _getRandomTip();
});
}
void _switchAvatar() {
setState(() {
_currentAvatarIndex = (_currentAvatarIndex + 1) % _avatarEmojis.length;
widget.userData['avatar'] = _avatarEmojis[_currentAvatarIndex];
});
// 添加触觉反馈
HapticFeedback.lightImpact();
// 显示切换提示
Get.snackbar(
'头像切换',
'头像已切换为 ${_avatarEmojis[_currentAvatarIndex]}',
duration: const Duration(seconds: 1),
);
}
@override
Widget build(BuildContext context) {
final primaryColor = _themeController.currentThemeColor;
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: [
primaryColor.withOpacity(0.95),
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(primaryColor)
: _buildCollapsedContent(),
],
),
),
),
),
),
);
}
Widget _buildCollapsedContent() {
return Row(
children: [
// 头像
GestureDetector(
onTap: () {
_switchAvatar();
widget.onAvatarTap?.call();
},
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(Color primaryColor) {
return Column(
children: [
// 头部信息
Row(
children: [
// 头像
GestureDetector(
onTap: () {
_switchAvatar();
widget.onAvatarTap?.call();
},
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: TextStyle(
color: primaryColor,
fontSize: 18,
fontWeight: FontWeight.bold,
),
decoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
hintText: '输入昵称',
hintStyle: TextStyle(
color: 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: TextStyle(
color: primaryColor,
fontSize: 14,
fontWeight: FontWeight.w600,
height: 1.3,
),
maxLines: 2,
),
),
const SizedBox(width: 12),
// 在线状态开关
Switch(
value: _isOnline,
onChanged: (value) {
_toggleOnlineStatus();
},
activeColor: 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,
),
),
),
);
}
}