petsHero-AI/lib/features/profile/profile_screen.dart
2026-03-15 11:25:09 +08:00

341 lines
9.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../../core/user/account_refresh.dart';
import '../recharge/payment_webview_screen.dart';
import '../../core/theme/app_colors.dart';
import '../../core/user/user_state.dart';
import '../../core/theme/app_spacing.dart';
import '../../core/theme/app_typography.dart';
/// Profile screen - matches Pencil KXeow
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key, required this.isActive});
final bool isActive;
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
@override
void initState() {
super.initState();
if (widget.isActive) refreshAccount(updateProfile: true);
}
@override
void didUpdateWidget(covariant ProfileScreen oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isActive && !oldWidget.isActive) {
refreshAccount(updateProfile: true);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(
AppSpacing.screenPadding,
AppSpacing.xxl,
AppSpacing.screenPadding,
AppSpacing.screenPaddingLarge,
),
child: Column(
children: [
ValueListenableBuilder<String?>(
valueListenable: UserState.avatar,
builder: (_, avatarUrl, __) {
return ValueListenableBuilder<String?>(
valueListenable: UserState.userName,
builder: (_, userName, __) {
return ValueListenableBuilder<String?>(
valueListenable: UserState.userId,
builder: (_, userId, __) {
return _ProfileHeader(
avatarUrl: avatarUrl,
userName: userName,
uid: userId,
);
},
);
},
);
},
),
const SizedBox(height: AppSpacing.xl),
_BalanceCard(
balance: UserCreditsData.of(context)?.creditsDisplay ?? '--',
onRecharge: () => Navigator.of(context).pushNamed('/recharge'),
),
const SizedBox(height: AppSpacing.xxl),
_MenuSection(
items: [
_MenuItem(
title: 'Privacy Policy',
icon: LucideIcons.chevron_right,
onTap: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const PaymentWebViewScreen(
paymentUrl: 'http://www.petsheroai.xyz/privacy.html',
title: 'Privacy Policy',
),
),
),
),
_MenuItem(
title: 'User Agreement',
icon: LucideIcons.chevron_right,
onTap: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const PaymentWebViewScreen(
paymentUrl: 'http://www.petsheroai.xyz/terms.html',
title: 'User Agreement',
),
),
),
),
],
),
],
),
),
);
}
}
class _ProfileHeader extends StatelessWidget {
const _ProfileHeader({
this.avatarUrl,
this.userName,
this.uid,
});
final String? avatarUrl;
final String? userName;
final String? uid;
@override
Widget build(BuildContext context) {
return Container(
height: 220,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: AppColors.border,
borderRadius: BorderRadius.circular(40),
border: Border.all(
color: AppColors.primaryLight,
width: 2,
),
),
clipBehavior: Clip.antiAlias,
child: avatarUrl != null && avatarUrl!.isNotEmpty
? CachedNetworkImage(
imageUrl: avatarUrl!,
fit: BoxFit.cover,
placeholder: (_, __) => const Icon(
LucideIcons.user,
size: 40,
color: AppColors.textSecondary,
),
errorWidget: (_, __, ___) => const Icon(
LucideIcons.user,
size: 40,
color: AppColors.textSecondary,
),
)
: const Icon(
LucideIcons.user,
size: 40,
color: AppColors.textSecondary,
),
),
const SizedBox(height: AppSpacing.lg),
Text(
userName ?? 'VIP',
style: AppTypography.bodyLarge.copyWith(
color: AppColors.textPrimary,
),
),
const SizedBox(height: AppSpacing.lg),
Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: AppColors.surfaceAlt,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.border),
),
child: Text(
uid != null && uid!.isNotEmpty ? 'UID $uid' : 'UID --',
style: AppTypography.caption.copyWith(
color: AppColors.textSecondary,
),
),
),
],
),
);
}
}
class _BalanceCard extends StatelessWidget {
const _BalanceCard({
required this.balance,
required this.onRecharge,
});
final String balance;
final VoidCallback onRecharge;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.xxl,
vertical: AppSpacing.xl,
),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
color: AppColors.shadowLight,
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'AVAILABLE BALANCE',
style: AppTypography.label.copyWith(
color: AppColors.textSecondary,
),
),
const SizedBox(height: AppSpacing.sm),
Text(
balance,
style: AppTypography.bodyLarge.copyWith(
fontSize: 24,
fontWeight: FontWeight.w700,
color: AppColors.primary,
),
),
],
),
GestureDetector(
onTap: onRecharge,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: AppSpacing.md,
),
decoration: BoxDecoration(
color: AppColors.primary,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'Recharge',
style: AppTypography.bodyRegular.copyWith(
color: AppColors.surface,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
);
}
}
class _MenuSection extends StatelessWidget {
const _MenuSection({required this.items});
final List<_MenuItem> items;
@override
Widget build(BuildContext context) {
return Column(
children: items
.map(
(item) => Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.md),
child: _MenuItem(
title: item.title,
icon: item.icon,
onTap: item.onTap,
),
),
)
.toList(),
);
}
}
class _MenuItem extends StatelessWidget {
const _MenuItem({
required this.title,
required this.icon,
required this.onTap,
});
final String title;
final IconData icon;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.xl,
vertical: 14,
),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: AppColors.shadowLight,
blurRadius: 6,
offset: Offset(0, 2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: AppTypography.bodyRegular.copyWith(
color: AppColors.textPrimary,
),
),
Icon(icon, size: 20, color: AppColors.textMuted),
],
),
),
);
}
}