Browse Source
- 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 methodsqrcode-capacitor
2 changed files with 513 additions and 708 deletions
@ -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
Loading…
Reference in new issue