Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@awesome-cordova-plugins/smartlook": "^6.14.0",
"@awesome-cordova-plugins/spinner-dialog": "^6.2.0",
"@capacitor-community/camera-preview": "^7.0.0",
"@capacitor-mlkit/document-scanner": "^7.3.0",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Mind it! These new packages need my blessing for security!

The new dependencies look stylish, but let me verify they're the latest and safest versions for my fans to use.

Also applies to: 54-54


🌐 Web query:

What are the latest versions of @capacitor-mlkit/document-scanner and @capacitor/filesystem packages on npm?

💡 Result:

As of August 7, 2025, the latest versions of the specified npm packages are:

  • @capacitor-mlkit/document-scanner: The most recent version is 7.2.1, released on April 7, 2025. (classic.yarnpkg.com)

  • @capacitor/filesystem: The latest stable version is 7.0.1, published 18 days ago. (npmjs.com)

Please note that @capacitor/filesystem has a nightly build version 7.0.2-nightly-20250527T150614.0, released 19 days ago. (npmjs.com)

For the most current information, you can visit the npm package pages:

Citations:


I will verify the version of @capacitor/filesystem in package.json now, mighty developer!


🏁 Script executed:

#!/bin/bash
rg "@capacitor/filesystem" -n package.json

Length of output: 84


Double-check published versions of new dependencies, Superstar!

I see two package versions that leap beyond the latest npm releases:

  • package.json:46 – “@capacitor-mlkit/document-scanner”: “^7.3.0”
    • Latest published is 7.2.1 (yarnpkg.com)
  • package.json:54 – “@capacitor/filesystem”: “^7.1.4”
    • Latest stable is 7.0.1 (npmjs.com)

Please confirm these versions actually exist on npm or adjust them down to the current stable releases. Rajini says: get this right at once!

🤖 Prompt for AI Agents
In package.json at line 46, the version "^7.3.0" for
"@capacitor-mlkit/document-scanner" is higher than the latest published version
7.2.1 on npm. Verify the actual published version on npm and if "^7.3.0" does
not exist, change it to "^7.2.1" or the latest stable version available.
Similarly, check the version for "@capacitor/filesystem" at line 54 and adjust
it to "^7.0.1" if "^7.1.4" is not published. Ensure all dependency versions
match the latest stable releases on npm before updating package.json.

