|
|
@@ -122,6 +122,8 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
readonly wordPreviewPage = signal(1);
|
|
|
readonly wordPreviewTotal = signal(0);
|
|
|
readonly wordPreviewLoading = signal(false);
|
|
|
+ readonly historyWordRevealed = signal<Set<string>>(new Set());
|
|
|
+ readonly historyAudioWordId = signal('');
|
|
|
private historyPreviewWords: Word[] = [];
|
|
|
readonly selected = signal<Set<string>>(new Set());
|
|
|
readonly selectionRevealed = signal<Set<string>>(new Set());
|
|
|
@@ -557,6 +559,10 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
const previous = this.wordPreviewPage();
|
|
|
this.wordPreviewPage.set(target);
|
|
|
if (this.isHistoryWordView) {
|
|
|
+ this.audioRequest++;
|
|
|
+ this.platform.stopAudio();
|
|
|
+ this.audioPreparing.set(false);
|
|
|
+ this.historyAudioWordId.set('');
|
|
|
const start = (target - 1) * this.wordPreviewPageSize;
|
|
|
this.words.set(this.historyPreviewWords.slice(start, start + this.wordPreviewPageSize));
|
|
|
setTimeout(() => document.getElementById('readonly-word-title')?.scrollIntoView({ behavior: 'smooth', block: 'start' }));
|
|
|
@@ -785,23 +791,47 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
const request = ++this.audioRequest, focusedWord = this.current(), step = this.studyStep();
|
|
|
const active = () => request === this.audioRequest && this.current() === focusedWord && this.studyStep() === step;
|
|
|
this.audioPreparing.set(false);
|
|
|
+ if (this.isHistoryWordView) this.historyAudioWordId.set(word.id);
|
|
|
+ const playAudio = async (url: string, silentFailure = false) => {
|
|
|
+ const status = await (this.isHistoryWordView
|
|
|
+ ? this.platform.play(url, silentFailure, {
|
|
|
+ onEnded: () => {
|
|
|
+ if (!active()) return;
|
|
|
+ this.historyWordRevealed.update(current => new Set(current).add(word.id));
|
|
|
+ this.historyAudioWordId.set('');
|
|
|
+ },
|
|
|
+ onError: () => {
|
|
|
+ if (!active()) return;
|
|
|
+ this.historyAudioWordId.set('');
|
|
|
+ this.platform.toast('音频播放中断,请再点一次单词重试', 'error');
|
|
|
+ },
|
|
|
+ })
|
|
|
+ : this.platform.play(url, silentFailure));
|
|
|
+ if (status !== 'played' && active()) this.historyAudioWordId.set('');
|
|
|
+ return status;
|
|
|
+ };
|
|
|
const original = word.audio?.trim();
|
|
|
if (original) {
|
|
|
- const status = await this.platform.play(this.wordAudioUrl(original), true);
|
|
|
+ const status = await playAudio(this.wordAudioUrl(original), true);
|
|
|
if (status !== 'failed' || !active()) return;
|
|
|
} else this.platform.stopAudio();
|
|
|
this.audioPreparing.set(true);
|
|
|
+ if (this.isHistoryWordView) this.historyAudioWordId.set(word.id);
|
|
|
try {
|
|
|
const url = await this.wordAudio.ensure(word.wordId, !!original);
|
|
|
if (!active()) return;
|
|
|
word.audio = url;
|
|
|
if (original && this.wordAudioUrl(original) === this.wordAudioUrl(url)) {
|
|
|
+ this.historyAudioWordId.set('');
|
|
|
this.platform.toast('音频暂时无法播放,请检查网络后重试', 'error');
|
|
|
return;
|
|
|
}
|
|
|
- await this.platform.play(this.wordAudioUrl(url));
|
|
|
+ await playAudio(this.wordAudioUrl(url));
|
|
|
} catch (error) {
|
|
|
- if (active()) this.platform.toast(error instanceof Error ? error.message : '读音暂时不可用,请再点喇叭重试', 'error');
|
|
|
+ if (active()) {
|
|
|
+ this.historyAudioWordId.set('');
|
|
|
+ this.platform.toast(error instanceof Error ? error.message : '读音暂时不可用,请再点喇叭重试', 'error');
|
|
|
+ }
|
|
|
} finally {
|
|
|
if (request === this.audioRequest) this.audioPreparing.set(false);
|
|
|
}
|
|
|
@@ -2180,6 +2210,9 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
private clearLearningStorage(): void { this.draft().clear(); }
|
|
|
|
|
|
private resetPageState(): void {
|
|
|
+ this.audioRequest++;
|
|
|
+ this.platform.stopAudio();
|
|
|
+ this.audioPreparing.set(false);
|
|
|
this.outsideChoicePool = null;
|
|
|
this.choicesLoading.set(false);
|
|
|
this.choiceOptionsVersion++;
|
|
|
@@ -2189,6 +2222,8 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
this.selectedNodes.set(new Set());
|
|
|
this.words.set([]);
|
|
|
this.historyPreviewWords = [];
|
|
|
+ this.historyWordRevealed.set(new Set());
|
|
|
+ this.historyAudioWordId.set('');
|
|
|
this.historySources.set([]);
|
|
|
this.wordPreviewPage.set(1);
|
|
|
this.wordPreviewTotal.set(0);
|