Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SF-2657 Chapter covered when audio is playing #2421

Merged
merged 7 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export class CheckingQuestionComponent extends SubscriptionDisposable implements
if (doc?.data == null) {
return;
}
if (doc.id !== this._questionDoc?.id) {
this.stopAudio();
}
this._questionDoc = doc;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ <h2 matSubheader>{{ t("filter_questions") }}</h2>
[matTooltipDisabled]="isAudioPlaying()"
(click)="toggleAudio()"
>
<mat-icon>{{ isAudioPlaying() ? "pause" : "play_circle_outline" }}</mat-icon>
<mat-icon>{{ isAudioPlaying() ? "stop" : "play_circle_outline" }}</mat-icon>
</button>
<app-font-size [class.hidden]="hideChapterText" (apply)="applyFontChange($event)"></app-font-size>
<app-share-button *ngIf="canShare" [defaultRole]="defaultShareRole"></app-share-button>
Expand All @@ -128,12 +128,14 @@ <h2 matSubheader>{{ t("filter_questions") }}</h2>
<!-- dir must be set on as-split-area because as-split is always set with dir=ltr -->
<as-split
direction="vertical"
unit="pixel"
[disabled]="!questionsList.activeQuestionDoc"
(dragEnd)="checkSliderPosition($event)"
[useTransition]="true"
[gutterSize]="showScriptureAudioPlayer ? 0 : 11"
>
<!-- Splitter area sizes are controlled programmatically. -->
<as-split-area [size]="100" [maxSize]="scriptureAreaMaxSize" [dir]="i18n.direction">
<as-split-area size="*" [dir]="i18n.direction" [visible]="!hideChapterText" [order]="1">
<div class="panel-content" #scripturePanelContainer>
<div class="scripture">
<app-checking-text
Expand All @@ -148,19 +150,14 @@ <h2 matSubheader>{{ t("filter_questions") }}</h2>
[projectDoc]="projectDoc"
></app-checking-text>
</div>
<div class="scripture-audio-player-wrapper" *ngIf="showScriptureAudioPlayer">
<app-checking-scripture-audio-player
[source]="chapterAudioSource"
[timing]="chapterTextAudioTiming"
[textDocId]="textDocId"
[canClose]="!hideChapterText"
(currentVerseChanged)="handleAudioTextRefChanged($event)"
(closed)="hideChapterAudio()"
></app-checking-scripture-audio-player>
</div>
</div>
</as-split-area>
<as-split-area [size]="0" [dir]="i18n.direction">
<as-split-area
[dir]="i18n.direction"
[order]="2"
[size]="hideChapterText ? '*' : 0"
[visible]="!showScriptureAudioPlayer || hideChapterText"
>
<div *ngIf="questionsList.activeQuestionDoc" id="answer-panel" #answerPanelContainer>
<app-checking-answers
#answersPanel
Expand All @@ -174,6 +171,24 @@ <h2 matSubheader>{{ t("filter_questions") }}</h2>
></app-checking-answers>
</div>
</as-split-area>
<as-split-area
lockSize="true"
[order]="hideChapterText ? 0 : 3"
[size]="130"
[visible]="showScriptureAudioPlayer"
>
<div class="scripture-audio-player-wrapper">
<app-checking-scripture-audio-player
*ngIf="showScriptureAudioPlayer"
[source]="chapterAudioSource"
[timing]="chapterTextAudioTiming"
[textDocId]="textDocId"
[canClose]="!hideChapterText"
(currentVerseChanged)="handleAudioTextRefChanged($event)"
(closed)="hideChapterAudio()"
></app-checking-scripture-audio-player>
</div>
</as-split-area>
</as-split>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,15 @@ describe('CheckingComponent', () => {
discardPeriodicTasks();
}));

