Files
patbond-flutter/lib/features/main/main_shell_page.dart
T
lixi c91f18a845
CI / flutter-gates (push) Successful in 1m49s
新增:疫苗登记/记录列表与档案页摘要卡接入(T2-13 疫苗半边)
- VaccinationFormPage:目录按宠物物种过滤(四态)、seriesKey 目录 code
  预填、scheduled/completed 状态-日期规则结构化 + 纯函数双重前端拦截,
  42201/40904 后端兜底横幅(均有测试)
- VaccinationRecordsPage:按系列分组、三态 TagPill(含 cancelled 留痕)、
  四态齐备;viewer 隐藏登记入口
- 档案页数据卡行接 GET /pets/{id}/summary 实时聚合:最新体重/疫苗进度/
  下一针三卡,null 语义为空态文案而非 0/0;从记录页返回即重拉摘要
- health_record 埋点装配(app→shell→pets→detail→记录页面族)
- 测试 205 → 224(+19)全绿;analyze 0 问题;format 无 diff

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 13:20:04 +08:00

229 lines
7.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:patbond_flutter/analytics/analytics_page_name.dart';
import 'package:patbond_flutter/analytics/page_view_tracker.dart';
import 'package:patbond_flutter/core/theme/app_theme.dart';
import 'package:patbond_flutter/features/create/create_page.dart';
import 'package:patbond_flutter/features/home/home_page.dart';
import 'package:patbond_flutter/features/pets/health_record_analytics.dart';
import 'package:patbond_flutter/features/pets/pet_analytics.dart';
import 'package:patbond_flutter/features/pets/pets_controller.dart';
import 'package:patbond_flutter/features/pets/pets_page.dart';
import 'package:patbond_flutter/features/post/post_detail_page.dart';
import 'package:patbond_flutter/features/profile/profile_page.dart';
import 'package:patbond_flutter/features/services/services_page.dart';
import 'package:patbond_flutter/models/models.dart';
import 'package:patbond_flutter/state/app_state.dart';
import 'package:patbond_flutter/widgets/common.dart';
class MainShellPage extends StatefulWidget {
const MainShellPage({
required this.appState,
required this.petsController,
super.key,
this.petAnalytics,
this.healthRecordAnalytics,
this.pageViewTracker,
this.onLogout,
});
final AppState appState;
/// 宠物档案状态(T2-11 拆出的独立 pets feature;档案 Tab 数据源)。
final PetsController petsController;
/// pet 域埋点强类型封装(建宠漏斗三事件)。
final PetAnalytics? petAnalytics;
/// health_record 域埋点强类型封装(T2-13 创建漏斗三事件 + viewed)。
final HealthRecordAnalytics? healthRecordAnalytics;
/// Tab 曝光补点(IndexedStack 切换不产生路由事件,03 号评估 §3.2)。
final PageViewTracker? pageViewTracker;
/// 退出登录:调 logout 接口并清会话,认证状态机自动回登录页。
final Future<void> Function()? onLogout;
@override
State<MainShellPage> createState() => _MainShellPageState();
}
class _MainShellPageState extends State<MainShellPage> {
int currentIndex = 0;
bool showPersonalServices = false;
static const titles = ['首页', '创作中心', '健康档案', '本地服务', '我的资料'];
/// Tab 索引 → pageName 枚举(03 号评估 §3.2 映射表;档案 Tab 自 T2-12
/// 起为真实宠物列表,页名由 pet_archive 改报 pet_list06 §5.2 字典 v2)。
static const _tabPages = [
AnalyticsPageName.home,
AnalyticsPageName.create,
AnalyticsPageName.petList,
AnalyticsPageName.services,
AnalyticsPageName.profile,
];
@override
void initState() {
super.initState();
// 初始 Tab 曝光补一次(03 号评估 §3.2 手动补点 1)。
widget.pageViewTracker?.reportTab(_tabPages[currentIndex]);
}
void selectTab(int index) {
setState(() => currentIndex = index);
widget.pageViewTracker?.reportTab(_tabPages[index]);
}
void openServices(bool personal) {
setState(() {
showPersonalServices = personal;
currentIndex = 3;
});
widget.pageViewTracker?.reportTab(_tabPages[3]);
}
void openPost(PostModel post) {
Navigator.of(context).push(
MaterialPageRoute<void>(
settings: RouteSettings(name: AnalyticsPageName.postDetail.pageName),
builder: (context) =>
PostDetailPage(appState: widget.appState, postId: post.id),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: widget.appState,
builder: (context, _) {
if (!widget.appState.isReady) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
final pages = [
HomePage(
appState: widget.appState,
onOpenPost: openPost,
onOpenServices: openServices,
onOpenCreate: () => selectTab(1),
),
CreatePage(
appState: widget.appState,
onPublished: (post) {
selectTab(0);
WidgetsBinding.instance.addPostFrameCallback(
(_) => openPost(post),
);
},
),
PetsPage(
controller: widget.petsController,
analytics: widget.petAnalytics,
healthAnalytics: widget.healthRecordAnalytics,
),
ServicesPage(
showPersonal: showPersonalServices,
locationWeather: widget.appState.locationWeather,
),
ProfilePage(appState: widget.appState, onLogout: widget.onLogout),
];
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white.withAlpha(245),
surfaceTintColor: Colors.transparent,
scrolledUnderElevation: 1,
centerTitle: true,
title: Text(
titles[currentIndex],
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w800),
),
leadingWidth: 118,
leading: InkWell(
onTap: () => selectTab(2),
borderRadius: BorderRadius.circular(24),
child: Padding(
padding: const EdgeInsets.only(left: 14),
child: Row(
children: [
RemoteImage(
url: widget.appState.pet.avatarUrl,
width: 36,
height: 36,
borderRadius: BorderRadius.circular(18),
),
const SizedBox(width: 7),
const Expanded(
child: Text(
'Patbond',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: AppColors.primary,
fontSize: 13,
fontWeight: FontWeight.w800,
),
),
),
],
),
),
),
actions: [
IconButton.filledTonal(
tooltip: '通知',
onPressed: () {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('暂无新通知 🐾')));
},
icon: const Icon(Icons.notifications_outlined, size: 21),
),
const SizedBox(width: 10),
],
),
body: SafeArea(
top: false,
child: IndexedStack(index: currentIndex, children: pages),
),
bottomNavigationBar: NavigationBar(
selectedIndex: currentIndex,
onDestinationSelected: selectTab,
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home_rounded),
label: '首页',
),
NavigationDestination(
icon: Icon(Icons.auto_awesome_outlined),
selectedIcon: Icon(Icons.auto_awesome),
label: '创作',
),
NavigationDestination(
icon: Icon(Icons.pets_outlined),
selectedIcon: Icon(Icons.pets),
label: '档案',
),
NavigationDestination(
icon: Icon(Icons.storefront_outlined),
selectedIcon: Icon(Icons.storefront),
label: '服务',
),
NavigationDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: '我的',
),
],
),
);
},
);
}
}