Feature: front/back camera toggle

- Added to gifting and profile dialog camera for now. Toggle button is hidden in desktop.
- WIP: same feature for QR scanner camera.
- WIP: ability to specify default camera depending on where it's called.
This commit is contained in:
Jose Olarte III
2025-05-26 19:23:28 +08:00
parent 861ed9dd2d
commit cb82846593
3 changed files with 82 additions and 10 deletions

View File

@@ -119,12 +119,21 @@
playsinline
muted
></video>
<button
class="absolute bottom-4 left-1/2 -translate-x-1/2 bg-white text-slate-800 p-3 rounded-full text-2xl leading-none"
@click="capturePhoto"
>
<font-awesome icon="camera" class="w-[1em]" />
</button>
<div class="absolute bottom-4 inset-x-0 flex items-center justify-center gap-4">
<button
class="bg-white text-slate-800 p-3 rounded-full text-2xl leading-none"
@click="capturePhoto"
>
<font-awesome icon="camera" class="w-[1em]" />
</button>
<button
v-if="platformCapabilities.isMobile"
class="bg-white text-slate-800 p-3 rounded-full text-2xl leading-none"
@click="rotateCamera"
>
<font-awesome icon="rotate" class="w-[1em]" />
</button>
</div>
</div>
</div>
<div
@@ -303,6 +312,9 @@ export default class ImageMethodDialog extends Vue {
/** Camera stream reference */
private cameraStream: MediaStream | null = null;
/** Current camera facing mode */
private currentFacingMode: 'environment' | 'user' = 'environment';
private platformService = PlatformServiceFactory.getInstance();
URL = window.URL || window.webkitURL;
@@ -478,7 +490,7 @@ export default class ImageMethodDialog extends Vue {
await this.$nextTick();
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: "environment" },
video: { facingMode: this.currentFacingMode },
});
logger.debug("Camera access granted");
this.cameraStream = stream;
@@ -503,15 +515,17 @@ export default class ImageMethodDialog extends Vue {
let errorMessage =
error instanceof Error ? error.message : "Failed to access camera";
if (
error instanceof Error && (
error.name === "NotReadableError" ||
error.name === "TrackStartError"
) {
)) {
errorMessage =
"Camera is in use by another application. Please close any other apps or browser tabs using the camera and try again.";
} else if (
error instanceof Error && (
error.name === "NotAllowedError" ||
error.name === "PermissionDeniedError"
) {
)) {
errorMessage =
"Camera access was denied. Please allow camera access in your browser settings.";
}
@@ -579,6 +593,42 @@ export default class ImageMethodDialog extends Vue {
}
}
async rotateCamera() {
if (this.platformCapabilities.isNativeApp) {
try {
await this.platformService.rotateCamera();
// Take a new picture with the rotated camera
const result = await this.platformService.takePicture();
this.blob = result.blob;
this.fileName = result.fileName;
this.showRetry = true;
} catch (error) {
logger.error("Error rotating camera:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "Failed to rotate camera. Please try again.",
},
5000,
);
}
} else {
// For web browsers, toggle between front and back cameras
this.currentFacingMode = this.currentFacingMode === 'environment' ? 'user' : 'environment';
// Stop current stream
if (this.cameraStream) {
this.cameraStream.getTracks().forEach(track => track.stop());
this.cameraStream = null;
}
// Start new stream with updated facing mode
await this.startCameraPreview();
}
}
private createBlobURL(blob: Blob): string {
return URL.createObjectURL(blob);
}