import 'dart:async'; 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/core/widgets/avatar_upload_sheet.dart'; import 'package:patbond_flutter/features/community/community_controller.dart'; import 'package:patbond_flutter/features/community/community_interaction_analytics.dart'; import 'package:patbond_flutter/features/community/feed_analytics.dart'; import 'package:patbond_flutter/features/community/media_uploader.dart'; import 'package:patbond_flutter/features/community/post_analytics.dart'; import 'package:patbond_flutter/features/community/post_compose_page.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_controller.dart'; import 'package:patbond_flutter/features/profile/profile_page.dart'; import 'package:patbond_flutter/features/services/services_page.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, required this.communityController, required this.profileController, super.key, this.currentUserId, this.petAnalytics, this.healthRecordAnalytics, this.feedAnalytics, this.interactionAnalytics, this.postAnalytics, this.mediaUploaderFactory, this.avatarUploaderBuilder, this.pageViewTracker, this.onLogout, }); final AppState appState; /// 宠物档案状态(T2-11 拆出的独立 pets feature;档案 Tab 数据源)。 final PetsController petsController; /// 社区状态(T3-12 数据层;首页 Feed segment 数据源,T3-14 接线)。 final CommunityController communityController; /// 本人资料与社区数字(T3.5-08);资料 Tab 与首页问候语共用。 final ProfileController profileController; /// 当前登录用户 id(详情页评论删除入口 / 关注钮自见性的 UI 判定)。 final String? currentUserId; /// pet 域埋点强类型封装(建宠漏斗三事件)。 final PetAnalytics? petAnalytics; /// health_record 域埋点强类型封装(T2-13 创建漏斗三事件 + viewed)。 final HealthRecordAnalytics? healthRecordAnalytics; /// feed 域埋点(T3-14 聚合曝光 + 加载失败)。 final FeedAnalytics? feedAnalytics; /// 互动域埋点(T3-16 评论成败对 + 关注对;详情页消费)。 final CommunityInteractionAnalytics? interactionAnalytics; /// post 域埋点(T3-17 发布漏斗五事件 + 媒体三段;发布页消费)。 final PostAnalytics? postAnalytics; /// 发布页 [MediaUploader] 构造口(桌面实测 / 测试替换选图与压缩层)。 final MediaUploaderFactory? mediaUploaderFactory; /// 头像上传构造口(T3.5-08/09:资料编辑页与宠物详情页)。 final AvatarUploaderBuilder? avatarUploaderBuilder; /// Tab 曝光补点(IndexedStack 切换不产生路由事件,03 号评估 §3.2)。 final PageViewTracker? pageViewTracker; /// 退出登录:调 logout 接口并清会话,认证状态机自动回登录页。 final Future Function()? onLogout; @override State createState() => _MainShellPageState(); } class _MainShellPageState extends State { int currentIndex = 0; bool showPersonalServices = false; static const titles = ['首页', '创作中心', '健康档案', '本地服务', '我的资料']; /// Tab 索引 → pageName 枚举(03 号评估 §3.2 映射表;档案 Tab 自 T2-12 /// 起为真实宠物列表,页名由 pet_archive 改报 pet_list,06 §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]); } /// 帖子详情(T3-15:真实数据整页;Feed 卡片与详情共享 /// communityController,互动状态跨页一致)。 void openPost(String postId) { Navigator.of(context).push( MaterialPageRoute( settings: RouteSettings(name: AnalyticsPageName.postDetail.pageName), builder: (context) => PostDetailPage( controller: widget.communityController, postId: postId, currentUserId: widget.currentUserId, analytics: widget.interactionAnalytics, ), ), ); } /// 发布页(T3-17:真实发布链路)。发布成功后回首页 Feed 并整体刷新, /// 新帖按 `(published_at DESC, id DESC)` 落在首位(跨客户端同一序)。 Future openCompose(PostEntryPoint entryPoint) async { final published = await Navigator.of(context).push( MaterialPageRoute( settings: RouteSettings(name: AnalyticsPageName.postForm.pageName), builder: (context) => PostComposePage( controller: widget.communityController, entryPoint: entryPoint, analytics: widget.postAnalytics, uploaderFactory: widget.mediaUploaderFactory, ), ), ); if (!mounted || published != true) return; selectTab(0); unawaited(widget.communityController.refresh()); ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('已发布,去首页看看吧 🐾'))); } @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, communityController: widget.communityController, profileController: widget.profileController, feedAnalytics: widget.feedAnalytics, isActive: currentIndex == 0, onOpenServices: openServices, onOpenCompose: () => openCompose(PostEntryPoint.feed), onOpenPost: openPost, ), CreatePage( appState: widget.appState, // T3-17:发布半边已真实化(发布页 push),AI 生成模拟原样留 M4。 onOpenCompose: () => openCompose(PostEntryPoint.createTab), ), PetsPage( controller: widget.petsController, analytics: widget.petAnalytics, healthAnalytics: widget.healthRecordAnalytics, avatarUploaderBuilder: widget.avatarUploaderBuilder, ), ServicesPage( showPersonal: showPersonalServices, locationWeather: widget.appState.locationWeather, ), ProfilePage( appState: widget.appState, controller: widget.profileController, avatarUploaderBuilder: widget.avatarUploaderBuilder, 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: '我的', ), ], ), ); }, ); } }