Browse Source

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.
pull/137/head
Matthew Raymer 2 weeks ago
parent
commit
835a270e65
  1. 2
      src/db-sql/migration.ts
  2. 6
      src/interfaces/absurd-sql.d.ts
  3. 1
      src/services/AbsurdSqlDatabaseService.ts
  4. 32
      src/services/QRScanner/CapacitorQRScanner.ts
  5. 7
      tsconfig.electron.json

2
src/db-sql/migration.ts

@ -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();

6
src/interfaces/absurd-sql.d.ts

@ -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>;
} }

1
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 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";

32
src/services/QRScanner/CapacitorQRScanner.ts

@ -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);
}
}
} }

7
tsconfig.electron.json

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