forked from trent_larson/crowd-funder-for-time-pwa
refactor(ContactQRScanShowView): simplify QR scanning and contact processing
- Remove complex camera lifecycle management code - Streamline QR code processing and contact handling - Simplify clipboard operations using native API - Remove redundant error handling and notification methods - Consolidate contact processing logic into focused methods
This commit is contained in:
317
src/components/QRScannerDialog.vue
Normal file
317
src/components/QRScannerDialog.vue
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="visible" class="dialog-overlay z-[60]">
|
||||||
|
<div class="dialog relative">
|
||||||
|
<div class="text-lg text-center font-light relative z-50">
|
||||||
|
<div
|
||||||
|
id="ViewHeading"
|
||||||
|
class="text-center font-bold absolute top-0 left-0 right-0 px-4 py-0.5 bg-black/50 text-white leading-none"
|
||||||
|
>
|
||||||
|
<span v-if="state.isProcessing">{{ state.processingStatus }}</span>
|
||||||
|
<span v-else>Scan QR Code</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="text-lg text-center px-2 py-0.5 leading-none absolute right-0 top-0 text-white cursor-pointer"
|
||||||
|
@click="close()"
|
||||||
|
>
|
||||||
|
<font-awesome icon="xmark" class="w-[1em]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<!-- Web QR Code Scanner -->
|
||||||
|
<qrcode-stream
|
||||||
|
v-if="useQRReader"
|
||||||
|
class="w-full max-w-lg mx-auto"
|
||||||
|
@detect="onScanDetect"
|
||||||
|
@error="onScanError"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Mobile Camera Button -->
|
||||||
|
<div v-else class="text-center mt-4">
|
||||||
|
<button
|
||||||
|
v-if="!state.isProcessing"
|
||||||
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||||
|
@click="openMobileCamera"
|
||||||
|
>
|
||||||
|
Open Camera
|
||||||
|
</button>
|
||||||
|
<div v-else class="text-center">
|
||||||
|
<font-awesome icon="spinner" class="fa-spin fa-3x" />
|
||||||
|
<p class="mt-2">{{ state.processingDetails }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="state.error" class="mt-4 text-red-500 text-center">
|
||||||
|
{{ state.error }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="mt-4 text-sm text-gray-600 text-center">
|
||||||
|
Position the QR code within the camera view to scan
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
import { QrcodeStream } from "vue-qrcode-reader";
|
||||||
|
import { reactive } from "vue";
|
||||||
|
import {
|
||||||
|
BarcodeScanner,
|
||||||
|
type ScanResult,
|
||||||
|
} from "@capacitor-mlkit/barcode-scanning";
|
||||||
|
import type { PluginListenerHandle } from "@capacitor/core";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
import { NotificationIface } from "../constants/app";
|
||||||
|
|
||||||
|
// Declare global constants
|
||||||
|
declare const __USE_QR_READER__: boolean;
|
||||||
|
declare const __IS_MOBILE__: boolean;
|
||||||
|
|
||||||
|
interface AppState {
|
||||||
|
isProcessing: boolean;
|
||||||
|
processingStatus: string;
|
||||||
|
processingDetails: string;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
QrcodeStream,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class QRScannerDialog extends Vue {
|
||||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
visible = false;
|
||||||
|
private scanListener: PluginListenerHandle | null = null;
|
||||||
|
private onScanCallback: ((result: string) => void) | null = null;
|
||||||
|
|
||||||
|
state = reactive<AppState>({
|
||||||
|
isProcessing: false,
|
||||||
|
processingStatus: "",
|
||||||
|
processingDetails: "",
|
||||||
|
error: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
async open(onScan: (result: string) => void) {
|
||||||
|
this.onScanCallback = onScan;
|
||||||
|
this.visible = true;
|
||||||
|
this.state.error = "";
|
||||||
|
|
||||||
|
if (!this.useQRReader) {
|
||||||
|
// Check if barcode scanning is supported on mobile
|
||||||
|
try {
|
||||||
|
const { supported } = await BarcodeScanner.isSupported();
|
||||||
|
if (!supported) {
|
||||||
|
this.showError("Barcode scanning is not supported on this device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.showError("Failed to check barcode scanner support");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.visible = false;
|
||||||
|
this.stopScanning().catch((error) => {
|
||||||
|
logger.error("Error stopping scanner during close:", error);
|
||||||
|
});
|
||||||
|
this.onScanCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async openMobileCamera() {
|
||||||
|
try {
|
||||||
|
this.state.isProcessing = true;
|
||||||
|
this.state.processingStatus = "Starting camera...";
|
||||||
|
logger.log("Opening mobile camera - starting initialization");
|
||||||
|
|
||||||
|
// Check current permission status
|
||||||
|
const status = await BarcodeScanner.checkPermissions();
|
||||||
|
logger.log("Camera permission status:", JSON.stringify(status, null, 2));
|
||||||
|
|
||||||
|
if (status.camera !== "granted") {
|
||||||
|
// Request permission if not granted
|
||||||
|
logger.log("Requesting camera permissions...");
|
||||||
|
const permissionStatus = await BarcodeScanner.requestPermissions();
|
||||||
|
if (permissionStatus.camera !== "granted") {
|
||||||
|
throw new Error("Camera permission not granted");
|
||||||
|
}
|
||||||
|
logger.log(
|
||||||
|
"Camera permission granted:",
|
||||||
|
JSON.stringify(permissionStatus, null, 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any existing listener first
|
||||||
|
await this.cleanupScanListener();
|
||||||
|
|
||||||
|
// Set up the listener before starting the scan
|
||||||
|
logger.log("Setting up new barcode listener");
|
||||||
|
this.scanListener = await BarcodeScanner.addListener(
|
||||||
|
"barcodesScanned",
|
||||||
|
async (result: ScanResult) => {
|
||||||
|
logger.log(
|
||||||
|
"Barcode scan result received:",
|
||||||
|
JSON.stringify(result, null, 2),
|
||||||
|
);
|
||||||
|
if (result.barcodes && result.barcodes.length > 0) {
|
||||||
|
this.state.processingDetails = `Processing QR code: ${result.barcodes[0].rawValue}`;
|
||||||
|
await this.handleScanResult(result.barcodes[0].rawValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
logger.log("Barcode listener setup complete");
|
||||||
|
|
||||||
|
// Start the scanner
|
||||||
|
logger.log("Starting barcode scanner");
|
||||||
|
await BarcodeScanner.startScan();
|
||||||
|
logger.log("Barcode scanner started successfully");
|
||||||
|
|
||||||
|
this.state.isProcessing = false;
|
||||||
|
this.state.processingStatus = "";
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to open camera:", error);
|
||||||
|
this.state.isProcessing = false;
|
||||||
|
this.state.processingStatus = "";
|
||||||
|
this.showError(
|
||||||
|
error instanceof Error ? error.message : "Failed to open camera",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cleanup on error
|
||||||
|
await this.cleanupScanListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleScanResult(rawValue: string) {
|
||||||
|
try {
|
||||||
|
this.state.isProcessing = true;
|
||||||
|
this.state.processingStatus = "Processing QR code...";
|
||||||
|
this.state.processingDetails = `Scanned value: ${rawValue}`;
|
||||||
|
|
||||||
|
// Stop scanning before processing
|
||||||
|
await this.stopScanning();
|
||||||
|
|
||||||
|
if (this.onScanCallback) {
|
||||||
|
await this.onScanCallback(rawValue);
|
||||||
|
// Only close after the callback is complete
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error handling scan result:", error);
|
||||||
|
this.showError("Failed to process scan result");
|
||||||
|
} finally {
|
||||||
|
this.state.isProcessing = false;
|
||||||
|
this.state.processingStatus = "";
|
||||||
|
this.state.processingDetails = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async cleanupScanListener() {
|
||||||
|
try {
|
||||||
|
if (this.scanListener) {
|
||||||
|
await this.scanListener.remove();
|
||||||
|
this.scanListener = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error removing scan listener:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopScanning() {
|
||||||
|
try {
|
||||||
|
await this.cleanupScanListener();
|
||||||
|
|
||||||
|
if (!this.useQRReader) {
|
||||||
|
// Stop the native scanner
|
||||||
|
await BarcodeScanner.stopScan();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error stopping scanner:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web QR reader handlers
|
||||||
|
async onScanDetect(result: { rawValue: string }) {
|
||||||
|
await this.handleScanResult(result.rawValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
onScanError(error: Error) {
|
||||||
|
logger.error("Scan error:", error);
|
||||||
|
this.showError("Failed to scan QR code");
|
||||||
|
}
|
||||||
|
|
||||||
|
private showError(message: string) {
|
||||||
|
this.state.error = message;
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: message,
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get useQRReader(): boolean {
|
||||||
|
return __USE_QR_READER__;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isMobile(): boolean {
|
||||||
|
return __IS_MOBILE__;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dialog-overlay {
|
||||||
|
z-index: 60;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background-color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 700px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 61;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add styles for the camera preview */
|
||||||
|
.qrcode-stream {
|
||||||
|
position: relative;
|
||||||
|
z-index: 62;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-stream video {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 70vh;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure mobile camera elements are also properly layered */
|
||||||
|
.barcode-scanner-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 62;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user