
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:signals/signals_flutter.dart';
import 'package:tuyu/components/image_widget.dart';
import 'package:tuyu/routes/ext/user_route_ext.dart';
import 'package:tuyu/store/user_signal.dart';
class MinePage extends StatefulWidget {
const MinePage({super.key});
@override
State<MinePage> createState() => _MinePageState();
}
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
class _MinePageState extends State<MinePage> with RouteAware {
@override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute);
}
@override
void didPush() => _refreshUser();
@override
void didPushNext() => _refreshUser();
@override
void didPopNext() => _refreshUser();
void _refreshUser() {
if (userSignal.isLogin()) userSignal.fetchCurrentUser();
}
@override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
final double statusBarHeight = MediaQuery.of(context).padding.top;
return Scaffold(
backgroundColor: const Color(0xFFF5F7FA),
extendBodyBehindAppBar: true,
body: Stack(
children: [
CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Stack(
clipBehavior: Clip.none,
children: [
Stack(
clipBehavior: Clip.none,
children: [
ImageWidget(
url: 'https://picsum.photos/id/209/800/600',
width: double.infinity,
height: 300,
fit: BoxFit.cover,
useImageUtil: false,
),
Positioned(
bottom: -5,
left: 0,
right: 0,
height: 60,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.white.withValues(alpha: 0.0),
const Color(
0xFFF5F7FA,
).withValues(alpha: 1),
],
),
),
),
),
),
),
Watch((_) {
final isLogin = userSignal.isLogin.value;
return isLogin
? Positioned(
top: statusBarHeight + 10,
right: 16,
child: Row(
children: [
const SizedBox(width: 12),
_CircleIconBtn(
icon: Icons.settings_outlined,
onTap: () => context.push('/settings'),
),
],
),
)
: const SizedBox.shrink();
}),
],
),
Positioned(
top: 140,
left: 0,
right: 0,
child: const _MineHeader(),
),
],
),
),
Watch((_) {
final isLogin = userSignal.isLogin.value;
final double spacing = isLogin ? 80 : 20;
return SliverToBoxAdapter(child: SizedBox(height: spacing));
}),
const SliverToBoxAdapter(child: _VipCard()),
const SliverToBoxAdapter(child: SizedBox(height: 20)),
SliverToBoxAdapter(child: _MineFeatureGrid()),
const SliverToBoxAdapter(child: SizedBox(height: 32)),
const SliverToBoxAdapter(
child: Center(
child: Text(
"艺图语 V0.0.1",
style: TextStyle(fontSize: 14, color: Colors.grey),
),
),
),
const SliverToBoxAdapter(child: SizedBox(height: 40)),
],
),
],
),
);
}
}
class _CircleIconBtn extends StatelessWidget {
final IconData icon;
final VoidCallback onTap;
const _CircleIconBtn({required this.icon, required this.onTap});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
splashColor: Colors.white.withOpacity(0.3),
highlightColor: Colors.white.withOpacity(0.1),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(icon, color: Colors.white, size: 20),
),
);
}
}
class _MineHeader extends StatelessWidget {
const _MineHeader();
@override
Widget build(BuildContext context) {
return Watch((_) {
return userSignal.isLogin.value
? _loginHeader(context)
: _guestHeader(context);
});
}
Widget _loginHeader(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Stack(
clipBehavior: Clip.none,
children: [
Container(
width: MediaQuery.of(context).size.width,
height: 220,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: Padding(
padding: const EdgeInsets.only(top: 50, left: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userSignal.currentUser.value?.name ?? '用户加载中',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const SizedBox(height: 8),
InkWell(
onTap: () {
final userId = userSignal.currentUser.value?.id;
if (userId != null) {
Clipboard.setData(
ClipboardData(text: userId.toString()),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('用户ID已复制'),
duration: Duration(seconds: 2),
),
);
}
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"ID: ${userSignal.currentUser.value?.id ?? '---'}",
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
const SizedBox(width: 10),
Icon(
Icons.copy_outlined,
size: 12,
color: Colors.grey.shade500,
),
],
),
),
),
const SizedBox(height: 10),
InkWell(
onTap: () => context.push('/user/profile'),
child: Row(
children: [
Flexible(
child: Text(
userSignal.currentUser.value?.intro ?? '暂无签名',
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Icon(
Icons.edit_outlined,
size: 16,
color: Colors.grey.shade500,
),
const SizedBox(width: 16),
],
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_StatItem(
count: userSignal.currentUser.value?.albumCount ?? 0,
label: "作品数",
),
const SizedBox(width: 36),
_StatItem(
count: userSignal.currentUser.value?.followCount ?? 0,
label: "关注",
),
const SizedBox(width: 36),
_StatItem(
count: userSignal.currentUser.value?.fansCount ?? 0,
label: "粉丝",
),
],
),
],
),
),
),
Positioned(
top: -30,
left: 20,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 4),
),
child: CircleImageWidget(
url: userSignal.currentUser.value?.face ?? '',
size: 64,
),
),
),
],
),
);
}
Widget _guestHeader(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Container(
width: MediaQuery.of(context).size.width,
height: 160,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.only(top: 60, left: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkWell(
onTap: () => context.pushLogin(),
child: const Text(
"登录/注册",
style: TextStyle(
fontSize: 23,
fontWeight: FontWeight.w500,
color: Color(0xFF4DD0E1),
),
),
),
const SizedBox(height: 12),
const Text(
"登录后,解锁更多功能",
style: TextStyle(color: Colors.grey),
),
],
),
),
),
Positioned(
top: -30,
child: CircleAvatar(
radius: 35,
backgroundColor: Colors.grey[400],
child: Icon(Icons.person, size: 35, color: Colors.grey.shade300),
),
),
],
),
);
}
}
class _StatItem extends StatelessWidget {
final int count;
final String label;
const _StatItem({required this.count, required this.label});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
count.toString(),
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)),
],
);
}
}
class _VipCard extends StatelessWidget {
const _VipCard();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF2B231D), Color(0xFF4A3F35)],
),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
const Icon(Icons.wallet_giftcard_rounded, color: Color(0xFFFFD1A1)),
const SizedBox(width: 10),
const Expanded(
child: Text(
"会员尊享VIP作品免费读、\n不看广告等特权",
style: TextStyle(
color: Color(0xFFFFD1A1),
fontSize: 11,
height: 1.4,
),
),
),
ElevatedButton(
onPressed: () {
if (!userSignal.isLogin.value) {
context.pushLogin();
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('VIP功能开发中,敬请期待'),
duration: Duration(seconds: 2),
),
);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
elevation: 0,
shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(horizontal: 12),
),
child: const Text("立即开通", style: TextStyle(fontSize: 12)),
),
],
),
),
);
}
}
class _MineFeatureGrid extends StatelessWidget {
List<_FeatureItem> _items(BuildContext context) => [
_FeatureItem(
Icons.favorite_rounded,
"我的点赞",
Colors.red,
() => context.push('/liked'),
),
_FeatureItem(
Icons.explore_rounded,
"浏览历史",
Colors.orange,
() => context.push('/user/history'),
),
_FeatureItem(
Icons.videocam_rounded,
"我的信息",
Colors.blue,
() => context.push('/user/profile'),
),
_FeatureItem(
Icons.people_rounded,
"我的关注",
Colors.green,
() => context.push('/following/list'),
),
_FeatureItem(
Icons.group_rounded,
"我的粉丝",
Colors.purple,
() => context.push('/follower/list'),
),
_FeatureItem(
Icons.storefront_rounded,
"成为摄影师",
Colors.amber,
() => context.push('/photographer/apply'),
),
_FeatureItem(
Icons.settings_rounded,
"设置",
Colors.grey,
() => context.push('/settings'),
),
_FeatureItem(
Icons.feedback_rounded,
"问题反馈",
Colors.teal,
() => context.push('/feedback'),
),
_FeatureItem(
Icons.chat_rounded,
"功能交流",
Colors.indigo,
() => context.push('/feedback'),
),
_FeatureItem(
Icons.palette_rounded,
"主题设置",
Colors.pink,
() => context.push('/settings/theme'),
),
];
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: GridView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
mainAxisSpacing: 20,
childAspectRatio: 0.9,
),
itemCount: _items(context).length,
itemBuilder: (context, index) {
final item = _items(context)[index];
return InkWell(
onTap: () {
if (!userSignal.isLogin.value) {
context.pushLogin();
} else {
item.onTap?.call();
}
},
splashColor: const Color(0xFF4DD0E1).withValues(alpha: 0.2),
highlightColor: const Color(0xFF4DD0E1).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(15),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: item.color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(15),
),
child: Icon(item.icon, color: item.color, size: 24),
),
const SizedBox(height: 8),
Text(
item.label,
style: const TextStyle(fontSize: 11, color: Colors.black87),
),
],
),
);
},
),
);
}
}
class _FeatureItem {
final IconData icon;
final String label;
final Function()? onTap;
final Color color;
const _FeatureItem(this.icon, this.label, this.color, [this.onTap]);
}