Browse Source

feat(qr-scanner): Enhance QR scanner dialog with user feedback

- Add status messages for different scanning states (initializing, scanning, error)
- Add visual feedback with color-coded scanning frame and animations
- Add camera switch button for toggling between front/back cameras
- Add scanning instructions and tips in the footer
- Add retry button for error recovery
- Improve error handling and state management
- Add browser compatibility message for unsupported browsers

This change improves the user experience by providing clear visual feedback
and guidance during the QR code scanning process.
pull/132/head
Matt Raymer 3 days ago
parent
commit
f750ea5d10
  1. 129
      src/components/QRScanner/QRScannerDialog.vue

129
src/components/QRScanner/QRScannerDialog.vue

@ -37,6 +37,16 @@
v-if="useQRReader && !isNativePlatform"
class="relative aspect-square"
>
<!-- Status Message -->
<div
class="absolute top-0 left-0 right-0 bg-black bg-opacity-50 text-white text-center py-2 z-10"
>
<p v-if="isInitializing">Initializing camera...</p>
<p v-else-if="isScanning">Position QR code in the frame</p>
<p v-else-if="error" class="text-red-300">{{ error }}</p>
<p v-else>Ready to scan</p>
</div>
<qrcode-stream
:camera="options?.camera === 'front' ? 'user' : 'environment'"
@decode="onDecode"
@ -44,9 +54,44 @@
@detect="onDetect"
@error="onError"
/>
<!-- Scanning Frame -->
<div
class="absolute inset-0 border-2 border-blue-500 opacity-50 pointer-events-none"
class="absolute inset-0 border-2"
:class="{
'border-blue-500': !error && !isScanning,
'border-green-500 animate-pulse': isScanning,
'border-red-500': error
}"
style="opacity: 0.5; pointer-events: none;"
></div>
<!-- Camera Switch Button -->
<button
@click="toggleCamera"
class="absolute bottom-4 right-4 bg-white rounded-full p-2 shadow-lg"
title="Switch camera"
>
<svg
class="h-6 w-6 text-gray-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</button>
</div>
<div v-else class="text-center py-8">
<p class="text-gray-500">
@ -56,19 +101,43 @@
: "QR code scanning is not supported in this browser."
}}
</p>
<p v-if="!isNativePlatform" class="text-sm text-gray-400 mt-2">
Please ensure you're using a modern browser with camera access.
</p>
</div>
</div>
<!-- Footer -->
<div class="p-4 border-t border-gray-200">
<p v-if="error" class="text-red-500 text-sm mb-4">{{ error }}</p>
<div class="flex justify-end">
<button
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200"
@click="close"
>
Cancel
</button>
<div class="flex flex-col space-y-4">
<!-- Instructions -->
<div class="text-sm text-gray-600">
<ul class="list-disc list-inside space-y-1">
<li>Ensure the QR code is well-lit and in focus</li>
<li>Hold your device steady</li>
<li>The QR code should fit within the scanning frame</li>
</ul>
</div>
<!-- Error Message -->
<p v-if="error" class="text-red-500 text-sm">{{ error }}</p>
<!-- Actions -->
<div class="flex justify-end space-x-2">
<button
v-if="error"
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
@click="retryScanning"
>
Retry
</button>
<button
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200"
@click="close"
>
Cancel
</button>
</div>
</div>
</div>
</div>
@ -106,6 +175,10 @@ export default class QRScannerDialog extends Vue {
__IS_MOBILE__ ||
Capacitor.getPlatform() === "android" ||
Capacitor.getPlatform() === "ios";
isInitializing = true;
isScanning = false;
preferredCamera: 'user' | 'environment' = 'environment';
created() {
logger.log("QRScannerDialog platform detection:", {
@ -133,10 +206,13 @@ export default class QRScannerDialog extends Vue {
return;
}
this.isInitializing = true;
this.error = null;
logger.log("Initializing QR scanner...");
try {
await promise;
this.error = null;
this.isInitializing = false;
logger.log("QR scanner initialized successfully");
} catch (error) {
const wrappedError =
@ -150,10 +226,13 @@ export default class QRScannerDialog extends Vue {
stack: wrappedError.stack,
name: wrappedError.name,
});
} finally {
this.isInitializing = false;
}
}
onDetect(promise: Promise<any>): void {
this.isScanning = true;
logger.log("QR code detected, processing...");
promise
.then((result) => {
@ -164,6 +243,9 @@ export default class QRScannerDialog extends Vue {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
})
.finally(() => {
this.isScanning = false;
});
}
@ -188,6 +270,7 @@ export default class QRScannerDialog extends Vue {
}
onError(error: Error): void {
this.isScanning = false;
logger.error("QR scanner error:", {
error: error.message,
stack: error.stack,
@ -199,6 +282,16 @@ export default class QRScannerDialog extends Vue {
}
}
toggleCamera(): void {
this.preferredCamera = this.preferredCamera === 'user' ? 'environment' : 'user';
}
retryScanning(): void {
this.error = null;
this.isInitializing = true;
// The QR scanner component will automatically reinitialize
}
async close(): Promise<void> {
logger.log("Closing QR scanner dialog");
this.visible = false;
@ -219,4 +312,20 @@ export default class QRScannerDialog extends Vue {
width: 100%;
height: 100%;
}
@keyframes pulse {
0% {
opacity: 0.5;
}
50% {
opacity: 0.75;
}
100% {
opacity: 0.5;
}
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
</style>

Loading…
Cancel
Save