Skip to content

Commit

Permalink
SF-2946 Replace recorder with recorder dialog (#2830)
Browse files Browse the repository at this point in the history
  • Loading branch information
RaymondLuong3 authored Nov 1, 2024
1 parent f0694f5 commit f2154d1
Show file tree
Hide file tree
Showing 20 changed files with 110 additions and 540 deletions.
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
<ng-container *transloco="let t; read: 'attach_audio'">
<div class="add-audio">
@if (!textAndAudio?.input?.audioUrl && textAndAudio?.audioAttachment?.status !== "recording") {
@if (audioUrl == null && textAndAudio?.audioAttachment?.status !== "recording") {
<button mat-icon-button (click)="startRecording()" [matTooltip]="t('tooltip_record')">
<mat-icon>mic</mat-icon>
</button>
}
@if (textAndAudio?.audioAttachment?.status === "recording") {
<button mat-icon-button (click)="stopRecording()" [matTooltip]="t('tooltip_stop')">
<mat-icon class="stop-recording">stop</mat-icon>
</button>
}
</div>
@if (textAndAudio?.input?.audioUrl) {
@if (audioUrl != null) {
<div class="audio-added">
<app-single-button-audio-player
#audio
[source]="textAndAudio?.input?.audioUrl"
(click)="toggleAudio()"
theme="secondary"
>
<app-single-button-audio-player #audio [source]="audioUrl" (click)="toggleAudio()" theme="secondary">
<mat-icon>{{ audio.playing ? "stop" : "play_arrow" }}</mat-icon>
</app-single-button-audio-player>
<button mat-icon-button (click)="deleteAudio()" class="clear">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { CommonModule } from '@angular/common';
import { DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { MatDialogRef } from '@angular/material/dialog';
import { By } from '@angular/platform-browser';
import { instance, mock, verify, when } from 'ts-mockito';
import { of } from 'rxjs';
import { anything, instance, mock, verify, when } from 'ts-mockito';
import { DialogService } from 'xforge-common/dialog.service';
import { OnlineStatusService } from 'xforge-common/online-status.service';
import { TestOnlineStatusModule } from 'xforge-common/test-online-status.module';
import { TestOnlineStatusService } from 'xforge-common/test-online-status.service';
import { TestTranslocoModule, configureTestingModule } from 'xforge-common/test-utils';
import { configureTestingModule, TestTranslocoModule } from 'xforge-common/test-utils';
import { UICommonModule } from 'xforge-common/ui-common.module';
import { AudioRecorderDialogComponent } from '../../shared/audio-recorder-dialog/audio-recorder-dialog.component';
import { SharedModule } from '../../shared/shared.module';
import { CheckingAudioRecorderComponent } from '../checking/checking-audio-recorder/checking-audio-recorder.component';
import { TextAndAudioComponent } from '../text-and-audio/text-and-audio.component';
import { AttachAudioComponent } from './attach-audio.component';

const mockTextAndAudio = mock(TextAndAudioComponent);
const mockCheckingAudioRecorder = mock(CheckingAudioRecorderComponent);
const mockDialogService = mock(DialogService);

describe('AttachAudioComponent', () => {
let env: TestEnvironment;
Expand All @@ -23,57 +25,67 @@ describe('AttachAudioComponent', () => {
imports: [CommonModule, UICommonModule, SharedModule, TestTranslocoModule, TestOnlineStatusModule.forRoot()],
declarations: [AttachAudioComponent],
providers: [
{
provide: OnlineStatusService,
useClass: TestOnlineStatusService
}
{ provide: OnlineStatusService, useClass: TestOnlineStatusService },
{ provide: DialogService, useMock: mockDialogService }
]
}));

beforeEach(async () => {
env = new TestEnvironment();
});

it('should show mic when no audio attached', () => {
when(mockTextAndAudio.input).thenReturn({});
when(mockTextAndAudio.audioAttachment).thenReturn({ status: 'reset' });
it('should show mic when no audio attached', fakeAsync(() => {
when(env.mockTextAndAudio.input).thenReturn({});
when(env.mockTextAndAudio.audioAttachment).thenReturn({ status: 'reset' });
env.fixture.detectChanges();
expect(env.iconButton.nativeElement.textContent).toBe('mic');
env.iconButton.nativeElement.click();
tick();
env.fixture.detectChanges();
verify(mockCheckingAudioRecorder.startRecording()).once();
});
verify(mockDialogService.openMatDialog(AudioRecorderDialogComponent, anything())).once();
verify(env.mockTextAndAudio.setAudioAttachment(anything())).once();
}));

it('should show stop when recording', () => {
when(mockTextAndAudio.audioAttachment).thenReturn({ status: 'recording' });
env.component.textAndAudio = instance(mockTextAndAudio);
it('does not save when no audio recorded', fakeAsync(() => {
when(env.mockTextAndAudio.input).thenReturn({});
when(env.mockRecorderDialogRef.afterClosed()).thenReturn(of(undefined));
env.fixture.detectChanges();
expect(env.iconButton.nativeElement.textContent).toBe('stop');
env.iconButton.nativeElement.click();
tick();
env.fixture.detectChanges();
verify(mockCheckingAudioRecorder.stopRecording()).once();
});
verify(mockDialogService.openMatDialog(AudioRecorderDialogComponent, anything())).once();
verify(env.mockTextAndAudio.setAudioAttachment(anything())).never();
expect(env.iconButton.nativeElement.textContent).toBe('mic');
}));

it('should show clear when audio is attached', () => {
when(mockTextAndAudio.audioAttachment).thenReturn({ status: 'processed' });
when(mockTextAndAudio.input).thenReturn({ audioUrl: 'blob://audio' });
when(env.mockTextAndAudio.audioAttachment).thenReturn({ status: 'processed' });
when(env.mockTextAndAudio.input).thenReturn({ audioUrl: 'blob://audio' });
env.fixture.detectChanges();
expect(env.component.audioPlayer).not.toBeNull();
expect(env.iconButton.nativeElement.textContent).toBe('clear');
env.iconButton.nativeElement.click();
env.fixture.detectChanges();
verify(mockCheckingAudioRecorder.resetRecording()).once();
verify(env.mockTextAndAudio.resetAudio()).once();
});
});

class TestEnvironment {
component: AttachAudioComponent;
fixture: ComponentFixture<AttachAudioComponent>;
mockTextAndAudio = mock(TextAndAudioComponent);
mockRecorderDialogRef = mock(MatDialogRef<AudioRecorderDialogComponent>);

constructor() {
when(this.mockRecorderDialogRef.afterClosed()).thenReturn(
of({ audio: { url: 'blob://audio', status: 'processed' } })
);
when(mockDialogService.openMatDialog(AudioRecorderDialogComponent, anything())).thenReturn(
instance(this.mockRecorderDialogRef)
);
this.fixture = TestBed.createComponent(AttachAudioComponent);
this.component = this.fixture.componentInstance;
when(mockTextAndAudio.audioComponent).thenReturn(instance(mockCheckingAudioRecorder));
this.component.textAndAudio = instance(mockTextAndAudio);
this.component.textAndAudio = instance(this.mockTextAndAudio);
this.fixture.detectChanges();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { Component, Input, ViewChild } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
import { firstValueFrom } from 'rxjs';
import { DialogService } from 'xforge-common/dialog.service';
import {
AudioRecorderDialogComponent,
AudioRecorderDialogData,
AudioRecorderDialogResult
} from '../../shared/audio-recorder-dialog/audio-recorder-dialog.component';
import { SingleButtonAudioPlayerComponent } from '../checking/single-button-audio-player/single-button-audio-player.component';
import { TextAndAudioComponent } from '../text-and-audio/text-and-audio.component';

Expand All @@ -11,18 +19,29 @@ export class AttachAudioComponent {
@ViewChild(SingleButtonAudioPlayerComponent) audioPlayer?: SingleButtonAudioPlayerComponent;
@Input() textAndAudio?: TextAndAudioComponent;

constructor() {}
constructor(private readonly dialogService: DialogService) {}

startRecording(): void {
this.textAndAudio?.audioComponent?.startRecording();
get audioUrl(): string | undefined {
return this.textAndAudio?.input?.audioUrl;
}

stopRecording(): void {
this.textAndAudio?.audioComponent?.stopRecording();
async startRecording(): Promise<void> {
const config: AudioRecorderDialogData = { countdown: true };
const recorderDialogRef: MatDialogRef<AudioRecorderDialogComponent, AudioRecorderDialogResult> =
this.dialogService.openMatDialog<
AudioRecorderDialogComponent,
AudioRecorderDialogData,
AudioRecorderDialogResult
>(AudioRecorderDialogComponent, { data: config });
const result: AudioRecorderDialogResult | undefined = await firstValueFrom(recorderDialogRef.afterClosed());
if (result?.audio != null && this.textAndAudio != null) {
this.textAndAudio.setAudioAttachment(result.audio);
}
}

deleteAudio(): void {
this.textAndAudio?.audioComponent?.resetRecording();
if (this.textAndAudio == null) return;
this.textAndAudio.resetAudio();
}

toggleAudio(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { TextAudioDoc } from '../../core/models/text-audio-doc';
import { TextsByBookId } from '../../core/models/texts-by-book-id';
import { SFProjectService } from '../../core/sf-project.service';
import { CheckingModule } from '../checking.module';
import { AudioAttachment } from '../checking/checking-audio-recorder/checking-audio-recorder.component';
import { AudioAttachment } from '../checking/checking-audio-player/checking-audio-player.component';
import {
ChapterAudioDialogComponent,
ChapterAudioDialogData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { QuestionDoc } from '../../core/models/question-doc';
import { TextAudioDoc } from '../../core/models/text-audio-doc';
import { TextsByBookId } from '../../core/models/texts-by-book-id';
import { SFProjectService } from '../../core/sf-project.service';
import { AudioAttachment } from '../checking/checking-audio-recorder/checking-audio-recorder.component';
import { AudioAttachment } from '../checking/checking-audio-player/checking-audio-player.component';
import { SingleButtonAudioPlayerComponent } from '../checking/single-button-audio-player/single-button-audio-player.component';

const TIMING_FILE_EXTENSION_REGEX = /.(tsv|csv|txt)$/i;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { CheckingAnswersComponent } from './checking/checking-answers/checking-a
import { CheckingCommentFormComponent } from './checking/checking-answers/checking-comments/checking-comment-form/checking-comment-form.component';
import { CheckingCommentsComponent } from './checking/checking-answers/checking-comments/checking-comments.component';
import { CheckingAudioPlayerComponent } from './checking/checking-audio-player/checking-audio-player.component';
import { CheckingAudioRecorderComponent } from './checking/checking-audio-recorder/checking-audio-recorder.component';
import { CheckingQuestionsComponent } from './checking/checking-questions/checking-questions.component';
import { CheckingScriptureAudioPlayerComponent } from './checking/checking-scripture-audio-player/checking-scripture-audio-player.component';
import { CheckingTextComponent } from './checking/checking-text/checking-text.component';
Expand All @@ -43,8 +42,6 @@ import { TextAndAudioComponent } from './text-and-audio/text-and-audio.component
FontSizeComponent,
CheckingCommentsComponent,
CheckingCommentFormComponent,
CheckingAudioRecorderComponent,
CheckingAudioRecorderComponent,
CheckingAudioPlayerComponent,
AudioPlayerComponent,
CheckingScriptureAudioPlayerComponent,
Expand All @@ -62,6 +59,6 @@ import { TextAndAudioComponent } from './text-and-audio/text-and-audio.component
ngfModule,
TranslocoModule
],
exports: [CheckingAudioRecorderComponent, CheckingAudioPlayerComponent]
exports: [CheckingAudioPlayerComponent]
})
export class CheckingModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
import { QuestionDialogData } from '../../question-dialog/question-dialog.component';
import { QuestionDialogService } from '../../question-dialog/question-dialog.service';
import { TextAndAudioComponent } from '../../text-and-audio/text-and-audio.component';
import { AudioAttachment } from '../checking-audio-recorder/checking-audio-recorder.component';
import { AudioAttachment } from '../checking-audio-player/checking-audio-player.component';
import { CheckingTextComponent } from '../checking-text/checking-text.component';
import { CommentAction } from './checking-comments/checking-comments.component';
import { CheckingQuestionComponent } from './checking-question/checking-question.component';
Expand Down Expand Up @@ -526,10 +526,6 @@ export class CheckingAnswersComponent extends SubscriptionDisposable implements
async submit(): Promise<void> {
if (this.textAndAudio != null) {
this.textAndAudio.suppressErrors = false;
if (this.textAndAudio.audioComponent?.isRecording) {
await this.textAndAudio.audioComponent.stopRecording();
this.noticeService.show(translate('checking_answers.recording_automatically_stopped'));
}
}
if (!this.textAndAudio?.hasTextOrAudio()) {
this.textAndAudio?.text.setErrors({ invalid: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { AudioStatus } from '../../../shared/audio/audio-player';
import { AudioPlayerComponent } from '../../../shared/audio/audio-player/audio-player.component';
import { AudioTimePipe } from '../../../shared/audio/audio-time-pipe';
import { InfoComponent } from '../../../shared/info/info.component';
import { AudioAttachment } from '../checking-audio-recorder/checking-audio-recorder.component';
import { AudioAttachment } from '../checking-audio-player/checking-audio-player.component';
import { CheckingAudioPlayerComponent } from './checking-audio-player.component';

describe('CheckingAudioPlayerComponent', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { I18nService } from 'xforge-common/i18n.service';
import { SubscriptionDisposable } from 'xforge-common/subscription-disposable';
import { AudioPlayerComponent } from '../../../shared/audio/audio-player/audio-player.component';

export interface AudioAttachment {
status?: 'denied' | 'processed' | 'recording' | 'reset' | 'stopped' | 'uploaded';
url?: string;
fileName?: string;
blob?: Blob;
}

@Component({
selector: 'app-checking-audio-player',
templateUrl: './checking-audio-player.component.html',
Expand Down

This file was deleted.

Loading

0 comments on commit f2154d1

Please sign in to comment.