it('should re-calculate scripture slide position on resize', fakeAsync(() => {
it('should re-calculate scripture slide position on drag end', fakeAsync(() => {
const testProject: SFProject = TestEnvironment.generateTestProject();
testProject.checkingConfig.hideCommunityCheckingText = true;
const env = new TestEnvironment({ user: CHECKER_USER, testProject });
env.waitForSliderUpdate();
(env.component as any)._scriptureAreaMaxSize = 1;
expect(env.component.scriptureAreaMaxSize).toEqual(1);
window.dispatchEvent(new Event('resize'));
expect(env.component.scriptureAreaMaxSize).toBeGreaterThan(1);
env.component.splitComponent?.setVisibleAreaSizes(['*', 1]);
expect(env.component.splitComponent?.getVisibleAreaSizes()[1]).toEqual(1);
env.component.checkSliderPosition({ sizes: ['*', 20] });
env.waitForSliderUpdate();
expect(env.component.splitComponent?.getVisibleAreaSizes()[1]).toBeGreaterThan(1);
flush();
discardPeriodicTasks();
}));
Expand Down Expand Up @@ -1500,7 +1500,7 @@ describe('CheckingComponent', () => {
env.simulateNewRemoteAnswer();
expect(env.showUnreadAnswersButton).toBeNull();
expect(env.answers.length).withContext('broken unrelated functionality').toEqual(0);
// Incoming remote answer should have been absorbed into the set of i
// Incoming remote answer should have been absorbed into the set of
// answers pending to show, since user was looking at the Add Answer button
expect(env.component.answersPanel!.answers.length).toEqual(2);
// We don't show the total answer count in the heading until the user adds her answer.
Expand Down Expand Up @@ -2108,27 +2108,17 @@ describe('CheckingComponent', () => {
discardPeriodicTasks();
}));

it('audio continues when changing question on the same chapter', fakeAsync(() => {
it('audio stops when changing question on the same chapter', fakeAsync(() => {
const env = new TestEnvironment({ user: ADMIN_USER, scriptureAudio: true });
const audio = env.mockScriptureAudioAndPlay();

env.selectQuestion(4);

verify(audio.stop()).never();
verify(audio.stop()).once();
expect(env.component).toBeDefined();
flush();
}));

it('audio continues when adding an answer on a question', fakeAsync(() => {
const env = new TestEnvironment({ user: ADMIN_USER, scriptureAudio: true });
const audio = env.mockScriptureAudioAndPlay();

env.answerQuestion('Answer while audio is playing');
env.waitForQuestionTimersToComplete();
verify(audio.stop()).never();
expect(env.component).toBeDefined();
}));

it('pauses chapter audio when adding a question', fakeAsync(() => {
const env = new TestEnvironment({ user: ADMIN_USER, scriptureAudio: true });
const audio = env.mockScriptureAudioAndPlay();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { SFProjectRole } from 'realtime-server/lib/esm/scriptureforge/models/sf-
import { getTextAudioId } from 'realtime-server/lib/esm/scriptureforge/models/text-audio';
import { TextInfo } from 'realtime-server/lib/esm/scriptureforge/models/text-info';
import { toVerseRef, VerseRefData } from 'realtime-server/lib/esm/scriptureforge/models/verse-ref-data';
import { asyncScheduler, combineLatest, fromEvent, merge, Subscription } from 'rxjs';
import { asyncScheduler, combineLatest, merge, Subscription } from 'rxjs';
import { distinctUntilChanged, filter, map, startWith, throttleTime } from 'rxjs/operators';
import { DataLoadingComponent } from 'xforge-common/data-loading-component';
import { FeatureFlagService } from 'xforge-common/feature-flags/feature-flag.service';
Expand Down Expand Up @@ -107,7 +107,7 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
projectUserConfigDoc?: SFProjectUserConfigDoc;
textDocId?: TextDocId;
totalVisibleQuestionsString: string = '0';
visibleQuestions?: QuestionDoc[];
visibleQuestions: Readonly<QuestionDoc[] | undefined>;
showScriptureAudioPlayer: boolean = false;
hasQuestionWithoutAudio: boolean = false;
isCreatingNewQuestion: boolean = false;
Expand Down Expand Up @@ -157,7 +157,6 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
private text?: TextInfo;
private isProjectAdmin: boolean = false;
private _scriptureAudioPlayer?: CheckingScriptureAudioPlayerComponent;
private _scriptureAreaMaxSize: number | null = null;

constructor(
private readonly activatedRoute: ActivatedRoute,
Expand Down Expand Up @@ -190,10 +189,6 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
return this.questionFilters.get(this.activeQuestionFilter);
}

get bookName(): string {
return this.text == null ? '' : this.i18n.localizeBook(this.text.bookNum);
}

get chapter(): number | undefined {
return this._chapter;
}
Expand All @@ -214,10 +209,6 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
}
}

get chapterStrings(): string[] {
return this.chapters.map(c => c.toString());
}

get isQuestionListPermanent(): boolean {
return this._isDrawerPermanent;
}
Expand Down Expand Up @@ -320,7 +311,7 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
return this._book;
}

private set book(book: number | undefined) {
set book(book: number | undefined) {
if (book === this.book) {
return;
}
Expand Down Expand Up @@ -440,42 +431,17 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
);
}

private get minAnswerPanelPercent(): number {
return Math.ceil((this.answerPanelElementMinimumHeight / this.splitContainerElementHeight) * 100);
}
private get fullyExpandedAnswerPanelPercent(): number {
return Math.ceil((this.fullyExpandedAnswerPanelHeight / this.splitContainerElementHeight) * 100);
}

private get splitContainerElementHeight(): number {
return this.splitContainerElement && this.splitComponent
? this.splitContainerElement.nativeElement.offsetHeight - this.splitComponent.gutterSize!
: 0;
}

private get contentPanelHeight(): number {
return this.scripturePanelContainerElement?.nativeElement.offsetHeight;
}

private get scriptureAudioPlayerAreaHeight(): number {
const scriptureAudioPlayerArea: Element | null = document.querySelector('.scripture-audio-player-wrapper');
return scriptureAudioPlayerArea == null ? 0 : scriptureAudioPlayerArea.getBoundingClientRect().height;
}

/** Percentage of the vertical space of the as-splitter, needed by just the Scripture audio player. */
private get scriptureAudioPlayerHeightPercent(): number {
return (this.scriptureAudioPlayerAreaHeight / this.splitContainerElementHeight) * 100;
}

/** maxSize for as-split-area for the Scripture+audio area. */
public get scriptureAreaMaxSize(): number | null {
return this._scriptureAreaMaxSize;
}

private set scriptureAreaMaxSize(value: number | null) {
this._scriptureAreaMaxSize = value;
}

ngOnInit(): void {
this.subscribe(
combineLatest([this.activatedRoute.params, this.activatedRoute.queryParams]),
Expand Down Expand Up @@ -760,15 +726,6 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
this.isScreenSmall = state.matches;
}
);

this.subscribe(fromEvent(window, 'resize'), () => {
if (this.hideChapterText) {
this.scriptureAreaMaxSize = this.scriptureAudioPlayerHeightPercent;
if (this.contentPanelHeight > this.scriptureAudioPlayerAreaHeight) {
this.calculateScriptureSliderPosition();
}
}
});
}

ngOnDestroy(): void {
Expand Down Expand Up @@ -926,7 +883,7 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A

checkSliderPosition(event: any): void {
if (event.hasOwnProperty('sizes')) {
if (event.sizes[1] < this.minAnswerPanelPercent) {
if (event.sizes[1] < this.answerPanelElementMinimumHeight) {
this.calculateScriptureSliderPosition();
}
}
Expand Down Expand Up @@ -965,6 +922,9 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
if (this.onlineStatusService.isOnline) {
questionDoc.updateAnswerFileCache();
}
if (!this.hideChapterText && !(actionSource?.isQuestionListChange ?? false)) {
this.toggleAudio(true);
}

// Ensure navigation is set to book/chapter of selected question
if (this.navigateQuestionChapter(questionDoc)) {
Expand Down Expand Up @@ -1083,9 +1043,17 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
this.showScriptureAudioPlayer = this.hideChapterText;
}

toggleAudio(): void {
this.showScriptureAudioPlayer = true;
this._scriptureAudioPlayer?.isPlaying ? this._scriptureAudioPlayer?.pause() : this._scriptureAudioPlayer?.play();
toggleAudio(forceStopAndHide: boolean = false): void {
this._scriptureAudioPlayer?.isPlaying || forceStopAndHide
? this._scriptureAudioPlayer?.stop()
: this._scriptureAudioPlayer?.play();

this.showScriptureAudioPlayer =
this.hideChapterText || this._scriptureAudioPlayer?.isPlaying
? true
: forceStopAndHide
? false
: !this.showScriptureAudioPlayer;
}

/**
Expand Down Expand Up @@ -1397,27 +1365,19 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A
// 100 ms is a speculative value for waiting for elements to be loaded and updated in the DOM.
const changeUpdateDelayMs: number = 100;
setTimeout(async () => {
if (this.splitComponent == null) {
if (this.splitComponent == null || this.hideChapterText) {
return;
}
if (this.hideChapterText) {
const answerPanelHeight = 100 - this.scriptureAudioPlayerHeightPercent;
this.splitComponent?.setVisibleAreaSizes([this.scriptureAudioPlayerHeightPercent, answerPanelHeight]);
this.scriptureAreaMaxSize = this.scriptureAudioPlayerHeightPercent;
let answerPanelHeight: number;
if (maximizeAnswerPanel) {
answerPanelHeight = Math.min(this.splitContainerElementHeight * 0.75, this.fullyExpandedAnswerPanelHeight);
} else {
let answerPanelHeight: number;
if (maximizeAnswerPanel) {
answerPanelHeight = this.fullyExpandedAnswerPanelPercent;
} else {
answerPanelHeight = this.minAnswerPanelPercent;
}

answerPanelHeight = Math.min(75, answerPanelHeight);
const scripturePanelHeight = 100 - answerPanelHeight;

this.splitComponent.setVisibleAreaSizes([scripturePanelHeight, answerPanelHeight]);
this.scriptureAreaMaxSize = null;
answerPanelHeight = this.answerPanelElementMinimumHeight;
}
this.splitComponent.setVisibleAreaSizes([
'*',
this.showScriptureAudioPlayer ? this.scriptureAudioPlayerAreaHeight : answerPanelHeight
]);
}, changeUpdateDelayMs);
}

Expand Down
Loading