import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../features/auth/application/auth_controller.dart'; import '../../features/auth/domain/auth_session.dart'; import '../../features/auth/presentation/login_screen.dart'; import '../../features/contracts/presentation/generated_contract_result_screen.dart'; import '../../features/dashboard/presentation/dashboard_screen.dart'; import '../../features/profile/presentation/profile_screen.dart'; import '../../features/review/presentation/review_entry_screen.dart'; import '../../features/review_history/presentation/review_detail_screen.dart'; import '../../features/review_history/presentation/review_history_screen.dart'; import '../../features/contracts/presentation/contract_template_list_screen.dart'; import '../../features/tools/presentation/tools_hub_screen.dart'; import '../../shared/network/api_client.dart'; import '../../shared/notifications/app_notification_action.dart'; import '../../shared/notifications/notification_service.dart'; import '../../shared/storage/session_store.dart'; import '../navigation/app_router.dart'; import '../theme/brand_assets.dart'; class AiContractAppShell extends StatefulWidget { const AiContractAppShell({super.key}); @override State createState() => _AiContractAppShellState(); } class _AiContractAppShellState extends State with WidgetsBindingObserver { AppTab _currentTab = AppTab.dashboard; late final AuthController _authController; final ApiClient _apiClient = const ApiClient(); String? _activeSessionUserId; bool _isHandlingNotificationAction = false; @override void initState() { super.initState(); _authController = AuthController(); WidgetsBinding.instance.addObserver(this); SessionStore.listenable.addListener(_handleSessionChanged); NotificationService.instance.pendingAction .addListener(_handleNotificationSignal); unawaited(_syncNotificationLifecycle()); } @override void dispose() { NotificationService.instance.pendingAction .removeListener(_handleNotificationSignal); SessionStore.listenable.removeListener(_handleSessionChanged); WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { unawaited(_syncNotificationLifecycle(runSyncNow: true)); } } void _selectTab(AppTab tab) { setState(() { _currentTab = tab; }); } void _openTools() { setState(() { _currentTab = AppTab.tools; }); } Future _openContractWorkspace() async { await Navigator.of(context).push( MaterialPageRoute( builder: (_) => const ContractTemplateListScreen(), ), ); } void _handleSessionChanged() { unawaited(_syncNotificationLifecycle()); } void _handleNotificationSignal() { unawaited(_tryHandlePendingNotificationAction()); } Future _syncNotificationLifecycle({bool runSyncNow = false}) async { final session = SessionStore.current; if (session == null || !session.isAuthenticated) { if (_activeSessionUserId != null) { _activeSessionUserId = null; await _safeNotificationCall(NotificationService.instance.onSignedOut); } return; } if (_activeSessionUserId != session.userId) { _activeSessionUserId = session.userId; await _safeNotificationCall(NotificationService.instance.onSignedIn); runSyncNow = true; } if (runSyncNow) { await _safeNotificationCall( NotificationService.instance.syncReviewNotifications); } await _tryHandlePendingNotificationAction(); } Future _tryHandlePendingNotificationAction() async { if (!mounted || _isHandlingNotificationAction) { return; } final session = SessionStore.current; final action = NotificationService.instance.pendingAction.value; if (session == null || !session.isAuthenticated || action == null) { return; } _isHandlingNotificationAction = true; try { switch (action.type) { case AppNotificationActionType.reviewStatus: final reviewId = action.reviewId; if (reviewId == null || reviewId.isEmpty) { break; } _selectTab(AppTab.history); await Navigator.of(context).push( MaterialPageRoute( builder: (_) => ReviewDetailScreen( reviewId: reviewId, initialTitle: action.reviewTitle?.isNotEmpty == true ? action.reviewTitle! : '审查详情', ), ), ); break; case AppNotificationActionType.generatedContract: final contractId = action.contractId; if (contractId == null || contractId.isEmpty) { break; } final contract = await _apiClient.getGeneratedContract( session.accessToken, contractId, ); if (!mounted) { return; } await Navigator.of(context).push( MaterialPageRoute( builder: (_) => GeneratedContractResultScreen(contract: contract), ), ); break; } NotificationService.instance.clearPendingAction(); } catch (_) { // Keep the action and allow a later retry. } finally { _isHandlingNotificationAction = false; } } Future _safeNotificationCall(Future Function() action) async { try { return await action(); } on MissingPluginException { return null; } catch (_) { return null; } } @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: SessionStore.listenable, builder: (context, session, _) { final isAuthenticated = session?.isAuthenticated ?? false; if (!isAuthenticated) { return Scaffold( resizeToAvoidBottomInset: true, body: SafeArea( child: LoginScreen(controller: _authController), ), ); } return Scaffold( appBar: AppBar( toolbarHeight: 66, systemOverlayStyle: SystemUiOverlayStyle.light, titleSpacing: 16, title: Row( children: [ const BrandMark(size: 34), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( _currentTab.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontSize: 17, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 2), Text( '${session!.userDisplayName} · ${session.role}', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Color(0xBFE8FBDC), fontSize: 12, fontWeight: FontWeight.w600, ), ), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.07), borderRadius: BorderRadius.circular(999), border: Border.all(color: Colors.white.withValues(alpha: 0.08)), ), child: const Text( 'Live', style: TextStyle( color: BrandAssets.accentLime, fontSize: 11, fontWeight: FontWeight.w800, ), ), ), ], ), ), body: SafeArea( child: AnimatedSwitcher( duration: const Duration(milliseconds: 240), child: _buildCurrentScreen(), ), ), bottomNavigationBar: Container( decoration: const BoxDecoration( color: BrandAssets.surface, border: Border(top: BorderSide(color: BrandAssets.line)), boxShadow: [ BoxShadow( color: Color(0x140F172A), blurRadius: 22, offset: Offset(0, -8), ), ], ), child: SafeArea( top: false, child: NavigationBar( height: 68, selectedIndex: AppTab.values.indexOf(_currentTab), onDestinationSelected: (index) { _selectTab(AppTab.values[index]); }, destinations: AppTab.values .map( (item) => NavigationDestination( icon: Icon(item.icon), selectedIcon: Icon(item.icon, color: BrandAssets.surfaceInk), label: item.label, ), ) .toList(growable: false), ), ), ), ); }, ); } Widget _buildCurrentScreen() { switch (_currentTab) { case AppTab.dashboard: return DashboardScreen( key: const ValueKey(AppTab.dashboard), onStartReview: () => _selectTab(AppTab.review), onOpenHistory: () => _selectTab(AppTab.history), onOpenProfile: () => _selectTab(AppTab.profile), onOpenLegalChat: _openTools, onOpenContracts: _openContractWorkspace, ); case AppTab.review: return const ReviewEntryScreen(key: ValueKey(AppTab.review)); case AppTab.tools: return const ToolsHubScreen(key: ValueKey(AppTab.tools)); case AppTab.history: return ReviewHistoryScreen( key: const ValueKey(AppTab.history), onStartReview: () => _selectTab(AppTab.review), ); case AppTab.profile: return ProfileScreen( key: const ValueKey(AppTab.profile), onOpenHistory: () => _selectTab(AppTab.history), ); } } }