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.
This commit is contained in:
Matthew Raymer
2025-05-28 08:37:02 +00:00
parent 13682a1930
commit 835a270e65
5 changed files with 39 additions and 9 deletions

View File

@@ -129,7 +129,7 @@ export async function registerMigrations(): Promise<void> {
export async function runMigrations( export async function runMigrations(
sqlExec: ( sqlExec: (
sql: string, sql: string,
params?: SqlValue[], params?: unknown[],
) => Promise<Array<QueryExecResult>>, ) => Promise<Array<QueryExecResult>>,
): Promise<void> { ): Promise<void> {
await registerMigrations(); await registerMigrations();

View File

@@ -13,10 +13,10 @@ declare module "@jlongster/sql.js" {
} }
interface AbsurdSqlDatabase { interface AbsurdSqlDatabase {
exec: (sql: string, params?: SqlValue[]) => Promise<QueryExecResult[]>; exec: (sql: string, params?: unknown[]) => Promise<QueryExecResult[]>;
run: ( run: (
sql: string, sql: string,
params?: SqlValue[], params?: unknown[],
) => Promise<{ changes: number; lastId?: number }>; ) => Promise<{ changes: number; lastId?: number }>;
} }
@@ -49,7 +49,7 @@ declare module "absurd-sql/dist/indexeddb-main-thread" {
} }
export interface SQLiteDatabase { export interface SQLiteDatabase {
exec: (sql: string, params?: SqlValue[]) => Promise<QueryExecResult[]>; exec: (sql: string, params?: unknown[]) => Promise<QueryExecResult[]>;
close: () => Promise<void>; close: () => Promise<void>;
} }

View File

@@ -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 initSqlJs from "@jlongster/sql.js";
import { SQLiteFS } from "absurd-sql"; import { SQLiteFS } from "absurd-sql";
import IndexedDBBackend from "absurd-sql/dist/indexeddb-backend"; import IndexedDBBackend from "absurd-sql/dist/indexeddb-backend";

View File

@@ -4,7 +4,7 @@ import {
StartScanOptions, StartScanOptions,
LensFacing, LensFacing,
} from "@capacitor-mlkit/barcode-scanning"; } from "@capacitor-mlkit/barcode-scanning";
import { QRScannerService, ScanListener, QRScannerOptions } from "./types"; import { QRScannerService, ScanListener, QRScannerOptions, CameraStateListener, CameraState } from "./types";
import { logger } from "@/utils/logger"; import { logger } from "@/utils/logger";
export class CapacitorQRScanner implements QRScannerService { export class CapacitorQRScanner implements QRScannerService {
@@ -12,6 +12,9 @@ export class CapacitorQRScanner implements QRScannerService {
private isScanning = false; private isScanning = false;
private listenerHandles: Array<() => Promise<void>> = []; private listenerHandles: Array<() => Promise<void>> = [];
private cleanupPromise: Promise<void> | null = null; private cleanupPromise: Promise<void> | null = null;
private cameraStateListeners: Set<CameraStateListener> = new Set();
private currentState: CameraState = "off";
private currentStateMessage?: string;
async checkPermissions(): Promise<boolean> { async checkPermissions(): Promise<boolean> {
try { try {
@@ -79,8 +82,11 @@ export class CapacitorQRScanner implements QRScannerService {
} }
try { try {
this.updateCameraState("initializing", "Starting camera...");
// Ensure we have permissions before starting // Ensure we have permissions before starting
if (!(await this.checkPermissions())) { if (!(await this.checkPermissions())) {
this.updateCameraState("permission_denied", "Camera permission denied");
logger.debug("Requesting camera permissions"); logger.debug("Requesting camera permissions");
const granted = await this.requestPermissions(); const granted = await this.requestPermissions();
if (!granted) { if (!granted) {
@@ -90,11 +96,13 @@ export class CapacitorQRScanner implements QRScannerService {
// Check if scanning is supported // Check if scanning is supported
if (!(await this.isSupported())) { if (!(await this.isSupported())) {
this.updateCameraState("error", "QR scanning not supported on this device");
throw new Error("QR scanning not supported on this device"); throw new Error("QR scanning not supported on this device");
} }
logger.info("Starting MLKit scanner"); logger.info("Starting MLKit scanner");
this.isScanning = true; this.isScanning = true;
this.updateCameraState("active", "Camera is active");
const scanOptions: StartScanOptions = { const scanOptions: StartScanOptions = {
formats: [BarcodeFormat.QrCode], formats: [BarcodeFormat.QrCode],
@@ -126,6 +134,7 @@ export class CapacitorQRScanner implements QRScannerService {
stack: wrappedError.stack, stack: wrappedError.stack,
}); });
this.isScanning = false; this.isScanning = false;
this.updateCameraState("error", wrappedError.message);
await this.cleanup(); await this.cleanup();
this.scanListener?.onError?.(wrappedError); this.scanListener?.onError?.(wrappedError);
throw wrappedError; throw wrappedError;
@@ -140,6 +149,7 @@ export class CapacitorQRScanner implements QRScannerService {
try { try {
logger.debug("Stopping QR scanner"); logger.debug("Stopping QR scanner");
this.updateCameraState("off", "Camera stopped");
await BarcodeScanner.stopScan(); await BarcodeScanner.stopScan();
logger.info("QR scanner stopped successfully"); logger.info("QR scanner stopped successfully");
} catch (error) { } catch (error) {
@@ -149,6 +159,7 @@ export class CapacitorQRScanner implements QRScannerService {
error: wrappedError.message, error: wrappedError.message,
stack: wrappedError.stack, stack: wrappedError.stack,
}); });
this.updateCameraState("error", wrappedError.message);
this.scanListener?.onError?.(wrappedError); this.scanListener?.onError?.(wrappedError);
throw wrappedError; throw wrappedError;
} finally { } finally {
@@ -207,4 +218,23 @@ export class CapacitorQRScanner implements QRScannerService {
// No-op for native scanner // No-op for native scanner
callback(null); 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);
}
}
} }

View File

@@ -19,8 +19,9 @@
} }
}, },
"include": [ "include": [
"src/electron/**/*.ts", "src/**/*.ts",
"src/utils/**/*.ts", "src/**/*.d.ts",
"src/constants/**/*.ts" "src/**/*.tsx",
"src/**/*.vue"
] ]
} }