From 835a270e65344d156cdd576c22915d66fe4a96eb Mon Sep 17 00:00:00 2001 From: Matthew Raymer <matthew.raymer@anomalistdesign.com> Date: Wed, 28 May 2025 08:37:02 +0000 Subject: [PATCH] feat(qr-scanner): Add camera state management to CapacitorQRScanner - Add camera state tracking and listener management - Implement addCameraStateListener and removeCameraStateListener methods - Add state transitions during scanning operations - Improve error handling with state updates - Add proper type imports for CameraState and CameraStateListener This change ensures CapacitorQRScanner fully implements the QRScannerService interface and provides proper camera state feedback to consumers. Camera state is now tracked through the entire lifecycle of scanning operations, with appropriate state transitions for initialization, active scanning, errors, and cleanup. --- src/db-sql/migration.ts | 2 +- src/interfaces/absurd-sql.d.ts | 6 ++-- src/services/AbsurdSqlDatabaseService.ts | 1 - src/services/QRScanner/CapacitorQRScanner.ts | 32 +++++++++++++++++++- tsconfig.electron.json | 7 +++-- 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/db-sql/migration.ts b/src/db-sql/migration.ts index 50faeef5..25f345b4 100644 --- a/src/db-sql/migration.ts +++ b/src/db-sql/migration.ts @@ -129,7 +129,7 @@ export async function registerMigrations(): Promise<void> { export async function runMigrations( sqlExec: ( sql: string, - params?: SqlValue[], + params?: unknown[], ) => Promise<Array<QueryExecResult>>, ): Promise<void> { await registerMigrations(); diff --git a/src/interfaces/absurd-sql.d.ts b/src/interfaces/absurd-sql.d.ts index e113fe75..b1534f8d 100644 --- a/src/interfaces/absurd-sql.d.ts +++ b/src/interfaces/absurd-sql.d.ts @@ -13,10 +13,10 @@ declare module "@jlongster/sql.js" { } interface AbsurdSqlDatabase { - exec: (sql: string, params?: SqlValue[]) => Promise<QueryExecResult[]>; + exec: (sql: string, params?: unknown[]) => Promise<QueryExecResult[]>; run: ( sql: string, - params?: SqlValue[], + params?: unknown[], ) => Promise<{ changes: number; lastId?: number }>; } @@ -49,7 +49,7 @@ declare module "absurd-sql/dist/indexeddb-main-thread" { } export interface SQLiteDatabase { - exec: (sql: string, params?: SqlValue[]) => Promise<QueryExecResult[]>; + exec: (sql: string, params?: unknown[]) => Promise<QueryExecResult[]>; close: () => Promise<void>; } diff --git a/src/services/AbsurdSqlDatabaseService.ts b/src/services/AbsurdSqlDatabaseService.ts index 7c03bf64..99d42fec 100644 --- a/src/services/AbsurdSqlDatabaseService.ts +++ b/src/services/AbsurdSqlDatabaseService.ts @@ -1,4 +1,3 @@ -// Remove inline declarations since they are now in src/types/absurd-sql.d.ts import initSqlJs from "@jlongster/sql.js"; import { SQLiteFS } from "absurd-sql"; import IndexedDBBackend from "absurd-sql/dist/indexeddb-backend"; diff --git a/src/services/QRScanner/CapacitorQRScanner.ts b/src/services/QRScanner/CapacitorQRScanner.ts index dc5a1f0b..15cfad1b 100644 --- a/src/services/QRScanner/CapacitorQRScanner.ts +++ b/src/services/QRScanner/CapacitorQRScanner.ts @@ -4,7 +4,7 @@ import { StartScanOptions, LensFacing, } from "@capacitor-mlkit/barcode-scanning"; -import { QRScannerService, ScanListener, QRScannerOptions } from "./types"; +import { QRScannerService, ScanListener, QRScannerOptions, CameraStateListener, CameraState } from "./types"; import { logger } from "@/utils/logger"; export class CapacitorQRScanner implements QRScannerService { @@ -12,6 +12,9 @@ export class CapacitorQRScanner implements QRScannerService { private isScanning = false; private listenerHandles: Array<() => Promise<void>> = []; private cleanupPromise: Promise<void> | null = null; + private cameraStateListeners: Set<CameraStateListener> = new Set(); + private currentState: CameraState = "off"; + private currentStateMessage?: string; async checkPermissions(): Promise<boolean> { try { @@ -79,8 +82,11 @@ export class CapacitorQRScanner implements QRScannerService { } try { + this.updateCameraState("initializing", "Starting camera..."); + // Ensure we have permissions before starting if (!(await this.checkPermissions())) { + this.updateCameraState("permission_denied", "Camera permission denied"); logger.debug("Requesting camera permissions"); const granted = await this.requestPermissions(); if (!granted) { @@ -90,11 +96,13 @@ export class CapacitorQRScanner implements QRScannerService { // Check if scanning is supported if (!(await this.isSupported())) { + this.updateCameraState("error", "QR scanning not supported on this device"); throw new Error("QR scanning not supported on this device"); } logger.info("Starting MLKit scanner"); this.isScanning = true; + this.updateCameraState("active", "Camera is active"); const scanOptions: StartScanOptions = { formats: [BarcodeFormat.QrCode], @@ -126,6 +134,7 @@ export class CapacitorQRScanner implements QRScannerService { stack: wrappedError.stack, }); this.isScanning = false; + this.updateCameraState("error", wrappedError.message); await this.cleanup(); this.scanListener?.onError?.(wrappedError); throw wrappedError; @@ -140,6 +149,7 @@ export class CapacitorQRScanner implements QRScannerService { try { logger.debug("Stopping QR scanner"); + this.updateCameraState("off", "Camera stopped"); await BarcodeScanner.stopScan(); logger.info("QR scanner stopped successfully"); } catch (error) { @@ -149,6 +159,7 @@ export class CapacitorQRScanner implements QRScannerService { error: wrappedError.message, stack: wrappedError.stack, }); + this.updateCameraState("error", wrappedError.message); this.scanListener?.onError?.(wrappedError); throw wrappedError; } finally { @@ -207,4 +218,23 @@ export class CapacitorQRScanner implements QRScannerService { // No-op for native scanner callback(null); } + + addCameraStateListener(listener: CameraStateListener): void { + this.cameraStateListeners.add(listener); + // Immediately notify the new listener of current state + listener.onStateChange(this.currentState, this.currentStateMessage); + } + + removeCameraStateListener(listener: CameraStateListener): void { + this.cameraStateListeners.delete(listener); + } + + private updateCameraState(state: CameraState, message?: string): void { + this.currentState = state; + this.currentStateMessage = message; + // Notify all listeners of state change + for (const listener of this.cameraStateListeners) { + listener.onStateChange(state, message); + } + } } diff --git a/tsconfig.electron.json b/tsconfig.electron.json index a2277a1c..73aec38b 100644 --- a/tsconfig.electron.json +++ b/tsconfig.electron.json @@ -19,8 +19,9 @@ } }, "include": [ - "src/electron/**/*.ts", - "src/utils/**/*.ts", - "src/constants/**/*.ts" + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue" ] } \ No newline at end of file