整个课程和数据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
+20 -99
View File
@@ -1,3 +1,5 @@
import 'courses/courses.dart';
class WritingCheckResult {
const WritingCheckResult({required this.complete, required this.message});
@@ -5,84 +7,24 @@ class WritingCheckResult {
final String message;
}
/// A deliberately small offline checker for teaching tasks. It checks only
/// the essential A0 information, accepts variable names/places, and never
/// claims to grade pronunciation or nuanced grammar.
/// A deliberately small offline checker for teaching tasks. The rule and the
/// hint come from the segment's writing task in the course JSON; it
/// checks only the essential information and never claims to grade
/// pronunciation or nuanced grammar.
class WritingFeedback {
const WritingFeedback._();
static String _normalize(String input) => input
.toLowerCase()
.replaceAll('’', "'")
.replaceAll('‘', "'")
.replaceAll(RegExp(r'\s+'), ' ')
.trim();
static WritingCheckResult check(
String lessonId,
String input, {
String? segmentId,
}) {
final text = _normalize(input);
final words = RegExp(
r"[a-z]+(?:'[a-z]+)?",
).allMatches(text).map((match) => match.group(0)!).toSet();
bool has(String word) => words.contains(_normalize(word));
bool hasPhrase(String phrase) => text.contains(_normalize(phrase));
bool hasAny(Iterable<String> choices) => choices.any(has);
final hasIntroduction =
hasPhrase("i'm") || hasPhrase('i am') || hasPhrase('my name is');
final hasItIs = hasPhrase("it's") || hasPhrase('it is');
final complete = switch (segmentId) {
'a0-04-a' => hasAll(words, ['zero', 'one', 'two', 'three']),
'a0-04-b' => hasAll(words, ['six', 'seven', 'eight']),
'a0-04-c' =>
RegExp(
r'\b(one|two|three|four|five|six|seven|eight|nine|zero)\b',
).allMatches(text).length >=
3,
'a0-08-a' => hasItIs && hasAny(['monday', 'tuesday', 'wednesday']),
'a0-08-b' =>
hasItIs && hasAny(['thursday', 'friday', 'saturday', 'sunday']),
'a0-08-c' => hasItIs && (has('oclock') || hasPhrase("o'clock")),
_ => switch (lessonId) {
'a0-01' => hasAny(['hello', 'hi']) && hasIntroduction,
'a0-02' =>
(hasPhrase('my name') || hasIntroduction) &&
RegExp(r'[a-z](?:[ -]?[a-z]){2,}').hasMatch(text),
'a0-03' => hasIntroduction && hasAny(['good', 'okay', 'tired']),
'a0-04' =>
RegExp(
r'\b(zero|one|two|three|four|five|six|seven|eight|nine|ten)\b',
).allMatches(text).length >=
3,
'a0-05' => hasItIs && hasAny(['book', 'pen', 'bag', 'key']),
'a0-06' => hasIntroduction && has('from') && words.length >= 3,
'a0-07' =>
hasPhrase('this is my') &&
hasAny(['mother', 'father', 'sister', 'brother', 'friend']),
'a0-08' =>
hasItIs &&
(hasAny([
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday',
'sunday',
]) ||
has('clock')),
'a0-09' => hasIntroduction && has('like') && words.length >= 3,
'a0-10' =>
hasIntroduction && has('from') && has('like') && has('water'),
_ => words.length >= 2,
},
};
static WritingCheckResult check(String segmentId, String input) {
final activity = activityBySegmentId(segmentId);
final rule = activity.writingRequiredTerms;
final complete = rule.isEmpty
? RegExp(r"[a-z]+(?:'[a-z]+)?")
.allMatches(normalizeAnswer(input))
.map((match) => match.group(0))
.toSet()
.length >=
2
: matchesRule(input, rule);
if (complete) {
return const WritingCheckResult(
complete: true,
@@ -91,30 +33,9 @@ class WritingFeedback {
}
return WritingCheckResult(
complete: false,
message: _hintFor(segmentId ?? lessonId),
message: activity.writingHint.isNotEmpty
? activity.writingHint
: '再补充一个完整英文句子。',
);
}
static bool hasAll(Set<String> words, Iterable<String> required) =>
required.every(words.contains);
static String _hintFor(String lessonId) => switch (lessonId) {
'a0-04-a' => '请写出 zero、one、two、three 四个数字。',
'a0-04-b' => '请写出 six、seven、eight 三个数字。',
'a0-04-c' => '用英文一个一个写出三个数字,例如 one-three-nine。',
'a0-08-a' => '用 It is / It’s 加 Monday、Tuesday 或 Wednesday。',
'a0-08-b' => '用 It is / It’s 加 Thursday、Friday、Saturday 或 Sunday。',
'a0-08-c' => '用 It is / It’s 加数字和 o’clock,例如 It’s three o’clock。',
'a0-01' => '试着同时写一句问候和姓名,例如:Hello. I’m …',
'a0-02' => '写出姓名并拼写一遍,例如 My name is Shen. S-H-E-N.',
'a0-03' => '用 I’m / I am 加上你的状态,例如 good 或 tired。',
'a0-04' => '用英文写出三个数字,例如 one-three-nine。',
'a0-05' => '用 It’s / It is 加一个物品,例如 a pen。',
'a0-06' => '用 I’m from … 写成一个完整句子。',
'a0-07' => '用 This is my … 介绍一位家人或朋友。',
'a0-08' => '用 It’s … 写一个星期或整点时间。',
'a0-09' => '用 I like … 写出一种喜好。',
'a0-10' => '分别写姓名、来自哪里和喜好三部分,喜好用 water:I like water.',
_ => '再补充一个完整英文句子。',
};
}