整个课程和数据UI重新设计版本

This commit is contained in:
shenlei
2026-09-18 17:55:44 +09:00
parent febfd30f49
commit 0a4f97d105
154 changed files with 12366 additions and 4784 deletions
@@ -3,14 +3,12 @@ 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 "../../core/courses/courses.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 "../profile/profile_page.dart";
import "../review/review_page.dart";
class LearningShell extends StatefulWidget {
@@ -22,7 +20,7 @@ class LearningShell extends StatefulWidget {
}
class _LearningShellState extends State<LearningShell> {
AppTab tab = AppTab.home;
AppTab tab = AppTab.learn;
var route = _ShellRoute.tab;
_ShellRoute? previousRoute;
bool dialogueInLesson = false;
@@ -41,11 +39,6 @@ class _LearningShellState extends State<LearningShell> {
route = _ShellRoute.lesson;
});
void showDialogueScene() => setState(() {
previousRoute = route;
route = _ShellRoute.scene;
});
void showDialogue({bool inLesson = false, String? scene}) => setState(() {
previousRoute = route;
dialogueInLesson = inLesson;
@@ -59,11 +52,6 @@ class _LearningShellState extends State<LearningShell> {
route = _ShellRoute.summary;
});
void showSettings() => setState(() {
previousRoute = route;
route = _ShellRoute.settings;
});
void showAssessment(AssessmentPack pack) => setState(() {
previousRoute = route;
assessmentPack = pack;
@@ -84,33 +72,27 @@ class _LearningShellState extends State<LearningShell> {
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);
showTab(tab);
}
case _ShellRoute.summary:
showTab(tab);
case _ShellRoute.settings:
showTab(AppTab.progress);
case _ShellRoute.adaptiveLesson:
showTab(AppTab.review);
case _ShellRoute.assessmentPreparation:
showTab(AppTab.progress);
showTab(AppTab.profile);
case _ShellRoute.assessment:
if (assessmentPack != null) {
showAssessment(assessmentPack!);
} else {
showTab(AppTab.progress);
showTab(AppTab.profile);
}
case _ShellRoute.tab:
if (tab != AppTab.home) {
showTab(AppTab.home);
if (tab != AppTab.learn) {
showTab(AppTab.learn);
}
}
}
@@ -130,12 +112,6 @@ class _LearningShellState extends State<LearningShell> {
onOpenDialogue: () => showDialogue(inLesson: true),
onFinish: () => showTab(tab),
);
case _ShellRoute.scene:
body = DialogueScenePage(
state: widget.state,
onStart: (id) => showDialogue(scene: id),
onBack: () => showTab(tab),
);
case _ShellRoute.dialogue:
body = DialoguePage(
key: ValueKey(dialogueInLesson ? 'lesson' : 'scene-$sceneId'),
@@ -155,16 +131,11 @@ class _LearningShellState extends State<LearningShell> {
case _ShellRoute.summary:
body = DialogueSummaryPage(
summary: dialogueSummary!,
onHome: () => showTab(AppTab.home),
onHome: () => showTab(AppTab.learn),
onLesson: showLesson,
onRetry: () => showDialogue(scene: sceneId),
onBack: () => showTab(tab),
);
case _ShellRoute.settings:
body = SettingsPage(
state: widget.state,
onBack: () => showTab(AppTab.progress),
);
case _ShellRoute.adaptiveLesson:
body = AdaptiveLessonPage(
state: widget.state,
@@ -174,7 +145,7 @@ class _LearningShellState extends State<LearningShell> {
body = AssessmentPage(
state: widget.state,
pack: assessmentPack!,
onFinished: () => showTab(AppTab.progress),
onFinished: () => showTab(AppTab.profile),
onStartReplacement: showAssessment,
);
case _ShellRoute.assessmentPreparation:
@@ -182,14 +153,14 @@ class _LearningShellState extends State<LearningShell> {
state: widget.state,
pack: assessmentPack!,
onStart: startAssessment,
onBack: () => showTab(AppTab.progress),
onBack: () => showTab(AppTab.profile),
);
case _ShellRoute.tab:
body = _tabContent();
}
return PopScope(
canPop: route == _ShellRoute.tab && tab == AppTab.home,
canPop: route == _ShellRoute.tab && tab == AppTab.learn,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) {
handleBack();
@@ -207,11 +178,6 @@ class _LearningShellState extends State<LearningShell> {
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),
@@ -242,17 +208,15 @@ class _LearningShellState extends State<LearningShell> {
Widget _tabContent() {
switch (tab) {
case AppTab.home:
case AppTab.learn:
return HomePage(
state: widget.state,
onStartPrimaryTask: () {
if (widget.state.reviewIsPrimary) {
showTab(AppTab.review);
} else {
showLesson();
}
onStartLesson: showLesson,
onOpenLesson: (id) {
widget.state.openLesson(id);
showLesson();
},
onOpenDialogue: showDialogueScene,
onStartReview: () => showTab(AppTab.review),
onResumeLessonDialogue: () => showDialogue(inLesson: true),
onStartReinforcement: () {
widget.state.scheduleA0Reinforcement();
@@ -265,19 +229,6 @@ class _LearningShellState extends State<LearningShell> {
if (pack != null) showAssessment(pack);
},
);
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(
state: widget.state,
@@ -286,14 +237,14 @@ class _LearningShellState extends State<LearningShell> {
case AppTab.review:
return ReviewPage(
state: widget.state,
onFinished: () => showTab(AppTab.home),
onFinished: () => showTab(AppTab.learn),
onOpenAdaptiveLesson: showAdaptiveLesson,
);
case AppTab.progress:
return ProgressPage(
case AppTab.profile:
return ProfilePage(
state: widget.state,
onSettings: showSettings,
onOpenAssessment: showAssessment,
onOpenReview: () => showTab(AppTab.review),
);
}
}
@@ -302,114 +253,9 @@ class _LearningShellState extends State<LearningShell> {
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<String> 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: 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,
),
],
),
),
],
),
);
}
}