Files
slogan/lib/features/profile/profile_page.dart
T
adminandClaude Opus 4.7 3e457b700a refactor: 前端改 2 Tab(穿搭向导 + 我的入口)
- 主框架收敛为「穿搭」「我的」两个 tab:
  - 穿搭 = 造型方案制作向导:个人信息完整性检查(4 类照片 +
    身形参数,缺失引导去完善)+ 任务参数(日期范围/地点)+ 已有方案
  - 我的 = 个人信息管理入口:用户卡片(昵称/会员状态)+ 我的形象 /
    我的衣橱 / 会员中心入口 + 退出登录
- profile_page 移除退出登录(归「我的」页)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-31 16:25:36 +08:00

200 lines
5.8 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 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:go_router/go_router.dart';
import 'avatar_provider.dart';
import 'body_provider.dart';
import 'photo_upload_provider.dart';
/// 我的形象:化身 + 照片 + 身形(退出登录在「我的」页)
class ProfilePage extends ConsumerWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final avatar = ref.watch(avatarProvider);
final photos = ref.watch(photoProvider);
final body = ref.watch(bodyProvider);
return Scaffold(
appBar: AppBar(title: const Text('我的形象')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildAvatarCard(context, ref, avatar),
const SizedBox(height: 12),
_buildPhotoCard(context, ref, photos),
const SizedBox(height: 12),
_buildBodyCard(context, ref, body),
],
),
);
}
Widget _buildAvatarCard(BuildContext context, WidgetRef ref,
AsyncValue<AvatarState> avatar) {
return _SectionCard(
title: '3D 化身',
icon: Icons.face_retouching_natural,
onActionText: null,
child: avatar.when(
loading: () => const Padding(
padding: EdgeInsets.all(16),
child: Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 2))),
),
error: (e, _) => _ActionRow(
text: '构建失败:${e.toString().replaceFirst('Exception: ', '')}',
actionText: '重试',
onAction: () => ref.read(avatarProvider.notifier).buildAvatar(),
),
data: (a) => _ActionRow(
text: a.built
? '已构建(模板 ${a.faceTemplateId}/${a.bodyTemplateId}'
: '尚未构建化身,需要先完成照片与身形',
actionText: a.built ? '查看' : '立即构建',
onAction: a.built
? () => context.go('/avatar-viewer')
: () async {
await ref.read(avatarProvider.notifier).buildAvatar();
if (context.mounted) {
Fluttertoast.showToast(msg: '化身构建完成');
}
},
),
),
);
}
Widget _buildPhotoCard(
BuildContext context, WidgetRef ref, AsyncValue<List<UserPhotoInfo>> photos) {
return _SectionCard(
title: '形象照片',
icon: Icons.photo_camera_outlined,
onActionText: '去拍摄',
onAction: () => context.go('/photo-guide'),
child: photos.when(
loading: () => const _InfoText('加载中...'),
error: (e, _) => _InfoText('加载失败:$e'),
data: (list) => Column(
children: [
for (final type in PhotoType.all)
_InfoText(
'${PhotoType.labels[type]}'
'${list.any((p) => p.type == type) ? '已上传' : '未上传'}'),
],
),
),
);
}
Widget _buildBodyCard(
BuildContext context, WidgetRef ref, AsyncValue<BodyState> body) {
return _SectionCard(
title: '身形参数',
icon: Icons.accessibility_new,
onActionText: '去设置',
onAction: () => context.go('/body-tune'),
child: body.when(
loading: () => const _InfoText('加载中...'),
error: (e, _) => _InfoText('加载失败:$e'),
data: (b) => _InfoText(
'身高 ${b.height}cm · 体重 ${b.weight}kg · 肤色 ${b.skinTone}'),
),
);
}
}
class _SectionCard extends StatelessWidget {
final String title;
final IconData icon;
final Widget child;
final String? onActionText;
final VoidCallback? onAction;
const _SectionCard({
required this.title,
required this.icon,
required this.child,
this.onActionText,
this.onAction,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon,
size: 20, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 8),
Text(title,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 12),
child,
if (onActionText != null) ...[
const SizedBox(height: 12),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: onAction,
child: Text(onActionText!),
),
),
],
],
),
),
);
}
}
class _ActionRow extends StatelessWidget {
final String text;
final String actionText;
final VoidCallback? onAction;
const _ActionRow({
required this.text,
required this.actionText,
required this.onAction,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Text(text, style: const TextStyle(fontSize: 13))),
if (onAction != null)
TextButton(onPressed: onAction, child: Text(actionText)),
],
);
}
}
class _InfoText extends StatelessWidget {
final String text;
const _InfoText(this.text);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(text, style: const TextStyle(fontSize: 13)),
);
}
}