import "package:flutter/material.dart"; import "../../core/app_state.dart"; import "../../core/app_theme.dart"; import "../../core/models.dart"; import "../../core/seed_courses.dart"; import "../../core/assessment_bank.dart"; import "../../widgets/app_widgets.dart"; import "../dialogue/dialogue_flow.dart"; import "../assessment/assessment_page.dart"; import "../home/home_page.dart"; import "../lesson/lesson_flow.dart"; import "../progress/progress_pages.dart"; import "../review/review_page.dart"; class LearningShell extends StatefulWidget { const LearningShell({super.key, required this.state}); final AppState state; @override State createState() => _LearningShellState(); } class _LearningShellState extends State { AppTab tab = AppTab.home; var route = _ShellRoute.tab; _ShellRoute? previousRoute; bool dialogueInLesson = false; AssessmentPack? assessmentPack; DialogueSummaryData? dialogueSummary; void showTab(AppTab value) => setState(() { tab = value; route = _ShellRoute.tab; previousRoute = null; }); void showLesson() => setState(() { previousRoute = route; route = _ShellRoute.lesson; }); void showDialogueScene() => setState(() { previousRoute = route; route = _ShellRoute.scene; }); void showDialogue({bool inLesson = false}) => setState(() { previousRoute = route; dialogueInLesson = inLesson; route = _ShellRoute.dialogue; }); void showSummary(DialogueSummaryData summary) => setState(() { previousRoute = route; dialogueSummary = summary; route = _ShellRoute.summary; }); void showSettings() => setState(() { previousRoute = route; route = _ShellRoute.settings; }); void showAssessment(AssessmentPack pack) => setState(() { previousRoute = route; assessmentPack = pack; route = _ShellRoute.assessmentPreparation; }); void startAssessment() => setState(() { previousRoute = route; route = _ShellRoute.assessment; }); void showAdaptiveLesson() => setState(() { previousRoute = route; route = _ShellRoute.adaptiveLesson; }); void handleBack() { switch (route) { case _ShellRoute.lesson: showTab(tab); case _ShellRoute.scene: showTab(tab); case _ShellRoute.dialogue: if (dialogueInLesson) { showLesson(); } else if (previousRoute == _ShellRoute.scene) { showDialogueScene(); } else { showTab(tab == AppTab.dialogue ? AppTab.dialogue : tab); } case _ShellRoute.summary: showTab(tab); case _ShellRoute.settings: showTab(AppTab.progress); case _ShellRoute.adaptiveLesson: showTab(AppTab.review); case _ShellRoute.assessmentPreparation: showTab(AppTab.progress); case _ShellRoute.assessment: if (assessmentPack != null) { showAssessment(assessmentPack!); } else { showTab(AppTab.progress); } case _ShellRoute.tab: if (tab != AppTab.home) { showTab(AppTab.home); } } } @override Widget build(BuildContext context) { Widget body; switch (route) { case _ShellRoute.lesson: body = LessonFlow( state: widget.state, onOpenDialogue: () => showDialogue(inLesson: true), onFinish: () => showTab(tab), ); case _ShellRoute.scene: body = DialogueScenePage( onStart: showDialogue, onBack: () => showTab(tab), ); case _ShellRoute.dialogue: body = DialoguePage( state: widget.state, isLessonDialogue: dialogueInLesson, onFinished: (summary) { if (dialogueInLesson) { showLesson(); } else if (summary != null) { showSummary(summary); } else { handleBack(); } }, ); case _ShellRoute.summary: body = DialogueSummaryPage( summary: dialogueSummary!, onHome: () => showTab(AppTab.home), onLesson: showLesson, onRetry: showDialogue, onBack: () => showTab(tab), ); case _ShellRoute.settings: body = SettingsPage( state: widget.state, onBack: () => showTab(AppTab.progress), ); case _ShellRoute.adaptiveLesson: body = AdaptiveLessonPage( state: widget.state, onFinished: () => showTab(AppTab.review), ); case _ShellRoute.assessment: body = AssessmentPage( state: widget.state, pack: assessmentPack!, onFinished: () => showTab(AppTab.progress), onStartReplacement: showAssessment, ); case _ShellRoute.assessmentPreparation: body = AssessmentPreparationPage( state: widget.state, pack: assessmentPack!, onStart: startAssessment, onBack: () => showTab(AppTab.progress), ); case _ShellRoute.tab: body = _tabContent(); } return PopScope( canPop: route == _ShellRoute.tab && tab == AppTab.home, onPopInvokedWithResult: (didPop, _) { if (!didPop) { handleBack(); } }, child: AnimatedBuilder( animation: widget.state, builder: (context, _) => Scaffold( body: body, bottomNavigationBar: route == _ShellRoute.tab ? NavigationBar( selectedIndex: tab.index, height: 70, indicatorColor: AppColors.softGreen, onDestinationSelected: (index) => showTab(AppTab.values[index]), destinations: const [ NavigationDestination( icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: "首页", ), NavigationDestination( icon: Icon(Icons.menu_book_outlined), selectedIcon: Icon(Icons.menu_book), label: "学习", ), NavigationDestination( icon: Icon(Icons.chat_bubble_outline), selectedIcon: Icon(Icons.chat_bubble), label: "对话", ), NavigationDestination( icon: Icon(Icons.refresh_outlined), selectedIcon: Icon(Icons.refresh), label: "复习", ), NavigationDestination( icon: Icon(Icons.person_outline), selectedIcon: Icon(Icons.person), label: "我的", ), ], ) : null, ), ), ); } Widget _tabContent() { switch (tab) { case AppTab.home: return HomePage( state: widget.state, onStartPrimaryTask: () { if (widget.state.reviewIsPrimary) { showTab(AppTab.review); } else { showLesson(); } }, onOpenDialogue: showDialogueScene, onResumeLessonDialogue: () => showDialogue(inLesson: true), ); case AppTab.learn: return _LearningMap( onOpenLesson: (id) { widget.state.openLesson(id); showLesson(); }, onStartReinforcement: () { widget.state.scheduleA0Reinforcement(); showTab(AppTab.review); }, onOpenReview: () => showTab(AppTab.review), state: widget.state, ); case AppTab.dialogue: return DialogueScenePage(onStart: showDialogue); case AppTab.review: return ReviewPage( state: widget.state, onFinished: () => showTab(AppTab.home), onOpenAdaptiveLesson: showAdaptiveLesson, ); case AppTab.progress: return ProgressPage( state: widget.state, onSettings: showSettings, onOpenAssessment: showAssessment, ); } } } enum _ShellRoute { tab, lesson, scene, dialogue, summary, settings, adaptiveLesson, assessmentPreparation, assessment, } class _LearningMap extends StatelessWidget { const _LearningMap({ required this.onOpenLesson, required this.onStartReinforcement, required this.onOpenReview, required this.state, }); final ValueChanged onOpenLesson; final VoidCallback onStartReinforcement; final VoidCallback onOpenReview; final AppState state; @override Widget build(BuildContext context) { return AppPage( child: SpacedColumn( children: [ const Eyebrow("学习地图 · 按掌握状态推进"), Text("从认识到开口", style: Theme.of(context).textTheme.headlineMedium), const Text("每节课都围绕一个能完成的小任务。"), if (state.reviewBacklog) SectionCard( tint: AppColors.warm, child: SpacedColumn( children: [ Text( "复习已有积压,今天先完成 ${state.dueReviewCount} 项到期复习,再开启新课。", style: const TextStyle(color: AppColors.warmInk), ), SecondaryButton(label: "先去复习", onPressed: onOpenReview), ], ), ), for (final lesson in a0SeedLessons) SectionCard( tint: lesson.id == state.activeLessonId ? AppColors.softGreen : null, onTap: state.isLessonUnlocked(lesson.id) && (!state.reviewBacklog || state.completedLessonIds.contains(lesson.id)) ? () => onOpenLesson(lesson.id) : null, child: Row( children: [ Icon( state.completedLessonIds.contains(lesson.id) ? Icons.check_circle : state.reviewBacklog ? Icons.lock_outline : state.isLessonUnlocked(lesson.id) ? Icons.play_circle_outline : Icons.lock_outline, color: state.completedLessonIds.contains(lesson.id) ? AppColors.green : AppColors.muted, ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "第 ${lesson.number} 课 · ${lesson.title}", style: const TextStyle(fontWeight: FontWeight.w600), ), const SizedBox(height: 3), Text( lesson.segments.length > 1 ? "${lesson.outcome} · 小段 ${lesson.segments.where((segment) => state.isSegmentComplete(segment.id)).length}/${lesson.segments.length}" : lesson.outcome, style: Theme.of(context).textTheme.bodyMedium, ), ], ), ), ], ), ), if (state.completedLessonIds.length == a0SeedLessons.length) SectionCard( tint: AppColors.softGreen, child: SpacedColumn( children: [ const Text( "A0 巩固变式", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ), const Text("换一个人物、地点或情境,继续巩固尚未稳定的核心表达。"), PrimaryButton( label: "安排一题巩固练习", onPressed: onStartReinforcement, ), ], ), ), ], ), ); } }