Skip to content

Commit 8c3ac62

Browse files
authored
Merge pull request #1805 from numbersprotocol/feature-custom-camera-rebrand-ui-only
add pre-view page before uploading capture
2 parents 2b01908 + 24ecfc4 commit 8c3ac62

File tree

10 files changed

+248
-56
lines changed

10 files changed

+248
-56
lines changed

src/app/features/home/custom-camera/custom-camera-routing.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NgModule } from '@angular/core';
2-
import { Routes, RouterModule } from '@angular/router';
2+
import { RouterModule, Routes } from '@angular/router';
33

44
import { CustomCameraPage } from './custom-camera.page';
55

src/app/features/home/custom-camera/custom-camera.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SharedModule } from '../../../shared/shared.module';
66
import { CustomCameraPageRoutingModule } from './custom-camera-routing.module';
77
import { CustomCameraPage } from './custom-camera.page';
88
import { CustomCameraService } from './custom-camera.service';
9+
import { PrePublishModeComponent } from './pre-publish-mode/pre-publish-mode.component';
910

1011
@NgModule({
1112
imports: [
@@ -16,6 +17,6 @@ import { CustomCameraService } from './custom-camera.service';
1617
JoyrideModule.forChild(),
1718
],
1819
providers: [CustomCameraService],
19-
declarations: [CustomCameraPage],
20+
declarations: [CustomCameraPage, PrePublishModeComponent],
2021
})
2122
export class CustomCameraPageModule {}

src/app/features/home/custom-camera/custom-camera.page.html

Lines changed: 65 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,61 +3,74 @@
33
[style.--background]="'transparent'"
44
*transloco="let t"
55
>
6-
<div id="camera-flash-placeholder"></div>
7-
<div
8-
class="select-from-go-pro-camera-button"
9-
*ngIf="lastConnectedGoProDevice$ | ngrxPush"
10-
(click)="captureFromGoPro()"
11-
>
12-
GoPro
13-
<mat-icon> featured_video </mat-icon>
14-
</div>
15-
16-
<mat-icon
17-
class="close-camera-button"
18-
(click)="leaveCustomCamera()"
19-
joyrideStep="highlightCustomCameraCloseButton"
20-
[title]="t('userGuide.cameraUsageGuide')"
21-
[text]="t('userGuide.afterTakingPhotosOrRecordingVideosCloseAndGoBackHome')"
22-
>
23-
close
24-
</mat-icon>
25-
26-
<div class="camera-buttons-container">
27-
<mat-icon class="temporarily-hidden" id="gallery-icon">
28-
video_collection
29-
</mat-icon>
30-
31-
<circle-progress
32-
(click)="onPress()"
33-
[maxTime]="maxRecordTimeInMilliseconds"
34-
ngxLongPress2
35-
(onLongPress)="onLongPress()"
36-
(onLongPressing)="onLongPressing($event)"
37-
(onReleasePressing)="onReleasePressing()"
38-
[percent]="curRecordTimeInPercent"
39-
[radius]="38"
40-
[outerStrokeWidth]="6"
41-
[innerStrokeWidth]="4"
42-
[outerStrokeColor]="'#78C000'"
43-
[innerStrokeColor]="'#F2F2F2'"
44-
[showTitle]="false"
45-
[showUnits]="false"
46-
[showSubtitle]="false"
47-
[animation]="false"
48-
[animationDuration]="0"
49-
joyrideStep="highlightCustomCameraCaptureButton"
50-
[title]="t('userGuide.cameraUsageGuide')"
51-
[text]="t('userGuide.tapToTakeAPhotoAndLongPressToRecordVideo')"
52-
></circle-progress>
6+
<ng-container *ngIf="(mode$ | ngrxPush) === 'capture'">
7+
<div id="camera-flash-placeholder"></div>
8+
<div
9+
class="select-from-go-pro-camera-button"
10+
*ngIf="lastConnectedGoProDevice$ | ngrxPush"
11+
(click)="captureFromGoPro()"
12+
>
13+
GoPro
14+
<mat-icon> featured_video </mat-icon>
15+
</div>
5316

