seaislee1209 f9666d4aa3 feat: UI规范化 + 故事吸入动画 + 音乐页面优化
- 全局字体统一(Outfit/DM Sans), 头部/按钮/Toast规范化
- 故事详情页: Genie Suck吸入动画(标题+卡片一起缩小模糊消失)
- 书架页: bookPop弹出+粒子效果(三段式动画完整链路)
- 音乐页面: 心情卡片emoji换Material图标+彩色圆块横排布局
- 音乐页面: 进度条胶囊宽度对齐, 播放按钮位置修复, 间距均匀化
- 音乐播放: 接入just_audio, 支持播放暂停进度拖拽自动切歌
- 新增: iOS风格毛玻璃Toast, 渐变背景组件, 通知页面
- 阶段总结文档更新

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 19:34:53 +08:00

290 lines
10 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:airhub_app/theme/design_tokens.dart';
import 'package:airhub_app/widgets/animated_gradient_background.dart';
import 'package:airhub_app/pages/profile/guide_feeding_page.dart';
class HelpPage extends StatelessWidget {
const HelpPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: Stack(
children: [
const AnimatedGradientBackground(),
Column(
children: [
_buildHeader(context),
Expanded(
child: ShaderMask(
shaderCallback: (Rect rect) {
return const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Colors.black, Colors.black, Colors.transparent],
stops: [0.0, 0.12, 0.92, 1.0],
).createShader(rect);
},
blendMode: BlendMode.dstIn,
child: SingleChildScrollView(
padding: EdgeInsets.only(
top: 20,
left: 20,
right: 20,
bottom: 40 + MediaQuery.of(context).padding.bottom,
),
child: Column(
children: [
const Text(
'帮助 Q&A',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 8),
const Text(
'更新日期2025年1月15日',
style: TextStyle(
fontSize: 13,
color: AppColors.textSecondary,
),
),
const SizedBox(height: 24),
_buildGuideCard(context),
const SizedBox(height: 20),
_buildFaqSection('设备连接与管理', [
_FaqItem(
'手机连接设备时"未扫描到设备"',
'请检查设备是否在配网模式下双击设备电源键按钮直至呈现Wi-Fi图标请确保设备和手机距离在10m内点击【重新扫描】。',
),
_FaqItem(
'手机连接设备时"连接设备失败"',
'可能为服务超时造成的异常,请保持设备处于配网模式下,点击【再试一次】。',
),
_FaqItem(
'如何添加多个 Wi-Fi 网络?',
'进入设备控制页 → 设置 → 配置网络,按提示添加备用网络。设备会自动切换到信号最强的网络。',
),
]),
_buildFaqSection('角色养成', [
_FaqItem(
'什么是角色记忆?',
'角色记忆是您与 AI 互动过程中产生的人格数据,包含对话风格、喜好偏好等信息。角色记忆可以在不同设备间迁移,让您的 AI 伙伴始终如一。',
),
_FaqItem(
'如何将角色记忆迁移到新设备?',
'进入「我的」→「角色记忆」,找到需要迁移的记忆,点击「注入设备」,选择目标设备即可完成迁移。',
),
]),
_buildFaqSection('常见问题', [
_FaqItem(
'设备离线怎么办?',
'请检查设备电源和网络连接。如果问题持续,尝试重启设备或重新配网。',
),
_FaqItem(
'如何联系客服?',
'您可以通过「我的」→「意见反馈」联系我们,或发送邮件至 support@airhub.com。',
),
]),
],
),
),
),
),
],
),
],
),
);
}
Widget _buildHeader(BuildContext context) {
return Container(
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 20,
left: AppSpacing.lg,
right: AppSpacing.lg,
bottom: AppSpacing.md,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppColors.iconBtnBg,
borderRadius: BorderRadius.circular(AppRadius.button),
),
child: const Icon(
Icons.arrow_back_ios_new,
color: AppColors.textPrimary,
size: 18,
),
),
),
Text('帮助中心', style: AppTextStyles.title),
const SizedBox(width: 40),
],
),
);
}
Widget _buildGuideCard(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFFEF9E7), Color(0xFFFDF2E9)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: const Color(0xFF8B5E3C).withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFECCFA8), Color(0xFFC99672)],
),
borderRadius: BorderRadius.circular(12),
),
alignment: Alignment.center,
child: const Text('📖', style: TextStyle(fontSize: 24)),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'喂养指南',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
SizedBox(height: 4),
Text(
'详细的角色养成方法和日常照顾指南',
style: TextStyle(
fontSize: 13,
color: AppColors.textSecondary,
),
),
],
),
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const GuideFeedingPage()),
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFECCFA8), Color(0xFFC99672)],
),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'查看 →',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
Widget _buildFaqSection(String title, List<_FaqItem> items) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 4, bottom: 8),
child: Text(
title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColors.sectionTitle,
letterSpacing: 0.5,
),
),
),
Container(
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.8),
borderRadius: BorderRadius.circular(16),
boxShadow: const [AppShadows.card],
),
clipBehavior: Clip.antiAlias,
child: Column(
children: items.map((item) => _buildExpansionTile(item)).toList(),
),
),
],
);
}
Widget _buildExpansionTile(_FaqItem item) {
return Theme(
data: ThemeData().copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
title: Text(
item.question,
style: const TextStyle(fontSize: 15, color: AppColors.textPrimary),
),
childrenPadding: const EdgeInsets.only(left: 20, right: 20, bottom: 16),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.answer,
style: const TextStyle(
fontSize: 14,
color: AppColors.textSecondary,
height: 1.5,
),
),
],
),
);
}
}
class _FaqItem {
final String question;
final String answer;
_FaqItem(this.question, this.answer);
}