"@capacitor/android": "^7.0.0",
"@capacitor/app": "^7.0.0",
"@capacitor/browser": "^7.0.0",
Expand Down
39 changes: 39 additions & 0 deletions src/app/core/services/mlkit-document-scanner.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Injectable } from '@angular/core';
import { DocumentScanner, ScanResult } from '@capacitor-mlkit/document-scanner';
import { from, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';

@Injectable({
providedIn: 'root',
})
export class MLKitDocumentScannerService {
scanDocument(): Observable<string> {
return from(DocumentScanner.scanDocument({ resultFormats: 'JPEG', galleryImportAllowed: true, pageLimit: 1 })).pipe(
switchMap((result: ScanResult) => {
if (result.scannedImages && result.scannedImages.length > 0) {
// MLKit returns file URLs, so we need to convert them to base64
return from(this.convertFileToBase64(result.scannedImages[0]));
}
throw new Error('No document scanned');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Kabali da! The error messages need translation power!

Per coding guidelines, services must use i18n keys. Error messages should follow the schema services.mlkitDocumentScanner.<messageKey>.

Consider injecting TranslocoService and using translation keys:

  • Line 17: services.mlkitDocumentScanner.noDocumentScanned
  • Line 32: services.mlkitDocumentScanner.fileConversionFailed
  • Line 36: services.mlkitDocumentScanner.fileConversionFailed

Also applies to: 32-32, 36-36

🤖 Prompt for AI Agents
In src/app/core/services/mlkit-document-scanner.service.ts at lines 17, 32, and
36, replace the hardcoded error messages with translation keys following the
pattern services.mlkitDocumentScanner.<messageKey>. Inject TranslocoService into
the service and use it to throw errors with translated messages for keys
services.mlkitDocumentScanner.noDocumentScanned and
services.mlkitDocumentScanner.fileConversionFailed accordingly.

}),
);
}

private async convertFileToBase64(fileUrl: string): Promise<string> {
try {
const response = await fetch(fileUrl);
const blob = await response.blob();
return new Promise<string>((resolve, reject): void => {
const reader = new FileReader();
reader.onloadend = (): void => {
const base64data = reader.result as string;
resolve(base64data);
};
reader.onerror = (): void => reject(new Error('Failed to convert file to base64'));
reader.readAsDataURL(blob);
});
} catch {
throw new Error('Failed to convert file to base64');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Mind it! The error details are vanishing like a villain in my movies!

The generic catch block swallows the original error information, making debugging difficult. Preserve the error details for better troubleshooting.

-    } catch {
-      throw new Error('Failed to convert file to base64');
+    } catch (error) {
+      throw new Error(`Failed to convert file to base64: ${error.message || 'Unknown error'}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch {
throw new Error('Failed to convert file to base64');
}
} catch (error) {
throw new Error(`Failed to convert file to base64: ${error.message || 'Unknown error'}`);
}
🤖 Prompt for AI Agents
In src/app/core/services/mlkit-document-scanner.service.ts around lines 35 to
37, the catch block throws a new error without preserving the original error
details, which hinders debugging. Modify the catch block to capture the caught
error object and include its message or stack trace when throwing the new error,
so the original error information is preserved and visible for troubleshooting.

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, EventEmitter, OnDestroy, OnInit, Input, AfterViewInit, ViewChild, Inject } from '@angular/core';
import { Component, EventEmitter, OnDestroy, OnInit, Input, AfterViewInit, ViewChild } from '@angular/core';
import { inject } from '@angular/core';
import { CameraPreviewPictureOptions } from '@capacitor-community/camera-preview';
import { ModalController, NavController, PopoverController } from '@ionic/angular';
import { ReceiptPreviewComponent } from './receipt-preview/receipt-preview.component';
Expand All @@ -23,6 +24,7 @@ import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-proper
import { AuthService } from 'src/app/core/services/auth.service';
import { CameraService } from 'src/app/core/services/camera.service';
import { CameraPreviewService } from 'src/app/core/services/camera-preview.service';
import { MLKitDocumentScannerService } from 'src/app/core/services/mlkit-document-scanner.service';
import { ReceiptPreviewData } from 'src/app/core/models/receipt-preview-data.model';
import { TranslocoService } from '@jsverse/transloco';

Expand Down Expand Up @@ -65,26 +67,43 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit

nativeSettings = NativeSettings;

constructor(
private modalController: ModalController,
private trackingService: TrackingService,
private router: Router,
private navController: NavController,
private transactionsOutboxService: TransactionsOutboxService,
private imagePicker: ImagePicker,
private networkService: NetworkService,
private popoverController: PopoverController,
private loaderService: LoaderService,
private orgService: OrgService,
private platformEmployeeSettingsService: PlatformEmployeeSettingsService,
private matSnackBar: MatSnackBar,
private snackbarProperties: SnackbarPropertiesService,
private authService: AuthService,
private cameraService: CameraService,
private cameraPreviewService: CameraPreviewService,
@Inject(DEVICE_PLATFORM) private devicePlatform: 'android' | 'ios' | 'web',
private translocoService: TranslocoService,
) {}
private readonly modalController = inject(ModalController);

private readonly trackingService = inject(TrackingService);

private readonly router = inject(Router);

private readonly navController = inject(NavController);

private readonly transactionsOutboxService = inject(TransactionsOutboxService);

private readonly imagePicker = inject(ImagePicker);

private readonly networkService = inject(NetworkService);

private readonly popoverController = inject(PopoverController);

private readonly loaderService = inject(LoaderService);

private readonly orgService = inject(OrgService);

private readonly platformEmployeeSettingsService = inject(PlatformEmployeeSettingsService);

private readonly matSnackBar = inject(MatSnackBar);

private readonly snackbarProperties = inject(SnackbarPropertiesService);

private readonly authService = inject(AuthService);

private readonly cameraService = inject(CameraService);

private readonly cameraPreviewService = inject(CameraPreviewService);

private readonly mlkitScanner = inject(MLKitDocumentScannerService);

private readonly devicePlatform = inject<'android' | 'ios' | 'web'>(DEVICE_PLATFORM);

private readonly translocoService = inject(TranslocoService);

setupNetworkWatcher(): void {
const networkWatcherEmitter = new EventEmitter<boolean>();
Expand Down Expand Up @@ -382,27 +401,32 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
this.trackingService.receiptLimitReached();
this.showLimitReachedPopover().subscribe(noop);
} else {
const cameraPreviewPictureOptions: CameraPreviewPictureOptions = {
quality: 70,
};

from(this.cameraPreviewService.capture(cameraPreviewPictureOptions)).subscribe((receiptData) => {
const base64PictureData = 'data:image/jpeg;base64,' + receiptData.value;
this.lastCapturedReceipt = base64PictureData;
if (!this.isBulkMode) {
this.stopCamera();
this.base64ImagesWithSource.push({
source: 'MOBILE_DASHCAM_SINGLE',
base64Image: base64PictureData,
});
this.onSingleCapture();
} else {
this.base64ImagesWithSource.push({
source: 'MOBILE_DASHCAM_BULK',
base64Image: base64PictureData,
this.mlkitScanner.scanDocument().subscribe({
next: (base64PictureData) => {
this.lastCapturedReceipt = base64PictureData;
if (!this.isBulkMode) {
this.stopCamera();
this.base64ImagesWithSource.push({
source: 'MOBILE_DASHCAM_SINGLE',
base64Image: base64PictureData,
});
this.onSingleCapture();
} else {
this.base64ImagesWithSource.push({
source: 'MOBILE_DASHCAM_BULK',
base64Image: base64PictureData,
});
this.onBulkCapture();
}
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
error: (error) => {
this.matSnackBar.openFromComponent(ToastMessageComponent, {
...this.snackbarProperties.setSnackbarProperties('failure', {
message: 'Camera not available',
}),
});
this.onBulkCapture();
}
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Thalaiva says: Don't waste the error information, use it wisely!

The error parameter should be utilized for logging to help with debugging. Also, the error message should use i18n keys.

-        // eslint-disable-next-line @typescript-eslint/no-unused-vars
-        error: (error) => {
+        error: (error) => {
+          console.error('Document scanning failed:', error);
           this.matSnackBar.openFromComponent(ToastMessageComponent, {
             ...this.snackbarProperties.setSnackbarProperties('failure', {
-              message: 'Camera not available',
+              message: this.translocoService.translate('captureReceipt.scanningFailed'),
             }),
           });
         },
🤖 Prompt for AI Agents
In src/app/shared/components/capture-receipt/capture-receipt.component.ts around
lines 422 to 429, the error parameter is currently unused and the error message
is hardcoded. Modify the error callback to log the error parameter for debugging
purposes and replace the hardcoded message with an i18n key to support
internationalization.

});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Rajini's rule: Always have a backup plan, like I have multiple entry styles!

Consider adding a check for MLKit scanner availability before use, with fallback to the previous camera preview method if unavailable.

Add a method to check scanner availability:

private async isScannerAvailable(): Promise<boolean> {
  try {
    // Check if MLKit scanner is available on the platform
    return await DocumentScanner.isSupported();
  } catch {
    return false;
  }
}

Then use it in onCaptureReceipt() with a fallback mechanism.

🤖 Prompt for AI Agents
In src/app/shared/components/capture-receipt/capture-receipt.component.ts
between lines 404 and 430, the code uses mlkitScanner.scanDocument() without
verifying if the scanner is available, risking runtime errors. Add a private
async method isScannerAvailable() that returns a boolean indicating scanner
support by calling DocumentScanner.isSupported() with error handling. Then, in
the onCaptureReceipt() method, call isScannerAvailable() before scanning; if the
scanner is unavailable, fallback to the previous camera preview method to ensure
functionality continuity.

}
}
Expand Down Expand Up @@ -515,14 +539,16 @@ export class CaptureReceiptComponent implements OnInit, OnDestroy, AfterViewInit
}

setUpAndStartCamera(): void {
this.cameraPreview.setUpAndStartCamera();
// MLKit scanner will handle camera setup and permissions
this.onCaptureReceipt();

if (this.transactionsOutboxService.singleCaptureCount === 3) {
this.showBulkModeToastMessage();
this.isBulkModePromptShown = true;
}
}

stopCamera(): void {
this.cameraPreview.stopCamera();
// MLKit scanner handles its own camera lifecycle
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Mind it! Empty methods need explanation, like my punch dialogues need context!

While MLKit handles camera lifecycle, consider documenting why this method is kept empty or remove it entirely if no longer needed.

   stopCamera(): void {
-    // MLKit scanner handles its own camera lifecycle
+    // MLKit scanner handles its own camera lifecycle
+    // Method retained for compatibility with existing call sites
+    // Can be removed after refactoring all references
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stopCamera(): void {
this.cameraPreview.stopCamera();
// MLKit scanner handles its own camera lifecycle
}
stopCamera(): void {
// MLKit scanner handles its own camera lifecycle
// Method retained for compatibility with existing call sites
// Can be removed after refactoring all references
}
🤖 Prompt for AI Agents
In src/app/shared/components/capture-receipt/capture-receipt.component.ts around
lines 551 to 553, the stopCamera method is empty without explanation. Add a
comment inside the method explaining that it is intentionally left empty because
MLKit manages the camera lifecycle, or if the method is no longer required,
remove it entirely to keep the code clean.

}
Loading