feat: 完善芽说英语品牌Logo与图标配置,完成跨平台云端同步服务开发与自动化部署
This commit is contained in:
@@ -123,6 +123,8 @@ class _DialoguePageState extends State<DialoguePage> {
|
||||
bool waitingForReply = false;
|
||||
String? validationError;
|
||||
|
||||
final Set<int> _shownTranslations = <int>{};
|
||||
|
||||
LessonDialogue get script => widget.isLessonDialogue
|
||||
? dialogueBySegmentId(
|
||||
lessonById(widget.state.activeLessonId)
|
||||
@@ -136,8 +138,63 @@ class _DialoguePageState extends State<DialoguePage> {
|
||||
goal: '姓名、地点、状态或喜好,并反问',
|
||||
prompts: prompts,
|
||||
hints: hints,
|
||||
translations: translations,
|
||||
);
|
||||
|
||||
String? _resolveTranslationFor(String text, int currentStage) {
|
||||
final cleanText = text.trim();
|
||||
// 1. Check current script prompts
|
||||
for (var i = 0; i < script.prompts.length; i++) {
|
||||
if (script.prompts[i].trim().toLowerCase() == cleanText.toLowerCase()) {
|
||||
if (i < script.translations.length) {
|
||||
return script.translations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. Check standalone prompts
|
||||
for (var i = 0; i < prompts.length; i++) {
|
||||
if (prompts[i].trim().toLowerCase() == cleanText.toLowerCase()) {
|
||||
if (i < translations.length) {
|
||||
return translations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3. Check all lesson dialogues
|
||||
for (final dialogue in a0Dialogues.values) {
|
||||
for (var i = 0; i < dialogue.prompts.length; i++) {
|
||||
if (dialogue.prompts[i].trim().toLowerCase() == cleanText.toLowerCase()) {
|
||||
if (i < dialogue.translations.length) {
|
||||
return dialogue.translations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 4. Check all segment dialogues
|
||||
for (final dialogue in a0SegmentDialogues.values) {
|
||||
for (var i = 0; i < dialogue.prompts.length; i++) {
|
||||
if (dialogue.prompts[i].trim().toLowerCase() == cleanText.toLowerCase()) {
|
||||
if (i < dialogue.translations.length) {
|
||||
return dialogue.translations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 5. Common fallback phrases
|
||||
if (cleanText.toLowerCase().contains("wonderful") &&
|
||||
cleanText.toLowerCase().contains("nice meeting you")) {
|
||||
return "太棒了 — 很高兴认识你!";
|
||||
}
|
||||
if (cleanText.toLowerCase().contains("goodbye") ||
|
||||
cleanText.toLowerCase().contains("bye")) {
|
||||
return "再见!";
|
||||
}
|
||||
// 6. If stage index is within script.translations
|
||||
if (currentStage >= 0 && currentStage < script.translations.length) {
|
||||
return script.translations[currentStage];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static const prompts = [
|
||||
'Hi! My name is Mia. What’s your name?',
|
||||
'Nice to meet you. Where are you from?',
|
||||
@@ -170,9 +227,26 @@ class _DialoguePageState extends State<DialoguePage> {
|
||||
if (canRestore) {
|
||||
stage = draft.stage;
|
||||
usedHelp = draft.usedHelp;
|
||||
turns.addAll(draft.turns);
|
||||
for (var i = 0; i < draft.turns.length; i++) {
|
||||
final t = draft.turns[i];
|
||||
if (!t.isLearner && (t.translation == null || t.translation!.isEmpty)) {
|
||||
final trans = _resolveTranslationFor(t.text, i ~/ 2);
|
||||
turns.add(t.copyWith(translation: trans));
|
||||
} else {
|
||||
turns.add(t);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
turns.add(DialogueTurn(text: script.prompts.first, isLearner: false));
|
||||
final initialPrompt = script.prompts.first;
|
||||
final initialTranslation = script.translations.firstOrNull ??
|
||||
_resolveTranslationFor(initialPrompt, 0);
|
||||
turns.add(
|
||||
DialogueTurn(
|
||||
text: initialPrompt,
|
||||
isLearner: false,
|
||||
translation: initialTranslation,
|
||||
),
|
||||
);
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
@@ -245,11 +319,16 @@ class _DialoguePageState extends State<DialoguePage> {
|
||||
(nextStage < script.prompts.length
|
||||
? script.prompts[nextStage]
|
||||
: 'Wonderful — nice meeting you!');
|
||||
var replyTranslation = aiResponse?.translation;
|
||||
if (replyTranslation == null || replyTranslation.isEmpty) {
|
||||
replyTranslation = _resolveTranslationFor(replyText, nextStage);
|
||||
}
|
||||
setState(() {
|
||||
turns.add(
|
||||
DialogueTurn(
|
||||
text: replyText,
|
||||
isLearner: false,
|
||||
translation: replyTranslation,
|
||||
),
|
||||
);
|
||||
waitingForReply = false;
|
||||
@@ -360,6 +439,94 @@ class _DialoguePageState extends State<DialoguePage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _toggleTurnTranslation(int index) async {
|
||||
if (index < 0 || index >= turns.length) return;
|
||||
final turn = turns[index];
|
||||
if (turn.isLearner) return;
|
||||
|
||||
if (_shownTranslations.contains(index)) {
|
||||
setState(() {
|
||||
_shownTranslations.remove(index);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
String? trans = turn.translation;
|
||||
if (trans == null || trans.isEmpty) {
|
||||
trans = _resolveTranslationFor(turn.text, index ~/ 2);
|
||||
}
|
||||
|
||||
if (trans != null && trans.isNotEmpty) {
|
||||
setState(() {
|
||||
turns[index] = turn.copyWith(translation: trans);
|
||||
_shownTranslations.add(index);
|
||||
});
|
||||
_saveDraft();
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_shownTranslations.add(index);
|
||||
turns[index] = turn.copyWith(translation: "正在翻译…");
|
||||
});
|
||||
final fetched = await AiService.instance.temporaryDefinition(
|
||||
provider: widget.state.aiProvider,
|
||||
endpoint: widget.state.aiEndpoint,
|
||||
model: widget.state.aiModel,
|
||||
text: turn.text,
|
||||
);
|
||||
if (!mounted) return;
|
||||
final finalTrans =
|
||||
(fetched != null && fetched.isNotEmpty) ? fetched : "暂无该句中文翻译";
|
||||
setState(() {
|
||||
turns[index] = turn.copyWith(translation: finalTrans);
|
||||
});
|
||||
_saveDraft();
|
||||
}
|
||||
|
||||
Future<void> _showLatestAiTranslation() async {
|
||||
final latestAiIndex = turns.lastIndexWhere((turn) => !turn.isLearner);
|
||||
if (latestAiIndex == -1) return;
|
||||
final latestAi = turns[latestAiIndex];
|
||||
|
||||
String? trans = latestAi.translation;
|
||||
if (trans == null || trans.isEmpty) {
|
||||
trans = _resolveTranslationFor(latestAi.text, stage);
|
||||
}
|
||||
|
||||
if (trans != null && trans.isNotEmpty) {
|
||||
setState(() {
|
||||
usedHelp = true;
|
||||
hint = "对方说:$trans";
|
||||
_shownTranslations.add(latestAiIndex);
|
||||
turns[latestAiIndex] = latestAi.copyWith(translation: trans);
|
||||
});
|
||||
_saveDraft();
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
usedHelp = true;
|
||||
hint = "正在获取对方英文翻译…";
|
||||
_shownTranslations.add(latestAiIndex);
|
||||
});
|
||||
|
||||
final fetched = await AiService.instance.temporaryDefinition(
|
||||
provider: widget.state.aiProvider,
|
||||
endpoint: widget.state.aiEndpoint,
|
||||
model: widget.state.aiModel,
|
||||
text: latestAi.text,
|
||||
);
|
||||
if (!mounted) return;
|
||||
final finalTrans =
|
||||
(fetched != null && fetched.isNotEmpty) ? fetched : "暂无该句中文翻译";
|
||||
setState(() {
|
||||
hint = "对方说:$finalTrans";
|
||||
turns[latestAiIndex] = latestAi.copyWith(translation: finalTrans);
|
||||
});
|
||||
_saveDraft();
|
||||
}
|
||||
|
||||
Future<void> _playLatestAi({required bool slow}) async {
|
||||
final latest = turns.where((turn) => !turn.isLearner).lastOrNull;
|
||||
if (latest == null) return;
|
||||
@@ -553,28 +720,83 @@ class _DialoguePageState extends State<DialoguePage> {
|
||||
children: [
|
||||
LexiconText(turn.text, state: widget.state),
|
||||
if (!turn.isLearner) ...[
|
||||
const SizedBox(height: 6),
|
||||
GestureDetector(
|
||||
onTap: () => VoiceService.instance.speak(turn.text),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.volume_up_outlined,
|
||||
size: 16,
|
||||
color: AppColors.green,
|
||||
if (_shownTranslations.contains(index) &&
|
||||
turn.translation != null &&
|
||||
turn.translation!.isNotEmpty) ...[
|
||||
const SizedBox(height: 6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.6),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
turn.translation!,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF2D3748),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
"播放",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.green,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () =>
|
||||
VoiceService.instance.speak(turn.text),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.volume_up_outlined,
|
||||
size: 16,
|
||||
color: AppColors.green,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
"播放",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.green,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
GestureDetector(
|
||||
onTap: () => _toggleTurnTranslation(index),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_shownTranslations.contains(index)
|
||||
? Icons.translate
|
||||
: Icons.translate_outlined,
|
||||
size: 16,
|
||||
color: AppColors.green,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_shownTranslations.contains(index)
|
||||
? "隐藏翻译"
|
||||
: "翻译",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.green,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -594,20 +816,17 @@ class _DialoguePageState extends State<DialoguePage> {
|
||||
onTap: () {
|
||||
setState(() {
|
||||
usedHelp = true;
|
||||
hint = script.hints[stage];
|
||||
final hintIdx = stage < script.hints.length
|
||||
? stage
|
||||
: (script.hints.isNotEmpty ? script.hints.length - 1 : 0);
|
||||
hint = script.hints.isNotEmpty ? script.hints[hintIdx] : null;
|
||||
});
|
||||
_saveDraft();
|
||||
},
|
||||
),
|
||||
_AssistChip(
|
||||
label: '翻译',
|
||||
onTap: () {
|
||||
setState(() {
|
||||
usedHelp = true;
|
||||
hint = translations[stage];
|
||||
});
|
||||
_saveDraft();
|
||||
},
|
||||
onTap: _showLatestAiTranslation,
|
||||
),
|
||||
_AssistChip(
|
||||
label: '慢一点',
|
||||
|
||||
Reference in New Issue
Block a user