课程内容 - 重写全部 21 个 A1 单元的听读材料,把本单元理解词织进听/读文本, 单元内理解词复现率从约 20% 提升到约 77%(各单元 56–96%)。 - 修正 U01 房间号与机场大巴同为 thirty 的撞车(改为 17/30/40)。 - 校验器容差按级别读取(A1 为 8%),materials 覆盖率、字数、 选项子串等校验全部通过;course_content_test 通过。 黑夜模式 - app_theme 拆分明/暗两套调色板,AppColors 随亮度切换; main 用 theme/darkTheme/themeMode + builder 镜像已解析亮度; 主题偏好持久化到快照;进度页新增“外观主题”切换。 文档 - COURSE-PACK-JSON.md 更新 A1 词池覆盖(840/933)与材料复现约定。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1866 lines
60 KiB
Dart
1866 lines
60 KiB
Dart
import 'a0_core.dart';
|
||
import 'models.dart';
|
||
|
||
/// Stable A0 core identities taught by each seed lesson. Wording can vary in
|
||
/// future exercises without creating a different mastery item.
|
||
const a0TargetItemIdsByLesson = <String, List<String>>{
|
||
'a0-01': ['A0-W36', 'A0-W37', 'A0-W40', 'A0-P01', 'A0-P02', 'A0-P03'],
|
||
'a0-02': ['A0-P04'],
|
||
'a0-03': ['A0-W33', 'A0-W34', 'A0-W35', 'A0-P05', 'A0-P06'],
|
||
'a0-04': [
|
||
'A0-W01',
|
||
'A0-W02',
|
||
'A0-W03',
|
||
'A0-W04',
|
||
'A0-W05',
|
||
'A0-W06',
|
||
'A0-W07',
|
||
'A0-W08',
|
||
'A0-W09',
|
||
'A0-W10',
|
||
'A0-W11',
|
||
'A0-W20',
|
||
'A0-P07',
|
||
'A0-P08',
|
||
],
|
||
'a0-05': ['A0-W19', 'A0-W21', 'A0-W22', 'A0-W23', 'A0-P09', 'A0-P10'],
|
||
'a0-06': ['A0-P11', 'A0-P12'],
|
||
'a0-07': ['A0-W25', 'A0-W26', 'A0-W27', 'A0-W28', 'A0-P13'],
|
||
'a0-08': [
|
||
'A0-W12',
|
||
'A0-W13',
|
||
'A0-W14',
|
||
'A0-W15',
|
||
'A0-W16',
|
||
'A0-W17',
|
||
'A0-W18',
|
||
'A0-P14',
|
||
'A0-P15',
|
||
'A0-P16',
|
||
'A0-P17',
|
||
],
|
||
'a0-09': [
|
||
'A0-W29',
|
||
'A0-W30',
|
||
'A0-W31',
|
||
'A0-W32',
|
||
'A0-W38',
|
||
'A0-W39',
|
||
'A0-P18',
|
||
'A0-P19',
|
||
],
|
||
'a0-10': ['A0-W24', 'A0-P20'],
|
||
};
|
||
|
||
/// A topic may contain multiple independently resumable teaching segments.
|
||
/// Segment IDs are stable even if later wording or exercises change.
|
||
class LessonSegment {
|
||
const LessonSegment({
|
||
required this.id,
|
||
required this.targetItemIds,
|
||
required this.previewItemIds,
|
||
this.dialogueRequiredTerms = const [],
|
||
this.independentRequiredTerms = const [],
|
||
this.estimatedMinutes = 12,
|
||
});
|
||
|
||
final String id;
|
||
final List<String> targetItemIds;
|
||
final List<String> previewItemIds;
|
||
|
||
/// One accepted term group per controlled dialogue turn. A learner only
|
||
/// needs to use one term from the current group; the surrounding wording
|
||
/// may vary naturally.
|
||
final List<List<String>> dialogueRequiredTerms;
|
||
|
||
/// Terms that show the independent attempt contains the taught content.
|
||
final List<String> independentRequiredTerms;
|
||
final int estimatedMinutes;
|
||
}
|
||
|
||
const a0LessonSegments = <String, List<LessonSegment>>{
|
||
'a0-04': [
|
||
LessonSegment(
|
||
id: 'a0-04-a',
|
||
targetItemIds: [
|
||
'A0-W01',
|
||
'A0-W02',
|
||
'A0-W03',
|
||
'A0-W04',
|
||
'A0-W05',
|
||
'A0-W06',
|
||
],
|
||
previewItemIds: [
|
||
'A0-W01',
|
||
'A0-W02',
|
||
'A0-W03',
|
||
'A0-W04',
|
||
'A0-W05',
|
||
'A0-W06',
|
||
],
|
||
dialogueRequiredTerms: [
|
||
['zero', 'one', 'two'],
|
||
['three', 'four', 'five'],
|
||
],
|
||
independentRequiredTerms: ['zero', 'one', 'two', 'three', 'four', 'five'],
|
||
),
|
||
LessonSegment(
|
||
id: 'a0-04-b',
|
||
targetItemIds: [
|
||
'A0-W07',
|
||
'A0-W08',
|
||
'A0-W09',
|
||
'A0-W10',
|
||
'A0-W11',
|
||
'A0-W20',
|
||
],
|
||
previewItemIds: [
|
||
'A0-W07',
|
||
'A0-W08',
|
||
'A0-W09',
|
||
'A0-W10',
|
||
'A0-W11',
|
||
'A0-W20',
|
||
],
|
||
dialogueRequiredTerms: [
|
||
['six', 'seven', 'eight'],
|
||
['it is a phone', "it's a phone"],
|
||
],
|
||
independentRequiredTerms: ['six', 'seven', 'eight', 'nine', 'ten'],
|
||
),
|
||
LessonSegment(
|
||
id: 'a0-04-c',
|
||
targetItemIds: ['A0-P07', 'A0-P08'],
|
||
previewItemIds: ['A0-P07', 'A0-P08'],
|
||
dialogueRequiredTerms: [
|
||
[
|
||
'my number is',
|
||
'one',
|
||
'two',
|
||
'three',
|
||
'four',
|
||
'five',
|
||
'six',
|
||
'seven',
|
||
'eight',
|
||
'nine',
|
||
'zero',
|
||
],
|
||
[
|
||
'one',
|
||
'two',
|
||
'three',
|
||
'four',
|
||
'five',
|
||
'six',
|
||
'seven',
|
||
'eight',
|
||
'nine',
|
||
'zero',
|
||
],
|
||
],
|
||
independentRequiredTerms: [
|
||
'one',
|
||
'two',
|
||
'three',
|
||
'four',
|
||
'five',
|
||
'six',
|
||
'seven',
|
||
'eight',
|
||
'nine',
|
||
'zero',
|
||
],
|
||
),
|
||
],
|
||
'a0-08': [
|
||
LessonSegment(
|
||
id: 'a0-08-a',
|
||
targetItemIds: ['A0-W12', 'A0-W13', 'A0-W14', 'A0-P14', 'A0-P15'],
|
||
previewItemIds: ['A0-W12', 'A0-W13', 'A0-W14', 'A0-P14', 'A0-P15'],
|
||
dialogueRequiredTerms: [
|
||
['what day'],
|
||
['monday', 'tuesday', 'wednesday'],
|
||
],
|
||
independentRequiredTerms: ['monday', 'tuesday', 'wednesday'],
|
||
),
|
||
LessonSegment(
|
||
id: 'a0-08-b',
|
||
targetItemIds: ['A0-W15', 'A0-W16', 'A0-W17', 'A0-W18'],
|
||
previewItemIds: ['A0-W15', 'A0-W16', 'A0-W17', 'A0-W18'],
|
||
dialogueRequiredTerms: [
|
||
['what day'],
|
||
['thursday', 'friday', 'saturday', 'sunday'],
|
||
],
|
||
independentRequiredTerms: ['thursday', 'friday', 'saturday', 'sunday'],
|
||
),
|
||
LessonSegment(
|
||
id: 'a0-08-c',
|
||
targetItemIds: ['A0-P16', 'A0-P17'],
|
||
previewItemIds: ['A0-P16', 'A0-P17'],
|
||
dialogueRequiredTerms: [
|
||
['what time'],
|
||
["o'clock", 'oclock'],
|
||
],
|
||
independentRequiredTerms: ["o'clock", 'oclock'],
|
||
),
|
||
],
|
||
};
|
||
|
||
class SeedLesson {
|
||
const SeedLesson({
|
||
required this.id,
|
||
required this.number,
|
||
required this.title,
|
||
required this.outcome,
|
||
required this.vocabulary,
|
||
this.schemaVersion = '1.0',
|
||
this.revision = 1,
|
||
this.stageVersion = 'A0-1.0',
|
||
this.source = ContentSource.builtInOriginal,
|
||
this.status = ContentStatus.approved,
|
||
this.estimatedMinutes = 12,
|
||
});
|
||
final String id;
|
||
final int number;
|
||
final String title;
|
||
final String outcome;
|
||
final List<VocabularyItem> vocabulary;
|
||
final String schemaVersion;
|
||
final int revision;
|
||
final String stageVersion;
|
||
final ContentSource source;
|
||
final ContentStatus status;
|
||
final int estimatedMinutes;
|
||
|
||
List<String> get targetItemIds =>
|
||
a0TargetItemIdsByLesson[id] ?? const <String>[];
|
||
List<String> get abilityIds => ['A0-C${number.toString().padLeft(2, '0')}'];
|
||
List<String> get prerequisiteIds => number == 1
|
||
? const []
|
||
: ['a0-${(number - 1).toString().padLeft(2, '0')}'];
|
||
List<LessonSegment> get segments =>
|
||
a0LessonSegments[id] ??
|
||
[
|
||
LessonSegment(
|
||
id: '$id-a',
|
||
targetItemIds: targetItemIds,
|
||
previewItemIds: targetItemIds,
|
||
estimatedMinutes: estimatedMinutes,
|
||
),
|
||
];
|
||
}
|
||
|
||
const a0SeedLessons = [
|
||
SeedLesson(
|
||
id: 'a0-01',
|
||
number: 1,
|
||
title: '你好,我是……',
|
||
outcome: '介绍姓名并回应问候',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-01-hello',
|
||
word: 'Hello',
|
||
meaning: '你好',
|
||
example: 'Hello. I’m Mia.',
|
||
exampleMeaning: '你好。我是 Mia。',
|
||
ipa: '/həˈləʊ/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-01-name',
|
||
word: 'name',
|
||
meaning: '名字',
|
||
example: 'My name is Shen.',
|
||
exampleMeaning: '我的名字是 Shen。',
|
||
ipa: '/neɪm/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-01-nice',
|
||
word: 'Nice to meet you',
|
||
meaning: '很高兴认识你',
|
||
example: 'Nice to meet you, too.',
|
||
exampleMeaning: '我也很高兴认识你。',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-01-thanks',
|
||
word: 'thanks',
|
||
meaning: '谢谢',
|
||
example: 'Thanks. Nice to meet you, too.',
|
||
exampleMeaning: '谢谢。我也很高兴认识你。',
|
||
ipa: '/θæŋks/',
|
||
),
|
||
],
|
||
),
|
||
SeedLesson(
|
||
id: 'a0-02',
|
||
number: 2,
|
||
title: '拼读我的名字',
|
||
outcome: '听懂拼写,并说出名字字母',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-02-spell',
|
||
word: 'spell',
|
||
meaning: '拼写',
|
||
example: 'How do you spell that?',
|
||
exampleMeaning: '那个怎么拼?',
|
||
ipa: '/spel/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-02-name',
|
||
word: 'name',
|
||
meaning: '名字',
|
||
example: 'My name is Shen.',
|
||
exampleMeaning: '我的名字是 Shen。',
|
||
ipa: '/neɪm/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-02-question',
|
||
word: 'How do you spell that?',
|
||
meaning: '那个怎么拼?',
|
||
example: 'How do you spell your name?',
|
||
exampleMeaning: '你的名字怎么拼?',
|
||
),
|
||
],
|
||
),
|
||
SeedLesson(
|
||
id: 'a0-03',
|
||
number: 3,
|
||
title: '你好吗?',
|
||
outcome: '问候并表达简单状态',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-03-good',
|
||
word: 'good',
|
||
meaning: '好的',
|
||
example: 'I’m good, thanks.',
|
||
exampleMeaning: '我很好,谢谢。',
|
||
ipa: '/ɡʊd/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-03-tired',
|
||
word: 'tired',
|
||
meaning: '累的',
|
||
example: 'I’m tired today.',
|
||
exampleMeaning: '我今天有点累。',
|
||
ipa: '/ˈtaɪəd/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-03-how',
|
||
word: 'How are you?',
|
||
meaning: '你好吗?',
|
||
example: 'How are you today?',
|
||
exampleMeaning: '你今天怎么样?',
|
||
),
|
||
],
|
||
),
|
||
SeedLesson(
|
||
id: 'a0-04',
|
||
number: 4,
|
||
title: '数字与电话号码',
|
||
outcome: '说 0–10 与虚拟号码',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-04-three',
|
||
word: 'three',
|
||
meaning: '三',
|
||
example: 'One, three, eight.',
|
||
exampleMeaning: '一、三、八。',
|
||
ipa: '/θriː/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-04-number',
|
||
word: 'number',
|
||
meaning: '号码',
|
||
example: 'My number is 1-3-8.',
|
||
exampleMeaning: '我的号码是 138。',
|
||
ipa: '/ˈnʌmbə(r)/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-04-phone',
|
||
word: 'phone',
|
||
meaning: '电话',
|
||
example: 'What’s your phone number?',
|
||
exampleMeaning: '你的电话号码是多少?',
|
||
),
|
||
],
|
||
),
|
||
SeedLesson(
|
||
id: 'a0-05',
|
||
number: 5,
|
||
title: '这是什么?',
|
||
outcome: '询问并说出常见物品',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-05-book',
|
||
word: 'book',
|
||
meaning: '书',
|
||
example: 'It’s a book.',
|
||
exampleMeaning: '这是一本书。',
|
||
ipa: '/bʊk/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-05-pen',
|
||
word: 'pen',
|
||
meaning: '笔',
|
||
example: 'It’s a pen.',
|
||
exampleMeaning: '这是一支笔。',
|
||
ipa: '/pen/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-05-this',
|
||
word: 'What’s this?',
|
||
meaning: '这是什么?',
|
||
example: 'What’s this? It’s a key.',
|
||
exampleMeaning: '这是什么?这是钥匙。',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-05-bag',
|
||
word: 'bag',
|
||
meaning: '包',
|
||
example: 'It’s a bag.',
|
||
exampleMeaning: '这是一个包。',
|
||
ipa: '/bæɡ/',
|
||
),
|
||
],
|
||
),
|
||
SeedLesson(
|
||
id: 'a0-06',
|
||
number: 6,
|
||
title: '我来自哪里?',
|
||
outcome: '说明来自哪里并反问',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-06-from',
|
||
word: 'from',
|
||
meaning: '来自',
|
||
example: 'I’m from Hong Kong.',
|
||
exampleMeaning: '我来自香港。',
|
||
ipa: '/frəm/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-06-place',
|
||
word: 'place',
|
||
meaning: '地点',
|
||
example: 'Where are you from?',
|
||
exampleMeaning: '你来自哪里?',
|
||
),
|
||
],
|
||
),
|
||
SeedLesson(
|
||
id: 'a0-07',
|
||
number: 7,
|
||
title: '我的家人',
|
||
outcome: '介绍一位家人',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-07-family',
|
||
word: 'family',
|
||
meaning: '家人',
|
||
example: 'This is my family.',
|
||
exampleMeaning: '这是我的家人。',
|
||
ipa: '/ˈfæməli/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-07-mother',
|
||
word: 'mother',
|
||
meaning: '母亲',
|
||
example: 'This is my mother.',
|
||
exampleMeaning: '这是我的妈妈。',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-07-sister',
|
||
word: 'sister',
|
||
meaning: '姐妹',
|
||
example: 'This is my sister.',
|
||
exampleMeaning: '这是我的姐姐(或妹妹)。',
|
||
ipa: '/ˈsɪstə(r)/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-07-brother',
|
||
word: 'brother',
|
||
meaning: '兄弟',
|
||
example: 'This is my brother.',
|
||
exampleMeaning: '这是我的哥哥(或弟弟)。',
|
||
ipa: '/ˈbrʌðə(r)/',
|
||
),
|
||
],
|
||
),
|
||
SeedLesson(
|
||
id: 'a0-08',
|
||
number: 8,
|
||
title: '星期与时间',
|
||
outcome: '说今天和整点',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-08-monday',
|
||
word: 'Monday',
|
||
meaning: '星期一',
|
||
example: 'It is Monday.',
|
||
exampleMeaning: '今天是星期一。',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-08-oclock',
|
||
word: 'o’clock',
|
||
meaning: '整点',
|
||
example: 'It’s three o’clock.',
|
||
exampleMeaning: '现在三点整。',
|
||
),
|
||
],
|
||
),
|
||
SeedLesson(
|
||
id: 'a0-09',
|
||
number: 9,
|
||
title: '我喜欢……',
|
||
outcome: '表达喜好并回答',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-09-like',
|
||
word: 'like',
|
||
meaning: '喜欢',
|
||
example: 'I like tea.',
|
||
exampleMeaning: '我喜欢茶。',
|
||
ipa: '/laɪk/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-09-tea',
|
||
word: 'tea',
|
||
meaning: '茶',
|
||
example: 'Do you like tea?',
|
||
exampleMeaning: '你喜欢茶吗?',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-09-music',
|
||
word: 'music',
|
||
meaning: '音乐',
|
||
example: 'I like music.',
|
||
exampleMeaning: '我喜欢音乐。',
|
||
ipa: '/ˈmjuːzɪk/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-09-movies',
|
||
word: 'movies',
|
||
meaning: '电影',
|
||
example: 'Do you like movies?',
|
||
exampleMeaning: '你喜欢看电影吗?',
|
||
ipa: '/ˈmuːviz/',
|
||
),
|
||
],
|
||
),
|
||
SeedLesson(
|
||
id: 'a0-10',
|
||
number: 10,
|
||
title: 'A0 综合任务',
|
||
outcome: '没听清时请对方重复,并完成自我介绍、喜好和反问',
|
||
vocabulary: [
|
||
VocabularyItem(
|
||
id: 'a0-10-water',
|
||
word: 'water',
|
||
meaning: '水',
|
||
example: 'I like water.',
|
||
exampleMeaning: '我喜欢喝水。',
|
||
ipa: '/ˈwɔːtər/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-10-again',
|
||
word: 'Please say that again.',
|
||
meaning: '请再说一遍。',
|
||
example: 'Please say that again.',
|
||
exampleMeaning: '请再说一遍。',
|
||
),
|
||
VocabularyItem(
|
||
id: 'a0-10-slowly',
|
||
word: 'Please speak slowly.',
|
||
meaning: '请说慢一点。',
|
||
example: 'Please speak slowly.',
|
||
exampleMeaning: '请说慢一点。',
|
||
),
|
||
],
|
||
),
|
||
];
|
||
|
||
SeedLesson lessonById(String id) =>
|
||
a0SeedLessons.firstWhere((lesson) => lesson.id == id);
|
||
|
||
class LessonActivity {
|
||
const LessonActivity({
|
||
required this.listening,
|
||
required this.listeningQuestion,
|
||
required this.answers,
|
||
required this.speaking,
|
||
this.speakingTip = '',
|
||
required this.reading,
|
||
required this.readingQuestion,
|
||
required this.readingAnswer,
|
||
this.readingOptions = const [],
|
||
required this.writingPrompt,
|
||
required this.writingExample,
|
||
required this.independentPrompt,
|
||
required this.independentHelp,
|
||
});
|
||
final String listening;
|
||
final String listeningQuestion;
|
||
final List<String> answers;
|
||
final String speaking;
|
||
|
||
/// 这句跟读的发音提示;为空时不显示,避免给每一句套用同一条提示。
|
||
final String speakingTip;
|
||
final String reading;
|
||
final String readingQuestion;
|
||
final String readingAnswer;
|
||
final List<String> readingOptions;
|
||
final String writingPrompt;
|
||
final String writingExample;
|
||
final String independentPrompt;
|
||
final String independentHelp;
|
||
}
|
||
|
||
int _seedHash(String seed) {
|
||
var hash = 0x811c9dc5;
|
||
for (final code in seed.codeUnits) {
|
||
hash = ((hash ^ code) * 0x01000193) & 0x7fffffff;
|
||
}
|
||
return hash;
|
||
}
|
||
|
||
/// 选项顺序固定会让学习者养成“永远选第一个”的习惯。[options] 的第一项是标准
|
||
/// 答案,这里按题目内容确定性地把它挪到某个位置,并打乱其余干扰项:同一道题
|
||
/// 每次进入顺序都一样,但答案在三个位置上分布均匀。
|
||
List<String> shuffledOptions(List<String> options, String seed) {
|
||
if (options.length < 2) return options;
|
||
final distractors = [...options.skip(1)];
|
||
var hash = _seedHash(seed);
|
||
for (var i = distractors.length - 1; i > 0; i--) {
|
||
hash = (hash * 1103515245 + 12345) & 0x7fffffff;
|
||
final j = hash % (i + 1);
|
||
final swap = distractors[i];
|
||
distractors[i] = distractors[j];
|
||
distractors[j] = swap;
|
||
}
|
||
return distractors
|
||
..insert(_seedHash('$seed#slot') % options.length, options.first);
|
||
}
|
||
|
||
const a0Activities = <String, LessonActivity>{
|
||
'a0-01': LessonActivity(
|
||
listening: 'Hello. I’m Mia. Nice to meet you.',
|
||
listeningQuestion: 'Mia 在做什么?',
|
||
answers: ['自我介绍', '买东西', '问时间'],
|
||
speaking: 'Hello. I’m Shen. Nice to meet you.',
|
||
reading:
|
||
'Mia: Hello. I’m Mia. What’s your name?\nShen: Hi. I’m Shen.\nMia: Nice to meet you.\nShen: Thanks. Nice to meet you, too.',
|
||
readingQuestion: 'Shen 说的最后一句是什么?',
|
||
readingAnswer: 'Nice to meet you, too.',
|
||
readingOptions: [
|
||
'Nice to meet you, too.',
|
||
'What’s your name?',
|
||
'Hi. I’m Shen.',
|
||
],
|
||
writingPrompt: '写一句问候和姓名介绍。',
|
||
writingExample: 'Hello. I’m Shen.',
|
||
independentPrompt: '不看句框,介绍你的名字并回应“Nice to meet you”。',
|
||
independentHelp: 'Hi. I’m … / Nice to meet you, too.',
|
||
),
|
||
'a0-02': LessonActivity(
|
||
listening: 'How do you spell your name?',
|
||
listeningQuestion: '这句话是什么意思?',
|
||
answers: ['你的名字怎么拼?', '你的名字是什么?', '你来自哪里?'],
|
||
speaking: 'S H E N',
|
||
speakingTip: '字母之间留一个短停顿:S /es/ - H /eɪtʃ/ - E /iː/ - N /en/。先清楚,不必快。',
|
||
reading:
|
||
'Mia: I’m Mia. M-I-A.\nShen: Hi, Mia. I’m Shen. S-H-E-N.\nMia: How do you spell Sam?\nShen: S-A-M.',
|
||
readingQuestion: 'Shen 自己的名字怎么拼写?',
|
||
readingAnswer: 'S-H-E-N',
|
||
readingOptions: ['S-H-E-N', 'M-I-A', 'S-A-M'],
|
||
writingPrompt: '把名字和拼写写完整。',
|
||
writingExample: 'My name is Shen.\nS-H-E-N.',
|
||
independentPrompt: '不看句框,用英文介绍名字并拼读它。',
|
||
independentHelp: 'My name is … / A-B-C.',
|
||
),
|
||
'a0-03': LessonActivity(
|
||
listening: 'How are you today?',
|
||
listeningQuestion: '对方在问什么?',
|
||
answers: ['你的状态', '你的号码', '你的地点'],
|
||
speaking: 'How are you? I’m good, thanks.',
|
||
reading:
|
||
'Mia: Hi, Shen. How are you?\nShen: I’m good, thanks. How are you?\nMia: I’m tired today.',
|
||
readingQuestion: 'Mia 说自己今天怎么样?',
|
||
readingAnswer: 'I’m tired today.',
|
||
readingOptions: ['I’m tired today.', 'I’m good, thanks.', 'I’m okay.'],
|
||
writingPrompt: '写一句今天的状态。',
|
||
writingExample: 'I’m okay today.',
|
||
independentPrompt: '不看句框,问候并说出你的状态。',
|
||
independentHelp: 'How are you? / I’m …',
|
||
),
|
||
'a0-04': LessonActivity(
|
||
listening: 'One, three, eight.',
|
||
listeningQuestion: '你听到的是哪个号码?',
|
||
answers: ['138', '183', '318'],
|
||
speaking: 'My number is one-three-eight.',
|
||
speakingTip: '号码一个数字一个数字地读,数字之间稍停:one - three - eight。',
|
||
reading:
|
||
'Mia: What’s your phone number?\nShen: My number is one-three-eight.\nMia: One-eight-three?\nShen: No. One-three-eight.',
|
||
readingQuestion: 'Shen 的号码到底是哪一个?',
|
||
readingAnswer: '138',
|
||
readingOptions: ['138', '183', '318'],
|
||
writingPrompt: '把虚拟号码 139 写成英文。',
|
||
writingExample: 'one-three-nine',
|
||
independentPrompt: '不看句框,说出一个三位虚拟号码。',
|
||
independentHelp: 'My number is one-two-three.',
|
||
),
|
||
'a0-05': LessonActivity(
|
||
listening: 'What’s this? It’s a key.',
|
||
listeningQuestion: '这是什么?',
|
||
answers: ['钥匙', '书', '水'],
|
||
speaking: 'What’s this? It’s a pen.',
|
||
reading:
|
||
'Mia: What’s this? Is it a pen?\nShen: No. It’s a book.\nMia: And what’s this?\nShen: It’s a bag.',
|
||
readingQuestion: 'Shen 说第一件东西是什么?',
|
||
readingAnswer: 'A book',
|
||
readingOptions: ['A book', 'A pen', 'A bag'],
|
||
writingPrompt: '写一句“这是一支笔”。',
|
||
writingExample: 'It’s a pen.',
|
||
independentPrompt: '不看句框,说出一个身边物品。',
|
||
independentHelp: 'It’s a …',
|
||
),
|
||
'a0-06': LessonActivity(
|
||
listening: 'Where are you from?',
|
||
listeningQuestion: '对方在问什么?',
|
||
answers: ['来自哪里', '叫什么', '喜欢什么'],
|
||
speaking: 'I’m from Hong Kong.',
|
||
reading:
|
||
'Mia: I’m from London. Where are you from?\nShen: I’m from Hong Kong.',
|
||
readingQuestion: 'Shen 来自哪里?',
|
||
readingAnswer: 'Hong Kong',
|
||
readingOptions: ['Hong Kong', 'London', 'Beijing'],
|
||
writingPrompt: '写一句你来自哪里。',
|
||
writingExample: 'I’m from Hong Kong.',
|
||
independentPrompt: '不看句框,说来自哪里并反问对方。',
|
||
independentHelp: 'I’m from … / Where are you from?',
|
||
),
|
||
'a0-07': LessonActivity(
|
||
listening: 'Who is this? This is my mother.',
|
||
listeningQuestion: '这个人是谁?',
|
||
answers: ['妈妈', '朋友', '老师'],
|
||
speaking: 'This is my family.',
|
||
reading:
|
||
'Mia: Who is this? Is this your father?\nShen: No. This is my mother.\nMia: And who is this? Your sister?\nShen: No. This is my brother.',
|
||
readingQuestion: 'Shen 先介绍的是哪一位家人?',
|
||
readingAnswer: 'My mother',
|
||
readingOptions: ['My mother', 'My brother', 'My father'],
|
||
writingPrompt: '介绍一位家人或朋友。',
|
||
writingExample: 'This is my mother.',
|
||
independentPrompt: '不看句框,介绍一位家人或朋友。',
|
||
independentHelp: 'This is my …',
|
||
),
|
||
'a0-08': LessonActivity(
|
||
listening: 'It’s three o’clock on Monday.',
|
||
listeningQuestion: '是什么时间?',
|
||
answers: ['星期一三点', '星期三一点', '星期一一点'],
|
||
speaking: 'It’s three o’clock.',
|
||
speakingTip: 'o’clock 读作 /əˈklɒk/,前面的 o 很轻。',
|
||
reading:
|
||
'Mia: What day is it today? Is it Tuesday?\nShen: No. It’s Monday.',
|
||
readingQuestion: '对话中今天到底是星期几?',
|
||
readingAnswer: 'Monday',
|
||
readingOptions: ['Monday', 'Tuesday', 'Sunday'],
|
||
writingPrompt: '写一句今天和整点。',
|
||
writingExample: 'It’s Monday. It’s three o’clock.',
|
||
independentPrompt: '不看句框,说今天星期几或现在几点。',
|
||
independentHelp: 'It’s … / It’s … o’clock.',
|
||
),
|
||
'a0-09': LessonActivity(
|
||
listening: 'Do you like tea? Yes, I do.',
|
||
listeningQuestion: '对方喜欢茶吗?',
|
||
answers: ['喜欢', '不喜欢', '不知道'],
|
||
speaking: 'I like tea. Do you like tea?',
|
||
reading:
|
||
'Mia: I like tea. Do you like tea?\nShen: No, I don’t. I like coffee and music.\nMia: Do you like movies?\nShen: Yes, I do.',
|
||
readingQuestion: 'Shen 喜欢喝什么?',
|
||
readingAnswer: 'Coffee',
|
||
readingOptions: ['Coffee', 'Tea', 'Music'],
|
||
writingPrompt: '写一句你的喜好。',
|
||
writingExample: 'I like tea.',
|
||
independentPrompt: '不看句框,说一个喜好并反问。',
|
||
independentHelp: 'I like … / Do you like …?',
|
||
),
|
||
'a0-10': LessonActivity(
|
||
listening: 'Do you like water? Sorry, please say that again.',
|
||
listeningQuestion: '第二个人希望对方做什么?',
|
||
answers: ['再说一遍', '说慢一点', '给他一杯水'],
|
||
speaking: 'Sorry, please say that again. I like water.',
|
||
speakingTip: 'water 的 t 常读得很轻,像轻轻的 d。先清楚,不必快。',
|
||
reading:
|
||
'Alex: Hi! I’m Alex. Where are you from?\nShen: Sorry? Please say that again.\nAlex: Where are you from?\nShen: I’m from Hong Kong. Do you like coffee?\nAlex: No, I don’t. I like water.',
|
||
readingQuestion: 'Alex 喜欢喝什么?',
|
||
readingAnswer: 'Water',
|
||
readingOptions: ['Water', 'Coffee', 'Tea'],
|
||
writingPrompt: '写姓名、地点和喜好三句,喜好用 water。',
|
||
writingExample: 'I’m Shen.\nI’m from Hong Kong.\nI like water.',
|
||
independentPrompt: '不看帮助:先请对方再说一遍,再说姓名、喜欢喝什么,并反问对方。',
|
||
independentHelp:
|
||
'Please say that again. / I’m … / I like water. / Do you like …?',
|
||
),
|
||
};
|
||
|
||
LessonActivity activityByLessonId(String id) => a0Activities[id]!;
|
||
|
||
/// Reviewed activities for split topics. They intentionally avoid values from
|
||
/// later segments so a segment can be completed independently.
|
||
const a0SegmentActivities = <String, LessonActivity>{
|
||
'a0-04-a': LessonActivity(
|
||
listening: 'One, two, three.',
|
||
listeningQuestion: '听到哪一组数字?',
|
||
answers: ['123', '132', '213'],
|
||
speaking: 'Zero, one, two, three.',
|
||
reading: 'Mia: One, two, three.\nShen: Four, five.',
|
||
readingQuestion: 'Shen 接着说了哪两个数字?',
|
||
readingAnswer: 'Four, five',
|
||
readingOptions: ['Four, five', 'One, two', 'Three, four'],
|
||
writingPrompt: '把 0、1、2、3 写成英文。',
|
||
writingExample: 'zero, one, two, three',
|
||
independentPrompt: '不看帮助,说出三个 0 到 5 的数字。',
|
||
independentHelp: 'zero / one / two / three / four / five',
|
||
),
|
||
'a0-04-b': LessonActivity(
|
||
listening: 'Six, seven, eight.',
|
||
listeningQuestion: '听到哪一组数字?',
|
||
answers: ['678', '687', '768'],
|
||
speaking: 'My phone is here.',
|
||
reading: 'Mia: Six, seven, eight.\nShen: Nine, ten.',
|
||
readingQuestion: 'Shen 最后说了哪两个数字?',
|
||
readingAnswer: 'Nine, ten',
|
||
readingOptions: ['Nine, ten', 'Six, seven', 'Seven, eight'],
|
||
writingPrompt: '把 6、7、8 写成英文。',
|
||
writingExample: 'six, seven, eight',
|
||
independentPrompt: '不看帮助,说出三个 6 到 10 的数字。',
|
||
independentHelp: 'six / seven / eight / nine / ten',
|
||
),
|
||
'a0-04-c': LessonActivity(
|
||
listening: 'My number is one-three-eight.',
|
||
listeningQuestion: '号码是多少?',
|
||
answers: ['138', '183', '318'],
|
||
speaking: 'My number is one-three-eight.',
|
||
speakingTip: '号码一个数字一个数字地读,数字之间稍停:one - three - eight。',
|
||
reading:
|
||
'Mia: What’s your phone number?\nShen: My number is five-zero-two.\nMia: Five-two-zero?\nShen: No. Five-zero-two.',
|
||
readingQuestion: 'Shen 最后确认的号码是哪一个?',
|
||
readingAnswer: '502',
|
||
readingOptions: ['502', '520', '205'],
|
||
writingPrompt: '把虚拟号码 139 写成英文。',
|
||
writingExample: 'one-three-nine',
|
||
independentPrompt: '不看句框,说出一个三位虚拟号码。',
|
||
independentHelp: 'My number is one-two-three.',
|
||
),
|
||
'a0-08-a': LessonActivity(
|
||
listening: 'It is Monday.',
|
||
listeningQuestion: '今天星期几?',
|
||
answers: ['星期一', '星期二', '星期三'],
|
||
speaking: 'It is Monday.',
|
||
reading:
|
||
'Mia: What day is it today? Is it Monday?\nShen: No. It is Tuesday.',
|
||
readingQuestion: 'Mia 猜错了,今天其实是星期几?',
|
||
readingAnswer: 'Tuesday',
|
||
readingOptions: ['Tuesday', 'Monday', 'Wednesday'],
|
||
writingPrompt: '写一句今天是星期一、二或三。',
|
||
writingExample: 'It is Monday.',
|
||
independentPrompt: '不看帮助,说出星期一、二或三。',
|
||
independentHelp: 'It is Monday / Tuesday / Wednesday.',
|
||
),
|
||
'a0-08-b': LessonActivity(
|
||
listening: 'It is Friday.',
|
||
listeningQuestion: '今天星期几?',
|
||
answers: ['星期五', '星期四', '星期日'],
|
||
speaking: 'It is Friday.',
|
||
reading: 'Mia: Is it Saturday today?\nShen: No. It is Sunday.',
|
||
readingQuestion: 'Shen 说今天是哪一天?',
|
||
readingAnswer: 'Sunday',
|
||
readingOptions: ['Sunday', 'Saturday', 'Friday'],
|
||
writingPrompt: '写一句今天是星期四到日。',
|
||
writingExample: 'It is Friday.',
|
||
independentPrompt: '不看帮助,说出星期四到日。',
|
||
independentHelp: 'It is Thursday / Friday / Saturday / Sunday.',
|
||
),
|
||
'a0-08-c': LessonActivity(
|
||
listening: 'It is three o’clock.',
|
||
listeningQuestion: '是什么时间?',
|
||
answers: ['三点', '一点', '星期三'],
|
||
speaking: 'It is three o’clock.',
|
||
reading:
|
||
'Mia: What time is it? Is it two o’clock?\nShen: No. It is three o’clock.',
|
||
readingQuestion: 'Shen 说现在是几点?',
|
||
readingAnswer: 'Three o’clock',
|
||
readingOptions: ['Three o’clock', 'Two o’clock', 'One o’clock'],
|
||
writingPrompt: '写一句整点时间。',
|
||
writingExample: 'It is three o’clock.',
|
||
independentPrompt: '不看帮助,说一个整点时间。',
|
||
independentHelp: 'It is … o’clock.',
|
||
),
|
||
};
|
||
|
||
LessonActivity activityBySegmentId(String segmentId, String lessonId) =>
|
||
a0SegmentActivities[segmentId] ?? activityByLessonId(lessonId);
|
||
|
||
/// Short, reviewed entries used before each split segment. These are kept
|
||
/// separate from review prompts: a review hint is not a learner-facing
|
||
/// dictionary definition.
|
||
const a0SegmentVocabulary = <String, List<VocabularyItem>>{
|
||
'a0-04-a': [
|
||
VocabularyItem(
|
||
id: 'A0-W01',
|
||
word: 'zero',
|
||
meaning: '零',
|
||
example: 'Zero, one, two.',
|
||
exampleMeaning: '零、一、二。',
|
||
ipa: '/ˈzɪrəʊ/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W02',
|
||
word: 'one',
|
||
meaning: '一',
|
||
example: 'One, two, three.',
|
||
exampleMeaning: '一、二、三。',
|
||
ipa: '/wʌn/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W03',
|
||
word: 'two',
|
||
meaning: '二',
|
||
example: 'Two and three.',
|
||
exampleMeaning: '二和三。',
|
||
ipa: '/tuː/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W04',
|
||
word: 'three',
|
||
meaning: '三',
|
||
example: 'Three, four, five.',
|
||
exampleMeaning: '三、四、五。',
|
||
ipa: '/θriː/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W05',
|
||
word: 'four',
|
||
meaning: '四',
|
||
example: 'Four and five.',
|
||
exampleMeaning: '四和五。',
|
||
ipa: '/fɔː/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W06',
|
||
word: 'five',
|
||
meaning: '五',
|
||
example: 'Five, please.',
|
||
exampleMeaning: '五,请。',
|
||
ipa: '/faɪv/',
|
||
),
|
||
],
|
||
'a0-04-b': [
|
||
VocabularyItem(
|
||
id: 'A0-W07',
|
||
word: 'six',
|
||
meaning: '六',
|
||
example: 'Six, seven, eight.',
|
||
exampleMeaning: '六、七、八。',
|
||
ipa: '/sɪks/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W08',
|
||
word: 'seven',
|
||
meaning: '七',
|
||
example: 'Seven, eight, nine.',
|
||
exampleMeaning: '七、八、九。',
|
||
ipa: '/ˈsevən/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W09',
|
||
word: 'eight',
|
||
meaning: '八',
|
||
example: 'Eight, nine, ten.',
|
||
exampleMeaning: '八、九、十。',
|
||
ipa: '/eɪt/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W10',
|
||
word: 'nine',
|
||
meaning: '九',
|
||
example: 'Nine and ten.',
|
||
exampleMeaning: '九和十。',
|
||
ipa: '/naɪn/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W11',
|
||
word: 'ten',
|
||
meaning: '十',
|
||
example: 'Ten, please.',
|
||
exampleMeaning: '十,请。',
|
||
ipa: '/ten/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W20',
|
||
word: 'phone',
|
||
meaning: '电话;手机',
|
||
example: 'It is a phone.',
|
||
exampleMeaning: '它是一部手机。',
|
||
ipa: '/fəʊn/',
|
||
),
|
||
],
|
||
'a0-04-c': [
|
||
VocabularyItem(
|
||
id: 'A0-P07',
|
||
word: "What's your phone number?",
|
||
meaning: '你的电话号码是多少?',
|
||
example: "What's your phone number?",
|
||
exampleMeaning: '你的电话号码是多少?',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-P08',
|
||
word: 'My number is one-three-eight.',
|
||
meaning: '我的号码是 138。',
|
||
example: 'My number is one-three-eight.',
|
||
exampleMeaning: '我的号码是 138。',
|
||
),
|
||
],
|
||
'a0-08-a': [
|
||
VocabularyItem(
|
||
id: 'A0-W12',
|
||
word: 'Monday',
|
||
meaning: '星期一',
|
||
example: 'It is Monday.',
|
||
exampleMeaning: '今天是星期一。',
|
||
ipa: '/ˈmʌndeɪ/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W13',
|
||
word: 'Tuesday',
|
||
meaning: '星期二',
|
||
example: 'It is Tuesday.',
|
||
exampleMeaning: '今天是星期二。',
|
||
ipa: '/ˈtjuːzdeɪ/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W14',
|
||
word: 'Wednesday',
|
||
meaning: '星期三',
|
||
example: 'It is Wednesday.',
|
||
exampleMeaning: '今天是星期三。',
|
||
ipa: '/ˈwenzdeɪ/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-P14',
|
||
word: 'What day is it today?',
|
||
meaning: '今天星期几?',
|
||
example: 'What day is it today?',
|
||
exampleMeaning: '今天星期几?',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-P15',
|
||
word: "It's Monday.",
|
||
meaning: '今天是星期一。',
|
||
example: "It's Monday.",
|
||
exampleMeaning: '今天是星期一。',
|
||
),
|
||
],
|
||
'a0-08-b': [
|
||
VocabularyItem(
|
||
id: 'A0-W15',
|
||
word: 'Thursday',
|
||
meaning: '星期四',
|
||
example: 'It is Thursday.',
|
||
exampleMeaning: '今天是星期四。',
|
||
ipa: '/ˈθɜːzdeɪ/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W16',
|
||
word: 'Friday',
|
||
meaning: '星期五',
|
||
example: 'It is Friday.',
|
||
exampleMeaning: '今天是星期五。',
|
||
ipa: '/ˈfraɪdeɪ/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W17',
|
||
word: 'Saturday',
|
||
meaning: '星期六',
|
||
example: 'It is Saturday.',
|
||
exampleMeaning: '今天是星期六。',
|
||
ipa: '/ˈsætədeɪ/',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-W18',
|
||
word: 'Sunday',
|
||
meaning: '星期日',
|
||
example: 'It is Sunday.',
|
||
exampleMeaning: '今天是星期日。',
|
||
ipa: '/ˈsʌndeɪ/',
|
||
),
|
||
],
|
||
'a0-08-c': [
|
||
VocabularyItem(
|
||
id: 'A0-P16',
|
||
word: 'What time is it?',
|
||
meaning: '现在几点?',
|
||
example: 'What time is it?',
|
||
exampleMeaning: '现在几点?',
|
||
),
|
||
VocabularyItem(
|
||
id: 'A0-P17',
|
||
word: "It's three o'clock.",
|
||
meaning: '现在三点。',
|
||
example: "It's three o'clock.",
|
||
exampleMeaning: '现在三点。',
|
||
),
|
||
],
|
||
};
|
||
|
||
List<VocabularyItem> vocabularyBySegmentId(String segmentId, String lessonId) {
|
||
if (!a0SegmentActivities.containsKey(segmentId)) {
|
||
return lessonById(lessonId).vocabulary;
|
||
}
|
||
return a0SegmentVocabulary[segmentId] ?? const <VocabularyItem>[];
|
||
}
|
||
|
||
/// One practical rule per seed lesson. These are reminders for using a sentence
|
||
/// in context, not abstract grammar lectures.
|
||
const a0GrammarNotes = <String, String>{
|
||
'a0-01': '介绍自己时,把名字放在 I’m 后面:I’m Mia. 不需要先学复杂时态。',
|
||
'a0-02': 'My name is 后面接名字;拼读时每个字母之间稍微停顿。',
|
||
'a0-03': '说状态用 I’m + 状态:I’m good / okay / tired。',
|
||
'a0-04': '报号码时数字一个一个说:one-three-eight,不要当成一百三十八。',
|
||
'a0-05': '问物品用 What’s this?;回答用 It’s a + 物品。',
|
||
'a0-06': '说地点用 I’m from + 地点:I’m from Hong Kong。',
|
||
'a0-07': '介绍人用 This is my + 人:This is my mother。',
|
||
'a0-08': '星期和整点都可用 It’s:It’s Monday. / It’s three o’clock。',
|
||
'a0-09': '说喜好用 I like + 东西;一般问句用 Do you like + 东西?',
|
||
'a0-10': '礼貌请求以 Please 开头:Please say that again.',
|
||
};
|
||
|
||
String grammarNoteForLesson(String id) => a0GrammarNotes[id] ?? '';
|
||
|
||
const a0SegmentGrammarNotes = <String, String>{
|
||
'a0-04-a': '数字单独说就可以:zero, one, two。每个词之间稍微停顿。',
|
||
'a0-04-b': '认物品可以说 It is a phone. 口语里也常说 It’s a phone.',
|
||
'a0-04-c': '报号码时一个数字一个数字说:one-three-eight,不把它读成一百三十八。',
|
||
'a0-08-a': '问星期用 What day is it?;回答用 It is / It’s + 星期。',
|
||
'a0-08-b': '星期四到星期日也沿用 It is / It’s + 星期这个句型。',
|
||
'a0-08-c': '说整点用 It is / It’s + 数字 + o’clock,例如 It’s three o’clock。',
|
||
};
|
||
|
||
String grammarNoteForSegment(String segmentId, String lessonId) =>
|
||
a0SegmentGrammarNotes[segmentId] ?? grammarNoteForLesson(lessonId);
|
||
|
||
String _normalizeDialogueInput(String response) => response
|
||
.toLowerCase()
|
||
.replaceAll('\u2019', "'")
|
||
.replaceAll('\u2018', "'")
|
||
.replaceAll('’', "'")
|
||
.replaceAll('‘', "'")
|
||
.replaceAll(RegExp(r'\s+'), ' ')
|
||
.trim();
|
||
|
||
bool matchesSegmentDialogue(String segmentId, int stage, String response) {
|
||
final text = _normalizeDialogueInput(response);
|
||
final segment = a0LessonSegments.values
|
||
.expand((segments) => segments)
|
||
.where((item) => item.id == segmentId)
|
||
.firstOrNull;
|
||
if (segment == null || stage >= segment.dialogueRequiredTerms.length) {
|
||
return false;
|
||
}
|
||
bool hasAny(Iterable<String> terms) =>
|
||
terms.any((term) => text.contains(_normalizeDialogueInput(term)));
|
||
// A phone-number report needs both the reporting frame and at least one
|
||
// spoken digit. Other segment turns need one reviewed, task-specific term.
|
||
if (segmentId == 'a0-04-c' && stage == 0) {
|
||
return text.contains('my number is') &&
|
||
hasAny(segment.dialogueRequiredTerms[stage].skip(1));
|
||
}
|
||
return hasAny(segment.dialogueRequiredTerms[stage]);
|
||
}
|
||
|
||
bool matchesSegmentIndependent(String segmentId, String response) {
|
||
final text = _normalizeDialogueInput(response);
|
||
final segment = a0LessonSegments.values
|
||
.expand((segments) => segments)
|
||
.where((item) => item.id == segmentId)
|
||
.firstOrNull;
|
||
if (segment == null) {
|
||
// Single-segment lessons: every group of the lesson's independent task
|
||
// has to be present. Unknown segments never pass.
|
||
final lessonId = segmentId.replaceFirst(RegExp(r'-[a-z]$'), '');
|
||
final groups = a0IndependentRequiredTerms[lessonId];
|
||
if (groups == null) return false;
|
||
return groups.every(
|
||
(group) => group.any((term) => _containsTerm(text, term)),
|
||
);
|
||
}
|
||
if (segmentId == 'a0-04-a' || segmentId == 'a0-04-b') {
|
||
final count = segment.independentRequiredTerms
|
||
.where((term) => text.contains(_normalizeDialogueInput(term)))
|
||
.length;
|
||
return count >= 3;
|
||
}
|
||
if (segmentId == 'a0-04-c') {
|
||
return segment.independentRequiredTerms
|
||
.where((term) => text.contains(_normalizeDialogueInput(term)))
|
||
.length >=
|
||
3;
|
||
}
|
||
return segment.independentRequiredTerms.any(
|
||
(term) => text.contains(_normalizeDialogueInput(term)),
|
||
);
|
||
}
|
||
|
||
const _introductionTerms = [
|
||
"i'm + #word",
|
||
'i am + #word',
|
||
'my name is',
|
||
"my name's",
|
||
];
|
||
|
||
/// What the independent attempt of each single-segment lesson has to contain.
|
||
/// Each inner list is one part of the task; a part passes when any of its
|
||
/// terms appears. Terms follow the same syntax as [LessonDialogue.requiredTerms].
|
||
const a0IndependentRequiredTerms = <String, List<List<String>>>{
|
||
// 介绍你的名字并回应 Nice to meet you
|
||
'a0-01': [
|
||
_introductionTerms,
|
||
['nice to meet you'],
|
||
],
|
||
// 介绍名字并拼读它
|
||
'a0-02': [
|
||
_introductionTerms,
|
||
['#spelling'],
|
||
],
|
||
// 问候并说出你的状态
|
||
'a0-03': [
|
||
['hello', 'hi', 'hey', 'how are you'],
|
||
['good', 'okay', 'ok', 'fine', 'great', 'tired'],
|
||
],
|
||
// 说出一个身边物品
|
||
'a0-05': [
|
||
["it's a", 'it is a', "it's an", 'it is an'],
|
||
],
|
||
// 说来自哪里并反问对方
|
||
'a0-06': [
|
||
["i'm from", 'i am from'],
|
||
['where are you from', '#question'],
|
||
],
|
||
// 介绍一位家人或朋友
|
||
'a0-07': [
|
||
['this is my'],
|
||
],
|
||
// 说一个喜好并反问
|
||
'a0-09': [
|
||
['i like', 'i love'],
|
||
['do you like', '#question'],
|
||
],
|
||
// 请对方重复,完成姓名、喜好和反问
|
||
'a0-10': [
|
||
['say that again', 'speak slowly', 'pardon'],
|
||
_introductionTerms,
|
||
['i like', 'i love'],
|
||
['#question'],
|
||
],
|
||
};
|
||
|
||
class LessonDialogue {
|
||
const LessonDialogue({
|
||
required this.goal,
|
||
required this.prompts,
|
||
required this.hints,
|
||
this.translations = const [],
|
||
this.requiredTerms = const [],
|
||
this.taskLabels = const [],
|
||
});
|
||
final String goal;
|
||
final List<String> prompts;
|
||
final List<String> hints;
|
||
final List<String> translations;
|
||
|
||
/// One accepted term group per learner turn. The learner only needs to hit
|
||
/// one term in the current group, so natural wording still passes. A term
|
||
/// may be a plain word or phrase, an `a + b` conjunction (both parts are
|
||
/// required), or a structural token starting with `#`.
|
||
final List<List<String>> requiredTerms;
|
||
|
||
/// Chinese label of what each learner turn has to do. It drives the
|
||
/// "还没完成本轮任务" message and the summary of completed tasks, so it must
|
||
/// describe the task without giving away the model answer.
|
||
final List<String> taskLabels;
|
||
}
|
||
|
||
/// Matches whole words only: the old `text.contains('it')` accepted almost any
|
||
/// sentence, including ones that never performed the task.
|
||
bool _containsTerm(String text, String term) {
|
||
if (term.contains(' + ')) {
|
||
return term.split(' + ').every((part) => _containsTerm(text, part.trim()));
|
||
}
|
||
if (term.startsWith('#')) return _matchesStructure(text, term);
|
||
final normTerm = _normalizeDialogueInput(term);
|
||
final escaped = RegExp.escape(normTerm);
|
||
return RegExp('(?<![a-z])$escaped(?![a-z])').hasMatch(text);
|
||
}
|
||
|
||
bool _matchesStructure(String text, String token) => switch (token) {
|
||
// Letters said one by one ("S-H-E-N", "s h e n", "L-I"), or the spelled
|
||
// name typed as one word ("Shen", "aaa"). Case and hyphens are ignored.
|
||
'#spelling' =>
|
||
RegExp(
|
||
r'(?:^|[^a-z])[a-z](?:\s*[-\u2010-\u2015]\s*[a-z]|\s[a-z])+(?![a-z])',
|
||
).hasMatch(text) ||
|
||
RegExp(r'^[a-z]{2,}$').hasMatch(
|
||
text
|
||
.replaceAll(RegExp(r'[-\u2010-\u2015]'), '')
|
||
.replaceAll(RegExp(r'^[^a-z]+|[^a-z]+$'), ''),
|
||
),
|
||
// Any spoken or written digit.
|
||
'#digit' => RegExp(
|
||
r'(?<![a-z])(zero|one|two|three|four|five|six|seven|eight|nine|ten)(?![a-z])|[0-9]',
|
||
).hasMatch(text),
|
||
// A real word after the frame, so "I'm" alone is not a name.
|
||
'#word' => RegExp(r'[a-z]{2,}').hasMatch(text),
|
||
// Any question addressed to the partner.
|
||
'#question' =>
|
||
text.contains('?') ||
|
||
RegExp(
|
||
r"(?<![a-z])(what|where|who|when|how|why|do you|are you|can you)(?![a-z])",
|
||
).hasMatch(text),
|
||
_ => false,
|
||
};
|
||
|
||
/// Whole-lesson and free-scene dialogues validate the same way segment
|
||
/// dialogues do: the turn has to contain the language the turn is teaching.
|
||
bool matchesDialogueStage(LessonDialogue script, int stage, String response) {
|
||
final text = _normalizeDialogueInput(response);
|
||
if (stage < 0 || stage >= script.requiredTerms.length) {
|
||
// No declared requirement: accept anything that is actually a word.
|
||
return RegExp(r'[a-z]{2,}').hasMatch(text);
|
||
}
|
||
final terms = script.requiredTerms[stage];
|
||
if (terms.isEmpty) return RegExp(r'[a-z]{2,}').hasMatch(text);
|
||
return terms.any((term) => _containsTerm(text, term));
|
||
}
|
||
|
||
String dialogueTaskLabel(LessonDialogue script, int stage) =>
|
||
stage >= 0 && stage < script.taskLabels.length
|
||
? script.taskLabels[stage]
|
||
: '完成本轮任务';
|
||
|
||
/// The free "初次见面" scene. It lives next to the lesson dialogues so its
|
||
/// tasks are validated and summarised by the same rules.
|
||
const a0MeetDialogue = LessonDialogue(
|
||
goal: '姓名、地点、状态或喜好,并反问',
|
||
prompts: [
|
||
'Hi! My name is Mia. What\u2019s your name?',
|
||
'Nice to meet you. Where are you from?',
|
||
'Great! How are you today? Or what do you like?',
|
||
'I\u2019m good, thanks. Now ask me one question!',
|
||
],
|
||
hints: [
|
||
'My name is Alex.',
|
||
'I\u2019m from Hong Kong.',
|
||
'I\u2019m good, thanks. / I like coffee.',
|
||
'What\u2019s your name? / Where are you from?',
|
||
],
|
||
translations: [
|
||
'嗨!我叫 Mia。你叫什么名字?',
|
||
'很高兴认识你。你来自哪里?',
|
||
'很好!你今天怎么样?或者你喜欢什么?',
|
||
'我很好,谢谢。现在请问我一个问题!',
|
||
],
|
||
requiredTerms: [
|
||
["i'm + #word", 'i am + #word', 'my name is', "my name's", 'name is'],
|
||
["i'm from", 'i am from', 'from + #word'],
|
||
['good', 'okay', 'ok', 'fine', 'great', 'tired', 'i like', 'i love'],
|
||
['#question'],
|
||
],
|
||
taskLabels: ['介绍姓名', '说明来自哪里', '表达状态或喜好', '反问对方'],
|
||
);
|
||
|
||
/// A free practice scene. It opens once [unlockAfterLessonId] is complete
|
||
/// (always open when null) and only uses language taught up to that lesson.
|
||
class DialogueScene {
|
||
const DialogueScene({
|
||
required this.id,
|
||
required this.title,
|
||
required this.summary,
|
||
required this.script,
|
||
required this.recapPrompt,
|
||
this.unlockAfterLessonId,
|
||
});
|
||
|
||
final String id;
|
||
final String title;
|
||
final String summary;
|
||
final LessonDialogue script;
|
||
|
||
/// The follow-up review prompt added after the scene is finished.
|
||
final String recapPrompt;
|
||
final String? unlockAfterLessonId;
|
||
}
|
||
|
||
const a0Scenes = [
|
||
DialogueScene(
|
||
id: 'a0-meet',
|
||
title: '初次见面',
|
||
summary: '介绍姓名、地点、状态或喜好,并反问对方',
|
||
script: a0MeetDialogue,
|
||
recapPrompt: '再用英语介绍一次自己。',
|
||
),
|
||
DialogueScene(
|
||
id: 'a0-number',
|
||
title: '留个电话',
|
||
summary: '问候、报出号码、再说一遍,并反问对方',
|
||
unlockAfterLessonId: 'a0-04',
|
||
recapPrompt: '用英语报一次你的虚拟电话号码。',
|
||
script: LessonDialogue(
|
||
goal: '问候并交换一个虚拟电话号码',
|
||
prompts: [
|
||
'Hi! How are you today?',
|
||
'Good. What\u2019s your phone number? Use a fake number.',
|
||
'Sorry, please say it again.',
|
||
'Thanks! Now ask me one question.',
|
||
],
|
||
hints: [
|
||
'I\u2019m good, thanks.',
|
||
'My number is one-three-eight.',
|
||
'One-three-eight.',
|
||
'What\u2019s your phone number?',
|
||
],
|
||
translations: [
|
||
'嗨!你今天好吗?',
|
||
'好的。你的电话号码是多少?用一个虚拟号码。',
|
||
'抱歉,请再说一遍。',
|
||
'谢谢!现在问我一个问题。',
|
||
],
|
||
requiredTerms: [
|
||
['good', 'okay', 'ok', 'fine', 'great', 'tired'],
|
||
['#digit'],
|
||
['#digit'],
|
||
['#question'],
|
||
],
|
||
taskLabels: ['说出你今天的状态', '报出一个号码', '再说一次号码', '反问对方'],
|
||
),
|
||
),
|
||
DialogueScene(
|
||
id: 'a0-photo',
|
||
title: '看照片聊天',
|
||
summary: '介绍照片里的人和东西,说喜好,并反问对方',
|
||
unlockAfterLessonId: 'a0-07',
|
||
recapPrompt: '用英语介绍一位家人或朋友。',
|
||
script: LessonDialogue(
|
||
goal: '介绍照片里的人和物品',
|
||
prompts: [
|
||
'Nice photo! Who is this?',
|
||
'And what\u2019s this in the photo?',
|
||
'Where are you from?',
|
||
'Cool! Now ask me one question.',
|
||
],
|
||
hints: [
|
||
'This is my sister.',
|
||
'It\u2019s a bag.',
|
||
'I\u2019m from Hong Kong.',
|
||
'Who is this? / What\u2019s this?',
|
||
],
|
||
translations: ['照片真好!这是谁?', '照片里这个是什么?', '你来自哪里?', '真酷!现在问我一个问题。'],
|
||
requiredTerms: [
|
||
['this is my', 'this is'],
|
||
["it's a", 'it is a', "it's an", 'it is an'],
|
||
["i'm from", 'i am from', 'from + #word'],
|
||
['#question'],
|
||
],
|
||
taskLabels: ['介绍照片里的人', '说出照片里的物品', '说出你来自哪里', '反问对方'],
|
||
),
|
||
),
|
||
DialogueScene(
|
||
id: 'a0-plan',
|
||
title: '约个时间',
|
||
summary: '说星期和整点,回答喜好,并反问对方',
|
||
unlockAfterLessonId: 'a0-09',
|
||
recapPrompt: '用英语说今天星期几和现在几点。',
|
||
script: LessonDialogue(
|
||
goal: '说明星期、时间和喜好',
|
||
prompts: [
|
||
'Hi! What day is it today?',
|
||
'What time is it now?',
|
||
'Do you like coffee or tea?',
|
||
'Me too! Now ask me one question.',
|
||
],
|
||
hints: [
|
||
'It\u2019s Friday.',
|
||
'It\u2019s three o\u2019clock.',
|
||
'I like tea.',
|
||
'Do you like music?',
|
||
],
|
||
translations: ['嗨!今天星期几?', '现在几点?', '你喜欢咖啡还是茶?', '我也是!现在问我一个问题。'],
|
||
requiredTerms: [
|
||
[
|
||
'monday',
|
||
'tuesday',
|
||
'wednesday',
|
||
'thursday',
|
||
'friday',
|
||
'saturday',
|
||
'sunday',
|
||
],
|
||
["o'clock", 'oclock', "it's + #digit", 'it is + #digit'],
|
||
['i like', 'i love', 'yes', 'no', "i don't"],
|
||
['#question'],
|
||
],
|
||
taskLabels: ['说出今天星期几', '说出现在几点', '说出你的喜好', '反问对方'],
|
||
),
|
||
),
|
||
];
|
||
|
||
DialogueScene sceneById(String id) =>
|
||
a0Scenes.where((scene) => scene.id == id).firstOrNull ?? a0Scenes.first;
|
||
|
||
const a0Dialogues = <String, LessonDialogue>{
|
||
'a0-01': LessonDialogue(
|
||
goal: '问候、介绍姓名并回应见面问候',
|
||
prompts: [
|
||
'Hi! I’m Mia. What’s your name?',
|
||
'Nice to meet you. Say: Nice to meet you, too.',
|
||
'Great! Say hello one more time.',
|
||
'Now ask me my name!',
|
||
],
|
||
hints: [
|
||
'I’m Alex.',
|
||
'Nice to meet you, too.',
|
||
'Hello!',
|
||
'What’s your name?',
|
||
],
|
||
translations: [
|
||
'嗨!我是 Mia。你叫什么名字?',
|
||
'很高兴认识你。请说:Nice to meet you, too(我也很高兴认识你)。',
|
||
'太棒了!再打一次招呼吧。',
|
||
'现在请问我的名字!',
|
||
],
|
||
requiredTerms: [
|
||
["i'm + #word", 'i am + #word', 'my name is', "my name's", 'name is'],
|
||
['nice to meet you'],
|
||
['hello', 'hi', 'hey'],
|
||
["what's your name", 'what is your name', 'your name', '#question'],
|
||
],
|
||
taskLabels: ['说出你的名字', '回应 Nice to meet you', '再打一次招呼', '反问对方的名字'],
|
||
),
|
||
'a0-02': LessonDialogue(
|
||
goal: '介绍姓名并完整拼读名字',
|
||
prompts: [
|
||
'Hi! What’s your name?',
|
||
'How do you spell that?',
|
||
'Thank you. Spell it one more time.',
|
||
'Now ask me my name!',
|
||
],
|
||
hints: ['My name is Alex.', 'A-L-E-X.', 'A-L-E-X.', 'What’s your name?'],
|
||
translations: ['嗨!你叫什么名字?', '那个怎么拼写?', '谢谢。请再拼写一次。', '现在请问我的名字!'],
|
||
requiredTerms: [
|
||
["i'm + #word", 'i am + #word', 'my name is', "my name's", 'name is'],
|
||
['#spelling'],
|
||
['#spelling'],
|
||
["what's your name", 'what is your name', 'your name', '#question'],
|
||
],
|
||
taskLabels: ['说出你的名字', '拼读你的名字', '再拼读一次', '反问对方的名字'],
|
||
),
|
||
'a0-03': LessonDialogue(
|
||
goal: '询问状态、回答并反问',
|
||
prompts: [
|
||
'Hi! How are you today?',
|
||
'Good! Ask me: How are you?',
|
||
'I’m okay. Say your state one more time.',
|
||
'Now say goodbye!',
|
||
],
|
||
hints: ['I’m good, thanks.', 'How are you?', 'I’m okay.', 'Bye!'],
|
||
translations: [
|
||
'嗨!你今天好吗?',
|
||
'很好!请问我:How are you(你好吗)?',
|
||
'我还好。请再说一次你的状态。',
|
||
'现在请说再见!',
|
||
],
|
||
requiredTerms: [
|
||
['good', 'okay', 'ok', 'fine', 'great', 'tired'],
|
||
['how are you', '#question'],
|
||
['good', 'okay', 'ok', 'fine', 'great', 'tired'],
|
||
['bye', 'goodbye', 'see you'],
|
||
],
|
||
taskLabels: ['说出你今天的状态', '反问对方的状态', '再说一次你的状态', '说再见'],
|
||
),
|
||
'a0-04': LessonDialogue(
|
||
goal: '报告一个虚拟三位号码并确认',
|
||
prompts: [
|
||
'What’s your phone number? Use a fake three-digit number.',
|
||
'I heard one-three-eight. Is that right?',
|
||
'Please say the three numbers again.',
|
||
'Now ask me for my number!',
|
||
],
|
||
hints: [
|
||
'My number is one-three-eight.',
|
||
'Yes.',
|
||
'One-three-eight.',
|
||
'What’s your phone number?',
|
||
],
|
||
translations: [
|
||
'你的电话号码是多少?可以使用一个虚拟的三位数字。',
|
||
'我听到了 1-3-8。对吗?',
|
||
'请再说一遍这三个数字。',
|
||
'现在请问我的电话号码!',
|
||
],
|
||
requiredTerms: [
|
||
['my number is + #digit', "my number's + #digit", 'number is + #digit'],
|
||
['yes', 'no', "that's right", 'right', 'correct'],
|
||
['#digit'],
|
||
[
|
||
"what's your phone number",
|
||
'what is your phone number',
|
||
'your phone number',
|
||
'your number',
|
||
],
|
||
],
|
||
taskLabels: ['报出一个三位号码', '确认或纠正听到的号码', '再说一次这三个数字', '反问对方的号码'],
|
||
),
|
||
'a0-05': LessonDialogue(
|
||
goal: '询问并说出一个物品',
|
||
prompts: [
|
||
'What’s this? It is a pen.',
|
||
'Now say: It’s a pen.',
|
||
'Ask me: What’s this?',
|
||
'Great! Say one more object.',
|
||
],
|
||
hints: ['It’s a pen.', 'It’s a pen.', 'What’s this?', 'It’s a book.'],
|
||
translations: [
|
||
'这是什么?这是一支笔。',
|
||
'现在请说:It’s a pen(这是一支笔)。',
|
||
'请问我:What’s this(这是什么)?',
|
||
'太棒了!再说一个物品吧。',
|
||
],
|
||
requiredTerms: [
|
||
["it's a", 'it is a', "it's an", 'it is an'],
|
||
["it's a", 'it is a', "it's an", 'it is an'],
|
||
["what's this", 'what is this'],
|
||
["it's a", 'it is a', "it's an", 'it is an'],
|
||
],
|
||
taskLabels: ['说出这个物品', '完整说出 It’s a … 句型', '反问 What’s this', '再说一个物品'],
|
||
),
|
||
'a0-06': LessonDialogue(
|
||
goal: '说来自哪里并反问',
|
||
prompts: [
|
||
'Where are you from?',
|
||
'Nice! Ask me: Where are you from?',
|
||
'Say where you are from one more time.',
|
||
'Say goodbye!',
|
||
],
|
||
hints: [
|
||
'I’m from Hong Kong.',
|
||
'Where are you from?',
|
||
'I’m from Hong Kong.',
|
||
'Bye!',
|
||
],
|
||
translations: [
|
||
'你来自哪里?',
|
||
'很好!请问我:Where are you from(你来自哪里)?',
|
||
'请再说一次你来自哪里。',
|
||
'请说再见!',
|
||
],
|
||
requiredTerms: [
|
||
["i'm from", 'i am from', 'from + #word'],
|
||
['where are you from', "where're you from", 'where are you'],
|
||
["i'm from", 'i am from', 'from + #word'],
|
||
['bye', 'goodbye', 'see you'],
|
||
],
|
||
taskLabels: ['说出你来自哪里', '反问对方来自哪里', '再说一次你来自哪里', '说再见'],
|
||
),
|
||
'a0-07': LessonDialogue(
|
||
goal: '介绍一位家人或朋友',
|
||
prompts: [
|
||
'Who is this?',
|
||
'Good. Say: This is my mother.',
|
||
'Ask me: Who is this?',
|
||
'Introduce one person again.',
|
||
],
|
||
hints: [
|
||
'This is my mother.',
|
||
'This is my mother.',
|
||
'Who is this?',
|
||
'This is my friend.',
|
||
],
|
||
translations: [
|
||
'这是谁?',
|
||
'很好。请说:This is my mother(这是我的妈妈)。',
|
||
'请问我:Who is this(这是谁)?',
|
||
'请再介绍一个人。',
|
||
],
|
||
requiredTerms: [
|
||
['this is my', 'this is'],
|
||
['this is my', 'this is'],
|
||
['who is this', "who's this"],
|
||
['this is my', 'this is'],
|
||
],
|
||
taskLabels: [
|
||
'介绍一位家人或朋友',
|
||
'完整说出 This is my … 句型',
|
||
'反问 Who is this',
|
||
'再介绍一个人',
|
||
],
|
||
),
|
||
'a0-08': LessonDialogue(
|
||
goal: '说明星期或整点',
|
||
prompts: [
|
||
'What day is it today?',
|
||
'What time is it?',
|
||
'Say one full time sentence.',
|
||
'Now ask me the day!',
|
||
],
|
||
hints: [
|
||
'It’s Monday.',
|
||
'It’s three o’clock.',
|
||
'It’s three o’clock.',
|
||
'What day is it?',
|
||
],
|
||
translations: ['今天星期几?', '现在几点了?', '请说一个完整的时间句子。', '现在请问我今天是星期几!'],
|
||
requiredTerms: [
|
||
[
|
||
'monday',
|
||
'tuesday',
|
||
'wednesday',
|
||
'thursday',
|
||
'friday',
|
||
'saturday',
|
||
'sunday',
|
||
],
|
||
["o'clock", 'oclock', "#digit + o'clock"],
|
||
["it's + o'clock", "it is + o'clock", "it's + #digit", 'it is + #digit'],
|
||
['what day is it', 'what day', '#question'],
|
||
],
|
||
taskLabels: ['说出今天星期几', '说出现在几点', '说一个完整的时间句子', '反问今天星期几'],
|
||
),
|
||
'a0-09': LessonDialogue(
|
||
goal: '表达喜好、回答和反问',
|
||
prompts: [
|
||
'What do you like?',
|
||
'Do you like tea?',
|
||
'Now ask me what I like.',
|
||
'Say your like one more time.',
|
||
],
|
||
hints: ['I like tea.', 'Yes, I do.', 'Do you like tea?', 'I like tea.'],
|
||
translations: ['你喜欢什么?', '你喜欢茶吗?', '现在请问我喜欢什么。', '请再说一次你的喜好。'],
|
||
requiredTerms: [
|
||
['i like', 'i love'],
|
||
['yes', 'no', 'i do', "i don't", 'i do not'],
|
||
['do you like', '#question'],
|
||
['i like', 'i love'],
|
||
],
|
||
taskLabels: ['说出你喜欢什么', '回答是否喜欢', '反问对方的喜好', '再说一次你的喜好'],
|
||
),
|
||
'a0-10': LessonDialogue(
|
||
goal: '请求重复或放慢语速,并完成基础沟通',
|
||
prompts: [
|
||
'Say: Please say that again.',
|
||
'Say: Please speak slowly.',
|
||
'Now ask me your name or where I am from.',
|
||
'Tell me what you like to drink.',
|
||
],
|
||
hints: [
|
||
'Please say that again.',
|
||
'Please speak slowly.',
|
||
'What’s your name?',
|
||
'I like water.',
|
||
],
|
||
translations: [
|
||
'请说:Please say that again(请再说一遍)。',
|
||
'请说:Please speak slowly(请说慢一点)。',
|
||
'现在请问我的名字或我来自哪里。',
|
||
'告诉我你喜欢喝什么。',
|
||
],
|
||
requiredTerms: [
|
||
['say that again', 'again', 'pardon'],
|
||
['slowly', 'slow'],
|
||
[
|
||
"what's your name",
|
||
'what is your name',
|
||
'where are you from',
|
||
'your name',
|
||
'#question',
|
||
],
|
||
['i like', 'i love'],
|
||
],
|
||
taskLabels: ['请求对方重复', '请求对方放慢语速', '反问名字或来自哪里', '说出你喜欢喝什么'],
|
||
),
|
||
};
|
||
|
||
const a0SegmentDialogues = <String, LessonDialogue>{
|
||
'a0-04-a': LessonDialogue(
|
||
goal: '听辨并说出 0 到 5',
|
||
prompts: ['Say zero, one, two.', 'Now say three, four, five.'],
|
||
hints: ['zero, one, two', 'three, four, five'],
|
||
translations: [
|
||
'请说 zero, one, two(0,1,2)。',
|
||
'现在请说 three, four, five(3,4,5)。',
|
||
],
|
||
taskLabels: ['说出 zero, one, two', '说出 three, four, five'],
|
||
),
|
||
'a0-04-b': LessonDialogue(
|
||
goal: '听辨 6 到 10 并认识 phone',
|
||
prompts: ['Say six, seven, eight.', 'What is this? Say: It is a phone.'],
|
||
hints: ['six, seven, eight', 'It is a phone.'],
|
||
translations: [
|
||
'请说 six, seven, eight(6,7,8)。',
|
||
'这是什么?请说:It is a phone(这是一部手机)。',
|
||
],
|
||
taskLabels: ['说出 six, seven, eight', '说出 It is a phone.'],
|
||
),
|
||
'a0-04-c': LessonDialogue(
|
||
goal: '询问并报告三位号码',
|
||
prompts: ['What is your phone number?', 'Say a three-digit number again.'],
|
||
hints: ['My number is one-three-eight.', 'one-three-eight'],
|
||
translations: ['你的电话号码是多少?', '请再说一次三位数字。'],
|
||
taskLabels: ['报出你的三位号码', '再说一次三位数字'],
|
||
),
|
||
'a0-08-a': LessonDialogue(
|
||
goal: '询问并说出星期一到三',
|
||
prompts: ['What day is it?', 'Say Monday, Tuesday, or Wednesday.'],
|
||
hints: ['What day is it?', 'It is Monday.'],
|
||
translations: ['今天星期几?', '请说 Monday, Tuesday, 或 Wednesday(周一、周二或周三)。'],
|
||
taskLabels: ['问今天星期几', '说出周一到周三中的一天'],
|
||
),
|
||
'a0-08-b': LessonDialogue(
|
||
goal: '说出星期四到日',
|
||
prompts: ['What day is it?', 'Say Thursday, Friday, Saturday, or Sunday.'],
|
||
hints: ['What day is it?', 'It is Friday.'],
|
||
translations: [
|
||
'今天星期几?',
|
||
'请说 Thursday, Friday, Saturday, 或 Sunday(周四、周五、周六或周日)。',
|
||
],
|
||
taskLabels: ['问今天星期几', '说出周四到周日中的一天'],
|
||
),
|
||
'a0-08-c': LessonDialogue(
|
||
goal: '询问并说出整点',
|
||
prompts: ['What time is it?', 'Say one full time sentence.'],
|
||
hints: ['What time is it?', 'It is three o’clock.'],
|
||
translations: ['现在几点了?', '请说一个完整的时间句子。'],
|
||
taskLabels: ['问现在几点', '说一个完整的时间句子'],
|
||
),
|
||
};
|
||
|
||
LessonDialogue dialogueByLessonId(String id) => a0Dialogues[id]!;
|
||
|
||
LessonDialogue dialogueBySegmentId(String segmentId, String lessonId) =>
|
||
a0SegmentDialogues[segmentId] ?? dialogueByLessonId(lessonId);
|
||
|
||
/// The English a learner has actually met up to and including [lessonId].
|
||
/// The AI partner is told to stay inside this list so a beginner never gets an
|
||
/// answer built from words the course has not taught yet.
|
||
List<String> taughtLanguageUpTo(String lessonId) {
|
||
final result = <String>[];
|
||
for (final entry in a0TargetItemIdsByLesson.entries) {
|
||
for (final id in entry.value) {
|
||
final word = a0CoreItems[id];
|
||
if (word != null) result.add(word);
|
||
}
|
||
if (entry.key == lessonId) break;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// Everything A0 teaches, for the free scene that mixes all topics.
|
||
List<String> get allTaughtLanguage => a0CoreItems.values.toList();
|