5417
<mat-icon
55-
(click)="flipCamera()"
56-
joyrideStep="highlightCustomCameraFlipButton"
18+
class="close-camera-button"
19+
(click)="leaveCustomCamera()"
20+
joyrideStep="highlightCustomCameraCloseButton"
5721
[title]="t('userGuide.cameraUsageGuide')"
58-
[text]="t('userGuide.flipTheCameraToSwitchBetweenFrontAndBackCameras')"
22+
[text]="
23+
t('userGuide.afterTakingPhotosOrRecordingVideosCloseAndGoBackHome')
24+
"
5925
>
60-
flip_camera_android
26+
close
6127
</mat-icon>
62-
</div>
28+
29+
<div class="camera-buttons-container">
30+
<mat-icon class="temporarily-hidden" id="gallery-icon">
31+
video_collection
32+
</mat-icon>
33+
34+
<circle-progress
35+
(click)="onPress()"
36+
[maxTime]="maxRecordTimeInMilliseconds"
37+
ngxLongPress2
38+
(onLongPress)="onLongPress()"
39+
(onLongPressing)="onLongPressing($event)"
40+
(onReleasePressing)="onReleasePressing()"
41+
[percent]="curRecordTimeInPercent"
42+
[radius]="38"
43+
[outerStrokeWidth]="6"
44+
[innerStrokeWidth]="4"
45+
[outerStrokeColor]="'#78C000'"
46+
[innerStrokeColor]="'#F2F2F2'"
47+
[showTitle]="false"
48+
[showUnits]="false"
49+
[showSubtitle]="false"
50+
[animation]="false"
51+
[animationDuration]="0"
52+
joyrideStep="highlightCustomCameraCaptureButton"
53+
[title]="t('userGuide.cameraUsageGuide')"
54+
[text]="t('userGuide.tapToTakeAPhotoAndLongPressToRecordVideo')"
55+
></circle-progress>
56+
57+
<mat-icon
58+
(click)="flipCamera()"
59+
joyrideStep="highlightCustomCameraFlipButton"
60+
[title]="t('userGuide.cameraUsageGuide')"
61+
[text]="t('userGuide.flipTheCameraToSwitchBetweenFrontAndBackCameras')"
62+
>
63+
flip_camera_android
64+
</mat-icon>
65+
</div>
66+
</ng-container>
67+
<ng-container *ngIf="(mode$ | ngrxPush) === 'pre-publish'">
68+
<app-pre-publish-mode
69+
[curCaptureFilePath]="curCaptureFilePath"
70+
[curCaptureMimeType]="curCaptureMimeType"
71+
[curCaptureSrc]="curCaptureSrc"
72+
(confirm)="confirmCurrentCapture()"
73+
(discard)="discardCurrentCapture()"
74+
></app-pre-publish-mode>
75+
</ng-container>
6376
</ion-content>

src/app/features/home/custom-camera/custom-camera.page.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import { Location } from '@angular/common';
33
import { Component, OnDestroy, OnInit } from '@angular/core';
44
import { Router } from '@angular/router';
5-
import { PluginListenerHandle } from '@capacitor/core';
5+
import { Capacitor, PluginListenerHandle } from '@capacitor/core';
66
import { UntilDestroy } from '@ngneat/until-destroy';
77
import { CaptureResult, PreviewCamera } from '@numbersprotocol/preview-camera';
8+
import { BehaviorSubject } from 'rxjs';
89
import { ErrorService } from '../../../shared/error/error.service';
910
import { UserGuideService } from '../../../shared/user-guide/user-guide.service';
1011
import { GoProBluetoothService } from '../../settings/go-pro/services/go-pro-bluetooth.service';
@@ -33,6 +34,13 @@ export class CustomCameraPage implements OnInit, OnDestroy {
3334

3435
curSessionCaptureMediaItems: CustomCameraMediaItem[] = [];
3536

37+
mode$ = new BehaviorSubject<'capture' | 'pre-publish'>('capture');
38+
39+
curCaptureFilePath?: string;
40+
curCaptureMimeType?: 'image/jpeg' | 'video/mp4';
41+
curCaptureType?: 'image' | 'video' = 'image';
42+
curCaptureSrc?: string;
43+
3644
readonly lastConnectedGoProDevice$ =
3745
this.goProBluetoothService.lastConnectedDevice$;
3846

@@ -85,7 +93,18 @@ export class CustomCameraPage implements OnInit, OnDestroy {
8593
if (data.errorMessage) {
8694
await this.errorService.toastError$(data.errorMessage).toPromise();
8795
} else if (data.filePath) {
88-
this.customCameraService.uploadToCapture(data.filePath, type);
96+
const filePath = data.filePath;
97+
98+
let mimeType: 'image/jpeg' | 'video/mp4' = 'image/jpeg';
99+
if (type === 'video') mimeType = 'video/mp4';
100+
101+
this.curCaptureFilePath = filePath;
102+
this.curCaptureMimeType = mimeType;
103+
this.curCaptureType = type;
104+
this.curCaptureSrc = Capacitor.convertFileSrc(filePath);
105+
this.mode$.next('pre-publish');
106+
107+
this.stopPreviewCamera();
89108
}
90109
}
91110

@@ -131,6 +150,23 @@ export class CustomCameraPage implements OnInit, OnDestroy {
131150
}
132151
}
133152

153+
discardCurrentCapture() {
154+
this.mode$.next('capture');
155+
this.startPreviewCamera();
156+
this.removeCurrentCapture();
157+
}
158+
159+
confirmCurrentCapture() {
160+
if (this.curCaptureFilePath && this.curCaptureType) {
161+
this.customCameraService.uploadToCapture(
162+
this.curCaptureFilePath,
163+
this.curCaptureType
164+
);
165+
this.removeCurrentCapture();
166+
}
167+
this.leaveCustomCamera();
168+
}
169+
134170
async leaveCustomCamera() {
135171
return this.location.back();
136172
}
@@ -142,6 +178,13 @@ export class CustomCameraPage implements OnInit, OnDestroy {
142178
});
143179
}
144180

