forked from jsnbuchanan/crowd-funder-for-time-pwa
feat: implement comprehensive camera state management
- Add CameraState type and CameraStateListener interface for standardized state handling - Implement camera state tracking in WebInlineQRScanner: - Add state management properties and methods - Update state transitions during camera operations - Add proper error state handling for different scenarios - Enhance QR scanner UI with improved state feedback: - Add color-coded status indicators - Implement state-specific messages and notifications - Add user-friendly error notifications for common issues - Improve error handling with specific states for: - Camera in use by another application - Permission denied - Camera not found - General errors This change improves the user experience by providing clear visual feedback about the camera's state and better error handling with actionable notifications.
This commit is contained in:
@@ -88,7 +88,7 @@
|
||||
class="absolute top-0 left-0 right-0 bg-black bg-opacity-50 text-white text-sm text-center py-2 z-10"
|
||||
>
|
||||
<div
|
||||
v-if="isInitializing"
|
||||
v-if="cameraState === 'initializing'"
|
||||
class="flex items-center justify-center space-x-2"
|
||||
>
|
||||
<svg
|
||||
@@ -112,10 +112,10 @@
|
||||
3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<span>{{ initializationStatus }}</span>
|
||||
<span>{{ cameraStateMessage || 'Initializing camera...' }}</span>
|
||||
</div>
|
||||
<p
|
||||
v-else-if="isScanning"
|
||||
v-else-if="cameraState === 'active'"
|
||||
class="flex items-center justify-center space-x-2"
|
||||
>
|
||||
<span
|
||||
@@ -125,8 +125,14 @@
|
||||
</p>
|
||||
<p v-else-if="error" class="text-red-400">Error: {{ error }}</p>
|
||||
<p v-else class="flex items-center justify-center space-x-2">
|
||||
<span class="inline-block w-2 h-2 bg-blue-500 rounded-full"></span>
|
||||
<span>Ready to scan</span>
|
||||
<span :class="{
|
||||
'inline-block w-2 h-2 rounded-full': true,
|
||||
'bg-green-500': cameraState === 'ready',
|
||||
'bg-yellow-500': cameraState === 'in_use',
|
||||
'bg-red-500': cameraState === 'error' || cameraState === 'permission_denied' || cameraState === 'not_found',
|
||||
'bg-blue-500': cameraState === 'off'
|
||||
}"></span>
|
||||
<span>{{ cameraStateMessage || 'Ready to scan' }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -202,6 +208,7 @@ import { retrieveAccountMetadata } from "../libs/util";
|
||||
import { Router } from "vue-router";
|
||||
import { logger } from "../utils/logger";
|
||||
import { QRScannerFactory } from "@/services/QRScanner/QRScannerFactory";
|
||||
import { CameraState } from "@/services/QRScanner/types";
|
||||
|
||||
interface QRScanResult {
|
||||
rawValue?: string;
|
||||
@@ -239,7 +246,8 @@ export default class ContactQRScanShow extends Vue {
|
||||
initializationStatus = "Initializing camera...";
|
||||
useQRReader = __USE_QR_READER__;
|
||||
preferredCamera: "user" | "environment" = "environment";
|
||||
cameraStatus = "Initializing";
|
||||
cameraState: CameraState = 'off';
|
||||
cameraStateMessage?: string;
|
||||
|
||||
ETHR_DID_PREFIX = ETHR_DID_PREFIX;
|
||||
|
||||
@@ -303,19 +311,70 @@ export default class ContactQRScanShow extends Vue {
|
||||
try {
|
||||
this.error = null;
|
||||
this.isScanning = true;
|
||||
this.isInitializing = true;
|
||||
this.initializationStatus = "Initializing camera...";
|
||||
this.lastScannedValue = "";
|
||||
this.lastScanTime = 0;
|
||||
|
||||
const scanner = QRScannerFactory.getInstance();
|
||||
|
||||
// Add camera state listener
|
||||
scanner.addCameraStateListener({
|
||||
onStateChange: (state, message) => {
|
||||
this.cameraState = state;
|
||||
this.cameraStateMessage = message;
|
||||
|
||||
// Update UI based on camera state
|
||||
switch (state) {
|
||||
case 'in_use':
|
||||
this.error = "Camera is in use by another application";
|
||||
this.isScanning = false;
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "Camera in Use",
|
||||
text: "Please close other applications using the camera and try again",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
break;
|
||||
case 'permission_denied':
|
||||
this.error = "Camera permission denied";
|
||||
this.isScanning = false;
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "Camera Access Required",
|
||||
text: "Please grant camera permission to scan QR codes",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
break;
|
||||
case 'not_found':
|
||||
this.error = "No camera found";
|
||||
this.isScanning = false;
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "No Camera",
|
||||
text: "No camera was found on this device",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
break;
|
||||
case 'error':
|
||||
this.error = this.cameraStateMessage || "Camera error";
|
||||
this.isScanning = false;
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Check if scanning is supported first
|
||||
if (!(await scanner.isSupported())) {
|
||||
this.error =
|
||||
"Camera access requires HTTPS. Please use a secure connection.";
|
||||
this.error = "Camera access requires HTTPS. Please use a secure connection.";
|
||||
this.isScanning = false;
|
||||
this.isInitializing = false;
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -328,40 +387,11 @@ export default class ContactQRScanShow extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check permissions first
|
||||
if (!(await scanner.checkPermissions())) {
|
||||
this.initializationStatus = "Requesting camera permission...";
|
||||
const granted = await scanner.requestPermissions();
|
||||
if (!granted) {
|
||||
this.error = "Camera permission denied";
|
||||
this.isScanning = false;
|
||||
this.isInitializing = false;
|
||||
// Show notification for better visibility
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "Camera Access Required",
|
||||
text: "Camera permission denied",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// For native platforms, use the scanner service
|
||||
scanner.addListener({
|
||||
onScan: this.onScanDetect,
|
||||
onError: this.onScanError,
|
||||
});
|
||||
|
||||
// Start scanning
|
||||
await scanner.startScan();
|
||||
} catch (error) {
|
||||
this.error = error instanceof Error ? error.message : String(error);
|
||||
this.isScanning = false;
|
||||
this.isInitializing = false;
|
||||
logger.error("Error starting scan:", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
@@ -828,12 +858,12 @@ export default class ContactQRScanShow extends Vue {
|
||||
try {
|
||||
await promise;
|
||||
this.isInitializing = false;
|
||||
this.cameraStatus = "Ready";
|
||||
this.cameraState = "ready";
|
||||
} catch (error) {
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
this.error = wrappedError.message;
|
||||
this.cameraStatus = "Error";
|
||||
this.cameraState = "error";
|
||||
this.isInitializing = false;
|
||||
logger.error("Error during QR scanner initialization:", {
|
||||
error: wrappedError.message,
|
||||
@@ -843,17 +873,17 @@ export default class ContactQRScanShow extends Vue {
|
||||
}
|
||||
|
||||
onCameraOn(): void {
|
||||
this.cameraStatus = "Active";
|
||||
this.cameraState = "active";
|
||||
this.isInitializing = false;
|
||||
}
|
||||
|
||||
onCameraOff(): void {
|
||||
this.cameraStatus = "Off";
|
||||
this.cameraState = "off";
|
||||
}
|
||||
|
||||
onDetect(result: unknown): void {
|
||||
this.isScanning = true;
|
||||
this.cameraStatus = "Detecting";
|
||||
this.cameraState = "detecting";
|
||||
try {
|
||||
let rawValue: string | undefined;
|
||||
if (
|
||||
@@ -874,7 +904,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
this.handleError(error);
|
||||
} finally {
|
||||
this.isScanning = false;
|
||||
this.cameraStatus = "Active";
|
||||
this.cameraState = "active";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,12 +927,12 @@ export default class ContactQRScanShow extends Vue {
|
||||
const wrappedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
this.error = wrappedError.message;
|
||||
this.cameraStatus = "Error";
|
||||
this.cameraState = "error";
|
||||
}
|
||||
|
||||
onError(error: Error): void {
|
||||
this.error = error.message;
|
||||
this.cameraStatus = "Error";
|
||||
this.cameraState = "error";
|
||||
logger.error("QR code scan error:", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
|
||||
Reference in New Issue
Block a user