整个课程和数据UI重新设计版本
This commit is contained in:
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user