import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:patbond_flutter/analytics/analytics_page_name.dart'; import 'package:patbond_flutter/analytics/analytics_route_observer.dart'; import 'package:patbond_flutter/analytics/analytics_service.dart'; import 'package:patbond_flutter/analytics/page_view_tracker.dart'; import 'package:patbond_flutter/analytics/session_tracker.dart'; import 'package:patbond_flutter/core/network/api_client.dart'; import 'package:patbond_flutter/core/network/token_refresher.dart'; import 'package:patbond_flutter/core/theme/app_theme.dart'; import 'package:patbond_flutter/features/auth/auth_repository.dart'; import 'package:patbond_flutter/features/auth/login_page.dart'; import 'package:patbond_flutter/features/auth/session_manager.dart'; import 'package:patbond_flutter/features/auth/splash_page.dart'; import 'package:patbond_flutter/features/main/main_shell_page.dart'; import 'package:patbond_flutter/state/app_state.dart'; class App extends StatefulWidget { const App({super.key, this.sessionManager, this.authRepository}); /// 测试注入口;生产默认走安全存储 + 真实 API。 final SessionManager? sessionManager; final AuthRepository? authRepository; @override State createState() => _AppState(); } class _AppState extends State { late final AppState appState; late final SessionManager sessionManager; late final AuthRepository authRepository; late final SessionTracker _sessionTracker; late final AnalyticsService _analytics; late final PageViewTracker _pageViewTracker; late final AnalyticsRouteObserver _routeObserver; @override void initState() { super.initState(); appState = AppState()..load(); sessionManager = widget.sessionManager ?? SessionManager(store: const SecureTokenStore()); _sessionTracker = SessionTracker( onLeaveForeground: () => _analytics.flushNow(), ); WidgetsBinding.instance.addObserver(_sessionTracker); _analytics = AnalyticsService( apiBaseUrl: patbondUserApiBaseUrl, getAccessToken: () => sessionManager.accessToken, getSessionId: () => _sessionTracker.sessionId, ); _initAppVersion(); // 冷启动恢复持久化埋点队列并冲刷离线积压(后台任务,不阻塞渲染)。 _analytics.restore(); _pageViewTracker = PageViewTracker(_analytics.trackEvent); _routeObserver = AnalyticsRouteObserver( tracker: _pageViewTracker, resolveRootPage: _resolveRootPage, ); authRepository = widget.authRepository ?? _buildRepository(); // 认证状态切换补点(根路由 AnimatedSwitcher 无路由事件) sessionManager.addListener(_reportAuthStateChange); } /// 异步读取 appVersion 并注入 analytics(启动后台任务,不阻塞渲染)。 Future _initAppVersion() async { try { final info = await PackageInfo.fromPlatform(); _analytics.setAppVersion('${info.version}+${info.buildNumber}'); } catch (error) { // 读取失败回退 'unknown'(AnalyticsService 构造默认值已兜底)。 } } AuthRepository _buildRepository() { final dio = buildPatbondDio(session: sessionManager); final refresher = TokenRefresher(dio: dio, session: sessionManager); final api = ApiClient( dio: dio, session: sessionManager, refresher: refresher, ); return ApiAuthRepository( api: api, session: sessionManager, refresher: refresher, analytics: _analytics, ); } AnalyticsPageName? _resolveRootPage() { // 回栈到无名根路由时解析当前认证状态页/主壳 Tab return switch (sessionManager.status) { AuthStatus.unknown => null, AuthStatus.unauthenticated => AnalyticsPageName.login, AuthStatus.authenticated => _pageViewTracker.currentTab, }; } void _reportAuthStateChange() { // 认证状态机切页补点(03 §3.2 非路由曝光 1/2) final page = switch (sessionManager.status) { AuthStatus.unauthenticated => AnalyticsPageName.login, AuthStatus.authenticated => _pageViewTracker.currentTab, AuthStatus.unknown => null, }; if (page != null) _pageViewTracker.report(page); } @override void dispose() { sessionManager.removeListener(_reportAuthStateChange); WidgetsBinding.instance.removeObserver(_sessionTracker); appState.dispose(); if (widget.sessionManager == null) sessionManager.dispose(); super.dispose(); } Widget _homeForStatus() { switch (sessionManager.status) { case AuthStatus.unknown: return SplashPage( key: const ValueKey('splash'), authRepository: authRepository, sessionManager: sessionManager, ); case AuthStatus.unauthenticated: return LoginPage( key: const ValueKey('login'), authRepository: authRepository, ); case AuthStatus.authenticated: return MainShellPage( key: const ValueKey('shell'), appState: appState, pageViewTracker: _pageViewTracker, onLogout: authRepository.logout, ); } } @override Widget build(BuildContext context) { return MaterialApp( title: 'Patbond', debugShowCheckedModeBanner: false, theme: buildAppTheme(), navigatorObservers: [_routeObserver], home: ListenableBuilder( listenable: sessionManager, builder: (context, _) { // 认证状态切换统一 300ms 淡入淡出(Splash ↔ 登录 ↔ 主壳, // BrandMark 同构保证品牌区过渡对位,组装稿 §7)。 return AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: _homeForStatus(), ); }, ), ); } }