整个课程和数据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
+78 -31
View File
@@ -5,14 +5,15 @@ import '../core/app_state.dart';
import '../core/app_theme.dart';
import '../core/ai_service.dart';
import '../core/models.dart';
import '../core/seed_courses.dart';
import '../core/courses/courses.dart';
import '../core/voice_service.dart';
import 'app_widgets.dart';
List<VocabularyItem> get courseLexiconEntries {
// Segment words first: they carry the example and IPA a lesson shows.
final entries = <VocabularyItem>[
...a0SeedLessons.expand((lesson) => lesson.vocabulary),
...a0SegmentVocabulary.values.expand((items) => items),
...segmentVocabulary.values.expand((items) => items),
...allLessons.expand((lesson) => lesson.vocabulary),
];
final seen = <String>{};
return entries.where((item) => seen.add(item.word.toLowerCase())).toList()
@@ -136,9 +137,7 @@ class _LexiconTextState extends State<LexiconText> {
final expression = RegExp(
r'(?<![a-zA-Z0-9])(?:' +
entries
.map(
(item) => RegExp.escape(item.word).replaceAll('’', "['’]"),
)
.map((item) => RegExp.escape(item.word).replaceAll('’', "['’]"))
.join('|') +
r')(?![a-zA-Z0-9])',
caseSensitive: false,
@@ -315,8 +314,8 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
sentenceAnalysis = result;
sentenceAnalysisError = result == null
? (widget.state.aiProvider.name == 'mock'
? '未能完成解析,请稍后重试。'
: 'AI 解析暂不可用,请检查网络或 AI 服务配置。')
? '未能完成解析,请稍后重试。'
: 'AI 解析暂不可用,请检查网络或 AI 服务配置。')
: null;
});
@@ -571,24 +570,41 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
IconButton(
icon: const Icon(Icons.volume_up_outlined, size: 20),
tooltip: '播放读音',
onPressed: () => VoiceService.instance.speak(phrase.phrase),
onPressed: () =>
VoiceService.instance.speak(phrase.phrase),
),
const SizedBox(width: 4),
_addedToReview.contains(phrase.phrase.toLowerCase())
? Chip(
label: Text('已在复习', style: TextStyle(fontSize: 12)),
avatar: Icon(Icons.check, size: 14, color: AppColors.green),
label: Text(
'已在复习',
style: TextStyle(fontSize: 12),
),
avatar: Icon(
Icons.check,
size: 14,
color: AppColors.green,
),
padding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
)
: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
visualDensity: VisualDensity.compact,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
),
onPressed: () => _addPhraseToReview(phrase),
icon: const Icon(Icons.bookmark_add_outlined, size: 14),
label: const Text('加复习', style: TextStyle(fontSize: 12)),
icon: const Icon(
Icons.bookmark_add_outlined,
size: 14,
),
label: const Text(
'加复习',
style: TextStyle(fontSize: 12),
),
),
],
),
@@ -602,10 +618,7 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
if (phrase.usageNote != null && phrase.usageNote!.isNotEmpty)
Text(
'用法:${phrase.usageNote}',
style: TextStyle(
fontSize: 12,
color: AppColors.muted,
),
style: TextStyle(fontSize: 12, color: AppColors.muted),
),
],
),
@@ -621,7 +634,9 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
style: TextStyle(fontSize: 12, color: AppColors.muted),
),
TextButton.icon(
onPressed: requestingSentenceAnalysis ? null : _requestSentenceAnalysis,
onPressed: requestingSentenceAnalysis
? null
: _requestSentenceAnalysis,
icon: const Icon(Icons.refresh, size: 14),
label: const Text('重新解析', style: TextStyle(fontSize: 12)),
),
@@ -716,7 +731,10 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
child: OutlinedButton.icon(
onPressed: () =>
VoiceService.instance.speak(queryText, slow: true),
icon: const Icon(Icons.slow_motion_video_outlined, size: 18),
icon: const Icon(
Icons.slow_motion_video_outlined,
size: 18,
),
label: const Text('慢速朗读'),
),
),
@@ -730,7 +748,10 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
style: Theme.of(context).textTheme.headlineMedium,
),
if (entry!.ipa != null)
Text(entry!.ipa!, style: Theme.of(context).textTheme.bodyMedium),
Text(
entry!.ipa!,
style: Theme.of(context).textTheme.bodyMedium,
),
Text(entry!.meaning, style: const TextStyle(fontSize: 18)),
SectionCard(
tint: AppColors.softGreen,
@@ -740,7 +761,8 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
children: [
Expanded(
child: PrimaryButton(
label: _addedToReview.contains(entry!.word.toLowerCase())
label:
_addedToReview.contains(entry!.word.toLowerCase())
? '已在复习中'
: '加入复习',
onPressed: () {
@@ -753,9 +775,13 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
// Provide option to analyze further with AI if user wants deeper context
if (sentenceAnalysis == null)
OutlinedButton.icon(
onPressed: requestingSentenceAnalysis ? null : _requestSentenceAnalysis,
onPressed: requestingSentenceAnalysis
? null
: _requestSentenceAnalysis,
icon: const Icon(Icons.auto_awesome_outlined, size: 16),
label: Text(requestingSentenceAnalysis ? 'AI 解析中…' : '请求 AI 句型与深度解析'),
label: Text(
requestingSentenceAnalysis ? 'AI 解析中…' : '请求 AI 句型与深度解析',
),
),
],
@@ -769,7 +795,11 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
const SizedBox(height: 4),
Row(
children: [
Icon(Icons.layers_outlined, size: 18, color: AppColors.green),
Icon(
Icons.layers_outlined,
size: 18,
color: AppColors.green,
),
const SizedBox(width: 6),
Text(
'本地词典匹配到的短语 (${localPhrases.length})',
@@ -798,13 +828,22 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
),
),
IconButton(
icon: const Icon(Icons.volume_up_outlined, size: 18),
icon: const Icon(
Icons.volume_up_outlined,
size: 18,
),
onPressed: () =>
VoiceService.instance.speak(phraseItem.word),
),
const SizedBox(width: 4),
_addedToReview.contains(phraseItem.word.toLowerCase())
? Icon(Icons.check, size: 18, color: AppColors.green)
_addedToReview.contains(
phraseItem.word.toLowerCase(),
)
? Icon(
Icons.check,
size: 18,
color: AppColors.green,
)
: OutlinedButton(
style: OutlinedButton.styleFrom(
visualDensity: VisualDensity.compact,
@@ -815,11 +854,17 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
),
onPressed: () =>
_addVocabItemToReview(phraseItem),
child: const Text('+ 复习', style: TextStyle(fontSize: 12)),
child: const Text(
'+ 复习',
style: TextStyle(fontSize: 12),
),
),
],
),
Text(phraseItem.meaning, style: const TextStyle(fontSize: 14)),
Text(
phraseItem.meaning,
style: const TextStyle(fontSize: 14),
),
if (phraseItem.example.isNotEmpty)
Text(
'例:${phraseItem.example} (${phraseItem.exampleMeaning})',
@@ -903,7 +948,9 @@ class _LexiconLookupSheetState extends State<_LexiconLookupSheet> {
),
TextButton.icon(
onPressed: () {
widget.state.removeTemporaryDefinition(controller.text);
widget.state.removeTemporaryDefinition(
controller.text,
);
setState(() => temporaryDefinition = null);
},
icon: const Icon(Icons.delete_outline, size: 18),