181+
private removeCurrentCapture() {
182+
// TODO: remove file this.curCaptureFilePath to free space
183+
this.curCaptureFilePath = undefined;
184+
this.curCaptureMimeType = undefined;
185+
this.curCaptureSrc = undefined;
186+
}
187+
145188
// eslint-disable-next-line class-methods-use-this
146189
private debugOnlyPreventContextMenuFromLongPressContextMenu() {
147190
// Prevent showing context menu on long press
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<div class="action-buttons">
2+
<button (click)="onDiscard()" class="back-button" mat-mini-fab>
3+
<mat-icon>arrow_back</mat-icon>
4+
</button>
5+
6+
<button
7+
(click)="onConfirm()"
8+
class="confirm-button"
9+
mat-flat-button
10+
*transloco="let t"
11+
>
12+
{{ t('customCamera.confirmCapture') | uppercase }}
13+
</button>
14+
</div>
15+
16+
<div class="preview" *ngIf="curCaptureFilePath && curCaptureMimeType">
17+
<app-media [src]="curCaptureSrc" [mimeType]="curCaptureMimeType"></app-media>
18+
</div>
19+
20+
<div class="footer"></div>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
.action-buttons {
2+
position: absolute;
3+
top: 0;
4+
left: 0;
5+
right: 0;
6+
padding: calc(var(--ion-safe-area-top) + 4px) 16px;
7+
background-color: black;
8+
display: flex;
9+
flex-direction: row;
10+
justify-content: space-between;
11+
z-index: 100;
12+
}
13+
14+
.footer {
15+
position: absolute;
16+
bottom: 0;
17+
left: 0;
18+
right: 0;
19+
padding: calc(var(--ion-safe-area-bottom) + 4px) 16px;
20+
background-color: black;
21+
display: flex;
22+
flex-direction: row;
23+
justify-content: space-between;
24+
z-index: 100;
25+
height: 75px;
26+
}
27+
28+
.back-button {
29+
background: #ffffff40 !important; /* stylelint-disable-line declaration-no-important */
30+
color: white !important; /* stylelint-disable-line declaration-no-important */
31+
backdrop-filter: blur(4px);
32+
box-shadow: none;
33+
}
34+
35+
.confirm-button {
36+
background: #486cd9 !important; /* stylelint-disable-line declaration-no-important */
37+
color: white !important; /* stylelint-disable-line declaration-no-important */
38+
border-radius: 37px;
39+
height: 38px;
40+
}
41+
42+
.preview {
43+
height: 100%;
44+
overflow: auto;
45+
display: flex;
46+
flex-direction: column;
47+
justify-content: center;
48+
49+
app-media {
50+
width: 100%;
51+
object-fit: cover;
52+
object-position: center;
53+
}
54+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2+
import { SharedTestingModule } from '../../../../shared/shared-testing.module';
3+
4+
import { PrePublishModeComponent } from './pre-publish-mode.component';
5+
6+
describe('PrePublishModeComponent', () => {
7+
let component: PrePublishModeComponent;
8+
let fixture: ComponentFixture<PrePublishModeComponent>;
9+
10+
beforeEach(
11+
waitForAsync(() => {
12+
TestBed.configureTestingModule({
13+
declarations: [PrePublishModeComponent],
14+
imports: [SharedTestingModule],
15+
}).compileComponents();
16+
17+
fixture = TestBed.createComponent(PrePublishModeComponent);
18+
component = fixture.componentInstance;
19+
fixture.detectChanges();
20+
})
21+
);
22+
23+
it('should create', () => {
24+
expect(component).toBeTruthy();
25+
});
26+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Component, EventEmitter, Input, Output } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-pre-publish-mode',
5+
templateUrl: './pre-publish-mode.component.html',
6+
styleUrls: ['./pre-publish-mode.component.scss'],
7+
})
8+
export class PrePublishModeComponent {
9+
@Input()
10+
curCaptureFilePath?: string;
11+
12+
@Input()
13+
curCaptureMimeType?: 'image/jpeg' | 'video/mp4';
14+
15+
@Input()
16+
curCaptureSrc?: string;
17+
18+
@Output() discard: EventEmitter<any> = new EventEmitter();
19+
20+
@Output() confirm: EventEmitter<any> = new EventEmitter();
21+
22+
onDiscard() {
23+
this.discard.emit(null);
24+
}
25+
26+
onConfirm() {
27+
this.confirm.emit(null);
28+
}
29+
}

src/assets/i18n/en-us.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@
268268
"networkActionsAreUnavailable": "Network Actions are unavailable. Please try again later."
269269
}
270270
},
271+
"customCamera": {
272+
"confirmCapture": "Confirm"
273+
},
271274
"wallets": {
272275
"wallets": "Wallets",
273276
"pullToRefreshBalance": "Pull to refresh balance",

src/assets/i18n/zh-tw.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@
268268
"networkActionsAreUnavailable": "暫時無法使用 Network Action,請稍後再試。"
269269
}
270270
},
271+
"customCamera": {
272+
"confirmCapture": "確認"
273+
},
271274
"wallets": {
272275
"wallets": "錢包",
273276
"pullToRefreshBalance": "下拉以刷新餘額",

0 commit comments

Comments
 (0)