Skip to content

Conversation

@transphorm
Copy link
Member

@transphorm transphorm commented Sep 28, 2025

note: copies files from the mobile sdk but does not remove them

Summary by CodeRabbit

  • New Features
    • Adds an Android MRZ & QR scanning module with camera preview, real-time status overlays, OCR-based MRZ parsing, and QR detection available to the React Native app.
  • Refactor
    • Migrates scanner components to a new module/namespace and integrates them into the app package list.
  • Chores
    • Adds a new Gradle module, manifest, resources (layout/colors/strings), and required dependencies; links the module in project settings.
  • Removal
    • Removes the legacy QRCodeScanner module and package.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 28, 2025

Walkthrough

Adds a new Android library module "android-mrz-qr-scanner", integrates it into the app, registers a new React Native package/module and view managers, introduces camera/ML Kit processing, MRZ/OCR/QR utilities and UI, and removes the legacy QRCodeScanner native module and package.

Changes

Cohort / File(s) Summary
Module build & inclusion
app/android/android-mrz-qr-scanner/build.gradle, app/android/settings.gradle, app/android/app/build.gradle
Adds new Android library module, Gradle config, dependencies (ML Kit, CameraX, ZXing, Fotoapparat, jmrtd, RxJava, RN, etc.), and includes it in project settings and app deps.
RN package & module wiring
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerModule.java, .../MRZQRScannerPackage.java, app/android/app/src/main/java/com/proofofpassportapp/MainApplication.kt, app/android/app/src/main/java/com/proofofpassportapp/CameraActivityPackage.java
Adds MRZQRScannerModule and MRZQRScannerPackage, registers package in MainApplication, and updates view manager references to new package.
Legacy removal
app/android/app/src/main/java/com/proofofpassportapp/QRCodeScannerModule.java, .../QRCodeScannerPackage.java
Removes the legacy QRCodeScannerModule and QRCodeScannerPackage source files.
Camera core & UI
app/android/android-mrz-qr-scanner/src/main/java/.../fragments/CameraFragment.kt, .../ui/CameraMLKitFragment.kt, .../ui/QrCodeScannerFragment.kt, .../ui/PassportOCRViewManager.kt, .../ui/QRCodeScannerViewManager.kt, app/android/android-mrz-qr-scanner/src/main/res/layout/fragment_camera_mrz.xml, .../res/values/colors.xml, .../res/values/strings.xml
Adds abstract CameraFragment (Fotoapparat integration, pinch/zoom, permission handling), MLKit camera fragment, QR/passport view managers (package renames), layout for camera preview and status overlays, and resources (color, camera permission rationale).
ML Kit pipeline
.../mlkit/FrameMetadata.kt, .../mlkit/GraphicOverlay.kt, .../mlkit/VisionImageProcessor.kt, .../mlkit/VisionProcessorBase.kt, .../mlkit/OcrMrzDetectorProcessor.kt
Adds FrameMetadata model, GraphicOverlay view, VisionImageProcessor interface, VisionProcessorBase with throttling and listener callbacks, and an ML Kit TextRecognizer-based OcrMrzDetectorProcessor.
Utilities: image / MRZ / OCR / QR
.../utils/ImageUtil.kt, .../utils/MRZUtil.kt, .../utils/OcrUtils.kt, .../utils/QrCodeDetectorProcessor.kt
Adds ImageUtil (NV21/JPEG/WSQ/JP2 handling, bitmap extraction), MRZUtil (line cleaning/parsing helpers), OcrUtils (OCR->MRZ extraction with multiple regex patterns and Belgium handling), and updates QR detector imports/package.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor JS as React JS
  participant RN as MRZQRScannerModule
  participant PKG as MRZQRScannerPackage
  participant VM as ViewManager (OCR/QR)
  participant UI as Fragment (CameraMLKit / QrCodeScannerFragment)
  participant Cam as Camera (Fotoapparat/CameraX)
  participant Proc as VisionProcessorBase / OcrMrzDetectorProcessor
  participant ML as ML Kit TextRecognizer
  participant Util as OcrUtils / MRZUtil

  JS->>RN: request scan / mount view
  RN->>PKG: register module & view managers
  JS->>VM: mount OCR or QR view
  VM->>UI: create/start fragment
  UI->>Cam: start preview
  Cam-->>UI: frames (ByteBuffer/Image)
  UI->>Proc: process frame (InputImage / metadata)
  Proc->>ML: detect text
  ML-->>Proc: text result
  Proc->>Util: processOcr(text)
  Util-->>UI: MRZInfo or failure
  UI-->>JS: emit events/callbacks (MRZ found / not found)
Loading
sequenceDiagram
  autonumber
  participant Old as Legacy QRCodeScannerModule
  participant New as MRZQRScannerModule
  note over Old: Activity-based scanning + photo-picker handling\n(removed)
  note over New: Fragment + CameraX/Fotoapparat + ML Kit pipeline\n(new)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • remicolin

Poem

Lenses wake and fragments stream,
Text to bytes, a parser's dream.
MRZ lines tidy, regex hums,
Old modules fall — the new one comes.
Camera sings; the scanner runs. 📸✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately describes the primary change of consolidating QR and MRZ logic into a folder and aligns with the PR objectives; it avoids unnecessary detail and clearly communicates the feature intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch justin/feat-consolidate-qr-mrz-scanner-logic-into-module

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

Comment @coderabbitai help to get the list of available commands and usage tips.

@transphorm transphorm changed the title feat: consolidate qr / mrz logic into a folder [SELF-772] feat: consolidate qr / mrz logic into a folder Sep 28, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QrCodeScannerFragment.kt (1)

85-90: Keep the camera subscription pipeline alive after view recreation

Calling dispose() on the shared CompositeDisposable permanently terminates it, so every subsequent disposable.add(...) after the fragment view is recreated (rotation, back stack return, etc.) instantly disposes the work. The QR decoding pipeline stops firing after the first teardown. Clearing instead of disposing keeps the container reusable while releasing the accumulated subscriptions.

-        if (!disposable.isDisposed) {
-            disposable.dispose();
-        }
+        disposable.clear()
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/CameraMLKitFragment.kt (1)

99-101: Fix Kotlin call to isDisposed.

Line 99 currently calls disposable.isDisposed(); in Kotlin, RxJava’s isDisposed() accessor is exposed as the property isDisposed. Invoking it like a function fails to compile, so the module can’t even build. Switch to property access before merging.

-        if (!disposable.isDisposed()) {
+        if (!disposable.isDisposed) {
             disposable.dispose();
         }
🧹 Nitpick comments (1)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionProcessorBase.kt (1)

114-141: Consider deferring original bitmap creation until after successful detection to cut hot‑path CPU/GC.

Most frames won’t yield results; decoding/rotating on the critical path can drop FPS.

Option: pass null here and, inside onSuccess, lazily compute the bitmap only when the listener actually needs it. This keeps the critical path lightweight.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20fa5c5 and 3fbea81.

📒 Files selected for processing (27)
  • app/android/android-mrz-qr-scanner/build.gradle (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/AndroidManifest.xml (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerModule.java (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerPackage.java (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/fragments/CameraFragment.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/FrameMetadata.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/GraphicOverlay.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/OcrMrzDetectorProcessor.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionImageProcessor.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionProcessorBase.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/CameraMLKitFragment.kt (2 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/PassportOCRViewManager.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QRCodeScannerViewManager.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QrCodeScannerFragment.kt (2 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/ImageUtil.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/MRZUtil.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/OcrUtils.kt (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/QrCodeDetectorProcessor.kt (2 hunks)
  • app/android/android-mrz-qr-scanner/src/main/res/layout/fragment_camera_mrz.xml (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/res/values/colors.xml (1 hunks)
  • app/android/android-mrz-qr-scanner/src/main/res/values/strings.xml (1 hunks)
  • app/android/app/build.gradle (1 hunks)
  • app/android/app/src/main/java/com/proofofpassportapp/CameraActivityPackage.java (1 hunks)
  • app/android/app/src/main/java/com/proofofpassportapp/MainApplication.kt (2 hunks)
  • app/android/app/src/main/java/com/proofofpassportapp/QRCodeScannerModule.java (0 hunks)
  • app/android/app/src/main/java/com/proofofpassportapp/QRCodeScannerPackage.java (0 hunks)
  • app/android/settings.gradle (1 hunks)
💤 Files with no reviewable changes (2)
  • app/android/app/src/main/java/com/proofofpassportapp/QRCodeScannerModule.java
  • app/android/app/src/main/java/com/proofofpassportapp/QRCodeScannerPackage.java
🧰 Additional context used
📓 Path-based instructions (2)
app/android/**/*

⚙️ CodeRabbit configuration file

app/android/**/*: Review Android-specific code for:

  • Platform-specific implementations
  • Performance considerations
  • Security best practices for mobile

Files:

  • app/android/android-mrz-qr-scanner/src/main/res/values/strings.xml
  • app/android/android-mrz-qr-scanner/src/main/AndroidManifest.xml
  • app/android/settings.gradle
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/FrameMetadata.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QRCodeScannerViewManager.kt
  • app/android/app/build.gradle
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/fragments/CameraFragment.kt
  • app/android/android-mrz-qr-scanner/src/main/res/values/colors.xml
  • app/android/app/src/main/java/com/proofofpassportapp/MainApplication.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/GraphicOverlay.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QrCodeScannerFragment.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/PassportOCRViewManager.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionImageProcessor.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/OcrUtils.kt
  • app/android/android-mrz-qr-scanner/src/main/res/layout/fragment_camera_mrz.xml
  • app/android/app/src/main/java/com/proofofpassportapp/CameraActivityPackage.java
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/QrCodeDetectorProcessor.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerModule.java
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/CameraMLKitFragment.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/MRZUtil.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionProcessorBase.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerPackage.java
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/OcrMrzDetectorProcessor.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/ImageUtil.kt
  • app/android/android-mrz-qr-scanner/build.gradle
app/android/**/*.{kt,java}

📄 CodeRabbit inference engine (app/AGENTS.md)

Document complex native Android module changes in the PR

Files:

  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/FrameMetadata.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QRCodeScannerViewManager.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/fragments/CameraFragment.kt
  • app/android/app/src/main/java/com/proofofpassportapp/MainApplication.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/GraphicOverlay.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QrCodeScannerFragment.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/PassportOCRViewManager.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionImageProcessor.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/OcrUtils.kt
  • app/android/app/src/main/java/com/proofofpassportapp/CameraActivityPackage.java
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/QrCodeDetectorProcessor.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerModule.java
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/CameraMLKitFragment.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/MRZUtil.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionProcessorBase.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerPackage.java
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/OcrMrzDetectorProcessor.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/ImageUtil.kt
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/mobile-sdk-migration.mdc:0-0
Timestamp: 2025-08-24T18:54:04.809Z
Learning: Applies to packages/mobile-sdk-alpha/src/processing/** : Place MRZ processing helpers in packages/mobile-sdk-alpha/src/processing/
Learnt from: CR
PR: selfxyz/self#0
File: packages/mobile-sdk-alpha/AGENTS.md:0-0
Timestamp: 2025-08-29T15:31:15.924Z
Learning: Applies to packages/mobile-sdk-alpha/{**/*.test.{ts,tsx},**/__tests__/**/*.{ts,tsx}} : Verify extractMRZInfo() using published sample MRZ strings (e.g., ICAO examples)
Learnt from: CR
PR: selfxyz/self#0
File: app/AGENTS.md:0-0
Timestamp: 2025-09-22T11:10:57.879Z
Learning: Applies to app/android/**/*.{kt,java} : Document complex native Android module changes in the PR
📚 Learning: 2025-09-22T11:10:57.879Z
Learnt from: CR
PR: selfxyz/self#0
File: app/AGENTS.md:0-0
Timestamp: 2025-09-22T11:10:57.879Z
Learning: Applies to app/android/**/*.{kt,java} : Document complex native Android module changes in the PR

Applied to files:

  • app/android/android-mrz-qr-scanner/src/main/AndroidManifest.xml
  • app/android/settings.gradle
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QRCodeScannerViewManager.kt
  • app/android/app/build.gradle
  • app/android/app/src/main/java/com/proofofpassportapp/MainApplication.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QrCodeScannerFragment.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/PassportOCRViewManager.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerModule.java
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/CameraMLKitFragment.kt
  • app/android/android-mrz-qr-scanner/build.gradle
📚 Learning: 2025-08-24T18:54:04.809Z
Learnt from: CR
PR: selfxyz/self#0
File: .cursor/rules/mobile-sdk-migration.mdc:0-0
Timestamp: 2025-08-24T18:54:04.809Z
Learning: Applies to packages/mobile-sdk-alpha/src/processing/** : Place MRZ processing helpers in packages/mobile-sdk-alpha/src/processing/

Applied to files:

  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QRCodeScannerViewManager.kt
  • app/android/app/src/main/java/com/proofofpassportapp/MainApplication.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QrCodeScannerFragment.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/PassportOCRViewManager.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/OcrUtils.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/QrCodeDetectorProcessor.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerModule.java
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/CameraMLKitFragment.kt
  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerPackage.java
📚 Learning: 2025-08-24T21:22:29.459Z
Learnt from: transphorm
PR: selfxyz/self#943
File: app/android/app/src/main/java/com/proofofpassportapp/MainApplication.kt:17-17
Timestamp: 2025-08-24T21:22:29.459Z
Learning: OpenSourceMergedSoMapping from com.facebook.react.soloader.OpenSourceMergedSoMapping is available and functional in React Native 0.75.x. It can be safely imported and used for SoLoader initialization in MainApplication.kt without causing build failures.

Applied to files:

  • app/android/app/src/main/java/com/proofofpassportapp/MainApplication.kt
📚 Learning: 2025-08-29T15:31:15.924Z
Learnt from: CR
PR: selfxyz/self#0
File: packages/mobile-sdk-alpha/AGENTS.md:0-0
Timestamp: 2025-08-29T15:31:15.924Z
Learning: Applies to packages/mobile-sdk-alpha/{**/*.test.{ts,tsx},**/__tests__/**/*.{ts,tsx}} : Verify extractMRZInfo() using published sample MRZ strings (e.g., ICAO examples)

Applied to files:

  • app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerPackage.java
🧬 Code graph analysis (7)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/fragments/CameraFragment.kt (2)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/CameraMLKitFragment.kt (2)
  • onRequestPermissionsResult (191-192)
  • onRequestPermissionsResult (317-327)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QrCodeScannerFragment.kt (2)
  • onRequestPermissionsResult (165-166)
  • onRequestPermissionsResult (241-251)
app/android/app/src/main/java/com/proofofpassportapp/MainApplication.kt (1)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerPackage.java (1)
  • MRZQRScannerPackage (13-28)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/GraphicOverlay.kt (1)
packages/mobile-sdk-alpha/android/src/main/java/com/selfxyz/selfSDK/mlkit/GraphicOverlay.kt (1)
  • postInvalidate (112-114)
app/android/app/src/main/java/com/proofofpassportapp/CameraActivityPackage.java (1)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/PassportOCRViewManager.kt (1)
  • reactContext (20-163)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerModule.java (2)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/PassportOCRViewManager.kt (2)
  • reactContext (20-163)
  • getName (27-27)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QRCodeScannerViewManager.kt (2)
  • reactContext (18-161)
  • getName (25-25)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionProcessorBase.kt (4)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionImageProcessor.kt (6)
  • process (25-48)
  • process (28-28)
  • process (31-31)
  • process (34-34)
  • process (37-37)
  • process (40-40)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/CameraMLKitFragment.kt (1)
  • process (132-162)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QrCodeScannerFragment.kt (1)
  • process (108-136)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/OcrMrzDetectorProcessor.kt (1)
  • detectInImage (51-53)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/MRZQRScannerPackage.java (2)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/PassportOCRViewManager.kt (1)
  • reactContext (20-163)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/ui/QRCodeScannerViewManager.kt (1)
  • reactContext (18-161)
🪛 detekt (1.23.8)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/MRZUtil.kt

[warning] 33-34: Empty catch block detected. If the exception can be safely ignored, name the exception according to one of the exemptions as per the configuration of this rule.

(detekt.empty-blocks.EmptyCatchBlock)


[warning] 33-33: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionProcessorBase.kt

[warning] 270-270: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: android-build-test
  • GitHub Check: e2e-ios
  • GitHub Check: analyze-ios
  • GitHub Check: analyze-android
  • GitHub Check: build-deps
🔇 Additional comments (1)
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionProcessorBase.kt (1)

14-24: Please document the native module changes and integration steps in the PR description.

Add a brief design/migration note (module wiring, RN bridge surface, threading model, and any breaking changes).

As per coding guidelines.

Comment on lines +58 to +60
// React Native dependencies
implementation 'com.facebook.react:react-native:+'
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pin the React Native artifact version

com.facebook.react:react-native:+ lets Gradle pick an arbitrary version at build time. That breaks reproducibility, risks silently pulling insecure releases, and can desynchronize this library from the app’s pinned React Native runtime. Please align this dependency with the exact version the app already ships with.

-    implementation 'com.facebook.react:react-native:+'
+    implementation "com.facebook.react:react-native:${rootProject.ext.get(\"reactNativeAndroidVersion\")}"

(Replace the property with whatever constant the app module already uses for the pinned React Native coordinate.)

📝 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
// React Native dependencies
implementation 'com.facebook.react:react-native:+'
}
// React Native dependencies
implementation "com.facebook.react:react-native:${rootProject.ext.get(\"reactNativeAndroidVersion\")}"
}
🤖 Prompt for AI Agents
In app/android/android-mrz-qr-scanner/build.gradle around lines 58 to 60, the
dependency uses a floating version 'com.facebook.react:react-native:+', which
must be replaced with the app's pinned React Native coordinate; locate the
constant/property the main app module already uses for the pinned React Native
version (e.g. a project ext property or dependency constant), replace the '+'
with that exact coordinate or property reference so the library and app use the
same explicit version, then sync/clean the Gradle build to verify resolution.

Comment on lines +146 to +156
rotation = getRotation(context!!, initialLensPosition)
buildCamera(cameraPreview!!, initialLensPosition)

hasCameraPermission = hasCameraPermission()
if (hasCameraPermission) {
checkPermissions(requestedPermissions)
} else {
fotoapparat?.start()
configureZoom()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix inverted permission flag handling

hasCameraPermission() currently returns true when the camera permission is missing (!= PERMISSION_GRANTED), yet we set hasCameraPermission = true in the grant path and expect subclasses to treat the flag as “permission available”. After the next lifecycle pass, hasCameraPermission() resets the flag to false, so any subclass relying on this protected field will believe the camera is still blocked and can short-circuit MRZ/QR flows or show the wrong UI. Please flip the boolean logic so the helper returns true when access is granted, and update the onResume/onPause branches accordingly to avoid re-request loops and inconsistent state exposure.

@@
-        hasCameraPermission = hasCameraPermission()
-        if (hasCameraPermission) {
-            checkPermissions(requestedPermissions)
-        } else {
-            fotoapparat?.start()
-            configureZoom()
-        }
+        hasCameraPermission = hasCameraPermission()
+        if (!hasCameraPermission) {
+            checkPermissions(requestedPermissions)
+        } else {
+            fotoapparat?.start()
+            configureZoom()
+        }
@@
-        hasCameraPermission = hasCameraPermission()
-        if (!hasCameraPermission) {
-            fotoapparat?.stop()
-        }
+        hasCameraPermission = hasCameraPermission()
+        if (hasCameraPermission) {
+            fotoapparat?.stop()
+        }
@@
-    protected fun hasCameraPermission(): Boolean {
-        return ContextCompat.checkSelfPermission(context!!, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
-    }
+    protected fun hasCameraPermission(): Boolean {
+        return ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
+    }

Also applies to: 248-296

🤖 Prompt for AI Agents
In
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/fragments/CameraFragment.kt
around lines 146-156 (and similarly update 248-296), the permission check logic
is inverted: change hasCameraPermission() to return true when
ContextCompat.checkSelfPermission(...) == PERMISSION_GRANTED, flip assignments
so hasCameraPermission = hasCameraPermission() reflects granted state, and
update the branches so when permission is granted you start/configure the camera
(fotoapparat?.start(), configureZoom(), buildCamera/rotation) and when missing
you call checkPermissions(requestedPermissions) to request them; also adjust
onResume/onPause flows to not re-request or reset the protected flag incorrectly
after lifecycle transitions so subclasses see a consistent permission-available
boolean.

Comment on lines +126 to +137
var originalBitmap:Bitmap?=null
if(isOriginalImageReturned){
try {
originalBitmap = inputImage.bitmapInternal
if (originalBitmap == null) {
val wrap = ByteBuffer.wrap(frame.image)
originalBitmap = ImageUtil.rotateBitmap(ImageUtil.getBitmap(wrap, frameMetadata)!!, frameMetadata.rotation.toFloat())
}
}catch (e:Exception){
e.printStackTrace()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix double-rotation of original bitmap in Frame path.

ImageUtil.getBitmap(...) already applies rotation; rotating again skews orientation.

Apply this diff:

-                    originalBitmap = inputImage.bitmapInternal
-                    if (originalBitmap == null) {
-                        val wrap = ByteBuffer.wrap(frame.image)
-                        originalBitmap = ImageUtil.rotateBitmap(ImageUtil.getBitmap(wrap, frameMetadata)!!, frameMetadata.rotation.toFloat())
-                    }
+                    originalBitmap = inputImage.bitmapInternal
+                    if (originalBitmap == null) {
+                        val wrap = ByteBuffer.wrap(frame.image)
+                        originalBitmap = ImageUtil.getBitmap(wrap, frameMetadata)
+                    }
📝 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
var originalBitmap:Bitmap?=null
if(isOriginalImageReturned){
try {
originalBitmap = inputImage.bitmapInternal
if (originalBitmap == null) {
val wrap = ByteBuffer.wrap(frame.image)
originalBitmap = ImageUtil.rotateBitmap(ImageUtil.getBitmap(wrap, frameMetadata)!!, frameMetadata.rotation.toFloat())
}
}catch (e:Exception){
e.printStackTrace()
}
}
var originalBitmap: Bitmap? = null
if (isOriginalImageReturned) {
try {
originalBitmap = inputImage.bitmapInternal
if (originalBitmap == null) {
val wrap = ByteBuffer.wrap(frame.image)
originalBitmap = ImageUtil.getBitmap(wrap, frameMetadata)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
🤖 Prompt for AI Agents
In
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/mlkit/VisionProcessorBase.kt
around lines 126 to 137, the code double-rotates the original bitmap when
falling back to ByteBuffer: ImageUtil.getBitmap(...) already returns a rotated
bitmap so calling ImageUtil.rotateBitmap(...) again skews orientation; change
the fallback to assign the bitmap directly from ImageUtil.getBitmap(wrap,
frameMetadata)!! (keeping the null-safety checks and try/catch) instead of
rotating it a second time.

Comment on lines +25 to +36
import org.jmrtd.lds.ImageInfo.WSQ_MIME_TYPE
import kotlin.experimental.and

object ImageUtil {

private val TAG = ImageUtil::class.java.simpleName

var JPEG_MIME_TYPE = "image/jpeg"
var JPEG2000_MIME_TYPE = "image/jp2" // Not decoded specially in this module
var JPEG2000_ALT_MIME_TYPE = "image/jpeg2000" // Not decoded specially in this module
var WSQ_MIME_TYPE = "image/x-wsq"

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Blocking: WSQ_MIME_TYPE redeclared; compile error risk.

You import org.jmrtd.lds.ImageInfo.WSQ_MIME_TYPE and also declare WSQ_MIME_TYPE locally; Kotlin will flag a redeclaration/conflicting import. Make one source of truth and make constants immutable.

Apply this diff:

-import org.jmrtd.lds.ImageInfo.WSQ_MIME_TYPE
+// import removed: avoid name clash with local constant

-    var JPEG_MIME_TYPE = "image/jpeg"
-    var JPEG2000_MIME_TYPE = "image/jp2" // Not decoded specially in this module
-    var JPEG2000_ALT_MIME_TYPE = "image/jpeg2000" // Not decoded specially in this module
-    var WSQ_MIME_TYPE = "image/x-wsq"
+    const val JPEG_MIME_TYPE = "image/jpeg"
+    const val JPEG2000_MIME_TYPE = "image/jp2" // Not decoded specially in this module
+    const val JPEG2000_ALT_MIME_TYPE = "image/jpeg2000" // Not decoded specially in this module
+    const val WSQ_MIME_TYPE = "image/x-wsq"

Also applies to: 96-101

🤖 Prompt for AI Agents
In
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/ImageUtil.kt
around lines 25-36 (and similarly lines 96-101), you have a conflicting import
of org.jmrtd.lds.ImageInfo.WSQ_MIME_TYPE and a local WSQ_MIME_TYPE declaration;
remove the duplicate by either deleting the local WSQ_MIME_TYPE or removing the
import and rename the local constant, and make all MIME constants immutable (use
val) so there's a single source of truth and no redeclaration compile error.

Comment on lines +39 to +45
if (image.format == ImageFormat.JPEG) {
val planes = image.planes
val buffer = planes[0].buffer
data = ByteArray(buffer.capacity())
buffer.get(data)
return data
} else if (image.format == ImageFormat.YUV_420_888) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

JPEG branch may over-read; use remaining() instead of capacity().

buffer.capacity() isn’t the readable length; use remaining() to avoid truncation or garbage bytes.

Apply this diff:

-            val buffer = planes[0].buffer
-            data = ByteArray(buffer.capacity())
-            buffer.get(data)
+            val buffer = planes[0].buffer
+            data = ByteArray(buffer.remaining())
+            buffer.get(data)
📝 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
if (image.format == ImageFormat.JPEG) {
val planes = image.planes
val buffer = planes[0].buffer
data = ByteArray(buffer.capacity())
buffer.get(data)
return data
} else if (image.format == ImageFormat.YUV_420_888) {
if (image.format == ImageFormat.JPEG) {
val planes = image.planes
val buffer = planes[0].buffer
data = ByteArray(buffer.remaining())
buffer.get(data)
return data
} else if (image.format == ImageFormat.YUV_420_888) {
//
}
🤖 Prompt for AI Agents
In
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/ImageUtil.kt
around lines 39 to 45, the JPEG branch uses buffer.capacity() to size the byte
array which can cause over-read or include invalid bytes; change allocation and
read to use buffer.remaining() (the number of readable bytes) so you allocate
ByteArray(buffer.remaining()) and then buffer.get(data) to safely read only the
available bytes.

Comment on lines +53 to +71
fun YUV_420_888toNV21(image: Image): ByteArray {
val nv21: ByteArray
val yBuffer = image.planes[0].buffer
val uBuffer = image.planes[1].buffer
val vBuffer = image.planes[2].buffer

val ySize = yBuffer.remaining()
val uSize = uBuffer.remaining()
val vSize = vBuffer.remaining()

nv21 = ByteArray(ySize + uSize + vSize)

//U and V are swapped
yBuffer.get(nv21, 0, ySize)
vBuffer.get(nv21, ySize, vSize)
uBuffer.get(nv21, ySize + vSize, uSize)

return nv21
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

NV21 conversion is incorrect (strides/pixel strides ignored); produces corrupted frames on many devices.

Current code concatenates planes and mis-sizes the buffer; you must respect rowStride/pixelStride and interleave VU for NV21.

Replace the function with a stride-aware implementation:

-    fun YUV_420_888toNV21(image: Image): ByteArray {
-        val nv21: ByteArray
-        val yBuffer = image.planes[0].buffer
-        val uBuffer = image.planes[1].buffer
-        val vBuffer = image.planes[2].buffer
-
-        val ySize = yBuffer.remaining()
-        val uSize = uBuffer.remaining()
-        val vSize = vBuffer.remaining()
-
-        nv21 = ByteArray(ySize + uSize + vSize)
-
-        //U and V are swapped
-        yBuffer.get(nv21, 0, ySize)
-        vBuffer.get(nv21, ySize, vSize)
-        uBuffer.get(nv21, ySize + vSize, uSize)
-
-        return nv21
-    }
+    fun YUV_420_888toNV21(image: Image): ByteArray {
+        val width = image.width
+        val height = image.height
+        val ySize = width * height
+        val uvSize = ySize / 2
+        val nv21 = ByteArray(ySize + uvSize)
+
+        val yPlane = image.planes[0]
+        val uPlane = image.planes[1]
+        val vPlane = image.planes[2]
+
+        // Copy Y respecting row/pixel stride
+        val yBuffer = yPlane.buffer
+        val yRowStride = yPlane.rowStride
+        val yPixelStride = yPlane.pixelStride
+        val yBytes = ByteArray(yBuffer.remaining())
+        yBuffer.get(yBytes)
+        var pos = 0
+        var yOff = 0
+        for (row in 0 until height) {
+            var col = 0
+            while (col < width) {
+                nv21[pos++] = yBytes[yOff + col * yPixelStride]
+                col++
+            }
+            yOff += yRowStride
+        }
+
+        // Interleave V then U (NV21) respecting strides
+        val uBuffer = uPlane.buffer
+        val vBuffer = vPlane.buffer
+        val uRowStride = uPlane.rowStride
+        val vRowStride = vPlane.rowStride
+        val uPixelStride = uPlane.pixelStride
+        val vPixelStride = vPlane.pixelStride
+        val uBytes = ByteArray(uBuffer.remaining()).also { uBuffer.get(it) }
+        val vBytes = ByteArray(vBuffer.remaining()).also { vBuffer.get(it) }
+
+        val chromaHeight = height / 2
+        val chromaWidth = width / 2
+        for (row in 0 until chromaHeight) {
+            var uRowOff = row * uRowStride
+            var vRowOff = row * vRowStride
+            var col = 0
+            while (col < chromaWidth) {
+                nv21[pos++] = vBytes[vRowOff + col * vPixelStride] // V
+                nv21[pos++] = uBytes[uRowOff + col * uPixelStride] // U
+                col++
+            }
+        }
+        return nv21
+    }
📝 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
fun YUV_420_888toNV21(image: Image): ByteArray {
val nv21: ByteArray
val yBuffer = image.planes[0].buffer
val uBuffer = image.planes[1].buffer
val vBuffer = image.planes[2].buffer
val ySize = yBuffer.remaining()
val uSize = uBuffer.remaining()
val vSize = vBuffer.remaining()
nv21 = ByteArray(ySize + uSize + vSize)
//U and V are swapped
yBuffer.get(nv21, 0, ySize)
vBuffer.get(nv21, ySize, vSize)
uBuffer.get(nv21, ySize + vSize, uSize)
return nv21
}
fun YUV_420_888toNV21(image: Image): ByteArray {
val width = image.width
val height = image.height
val ySize = width * height
val uvSize = ySize / 2
val nv21 = ByteArray(ySize + uvSize)
val yPlane = image.planes[0]
val uPlane = image.planes[1]
val vPlane = image.planes[2]
// Copy Y respecting row/pixel stride
val yBuffer = yPlane.buffer
val yRowStride = yPlane.rowStride
val yPixelStride = yPlane.pixelStride
val yBytes = ByteArray(yBuffer.remaining())
yBuffer.get(yBytes)
var pos = 0
var yOff = 0
for (row in 0 until height) {
var col = 0
while (col < width) {
nv21[pos++] = yBytes[yOff + col * yPixelStride]
col++
}
yOff += yRowStride
}
// Interleave V then U (NV21) respecting strides
val uBuffer = uPlane.buffer
val vBuffer = vPlane.buffer
val uRowStride = uPlane.rowStride
val vRowStride = vPlane.rowStride
val uPixelStride = uPlane.pixelStride
val vPixelStride = vPlane.pixelStride
val uBytes = ByteArray(uBuffer.remaining()).also { uBuffer.get(it) }
val vBytes = ByteArray(vBuffer.remaining()).also { vBuffer.get(it) }
val chromaHeight = height / 2
val chromaWidth = width / 2
for (row in 0 until chromaHeight) {
var uRowOff = row * uRowStride
var vRowOff = row * vRowStride
var col = 0
while (col < chromaWidth) {
nv21[pos++] = vBytes[vRowOff + col * vPixelStride] // V
nv21[pos++] = uBytes[uRowOff + col * uPixelStride] // U
col++
}
}
return nv21
}

Comment on lines +86 to +95
var inputStream = inputStream
/* DEBUG */
synchronized(inputStream) {
val dataIn = DataInputStream(inputStream)
val bytes = ByteArray(imageLength)
dataIn.readFully(bytes)
inputStream = ByteArrayInputStream(bytes)
}
/* END DEBUG */

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unbounded, duplicate buffering of InputStream; potential OOM/DoS and wrong-length reads. Also decodeStream may return null.

  • Remove the DEBUG full read and avoid reading the stream twice.
  • For WSQ, read exactly imageLength with a sane upper bound.
  • Guard BitmapFactory.decodeStream with requireNotNull.

Apply this diff:

-        var inputStream = inputStream
-        /* DEBUG */
-        synchronized(inputStream) {
-            val dataIn = DataInputStream(inputStream)
-            val bytes = ByteArray(imageLength)
-            dataIn.readFully(bytes)
-            inputStream = ByteArrayInputStream(bytes)
-        }
-        /* END DEBUG */
+        val stream = inputStream
@@
-            val wsqDecoder = WsqDecoder()
-            val bitmap = wsqDecoder.decode(inputStream.readBytes())
+            val wsqDecoder = WsqDecoder()
+            require(imageLength in 1..(16 * 1024 * 1024)) { "imageLength too large: $imageLength" }
+            val wsqBytes = ByteArray(imageLength)
+            DataInputStream(stream).readFully(wsqBytes)
+            val bitmap = wsqDecoder.decode(wsqBytes)
@@
-            return BitmapFactory.decodeStream(inputStream)
+            return requireNotNull(BitmapFactory.decodeStream(stream)) {
+                "Unsupported or corrupt image format (mimeType=$mimeType)"
+            }

Also applies to: 98-101, 110-111

🤖 Prompt for AI Agents
In
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/ImageUtil.kt
around lines 86-95 (also apply same fixes to 98-101 and 110-111): remove the
DEBUG block that reads the entire InputStream into a byte array while
synchronizing (this duplicates buffering and can OOM/DoS and mis-handle
lengths); instead consume the incoming InputStream once and, for WSQ images,
read exactly imageLength bytes with a sane upper bound/cap (validate imageLength
before allocating and use a bounded read loop or a LimitedInputStream wrapper to
prevent over-reads); stop creating duplicate ByteArrayInputStream copies of the
same data; and guard BitmapFactory.decodeStream by wrapping its result with
requireNotNull (throw with a clear message) so null decode results fail fast.

Comment on lines +44 to +49
val lines = mrz.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (lines.size > 2) {
return cleanLine1(lines[0]) + "\n" + cleanLine2(lines[1])
}
throw IllegalArgumentException("Not enough lines")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix MRZ line-count check.

Standard MRZ payloads are exactly two lines long, but cleanString only accepts inputs where lines.size > 2. That means every valid two-line MRZ string now throws IllegalArgumentException("Not enough lines"), breaking the scanner path completely. Please accept two-line inputs by relaxing the guard.

-        if (lines.size > 2) {
+        if (lines.size >= 2) {
             return cleanLine1(lines[0]) + "\n" + cleanLine2(lines[1])
         }
         throw IllegalArgumentException("Not enough lines")
📝 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
val lines = mrz.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (lines.size > 2) {
return cleanLine1(lines[0]) + "\n" + cleanLine2(lines[1])
}
throw IllegalArgumentException("Not enough lines")
}
val lines = mrz.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (lines.size >= 2) {
return cleanLine1(lines[0]) + "\n" + cleanLine2(lines[1])
}
throw IllegalArgumentException("Not enough lines")
}
🤖 Prompt for AI Agents
In
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/MRZUtil.kt
around lines 44 to 49, the method currently rejects valid two-line MRZs by
checking `lines.size > 2`; change the guard to accept two-line inputs (e.g.,
`lines.size >= 2` or `lines.size == 2`) so the function returns cleaned line1 +
"\n" + cleaned line2 for standard MRZ payloads instead of throwing
IllegalArgumentException("Not enough lines"); keep the existing
cleanLine1/cleanLine2 calls and error path for fewer than two lines.

Comment on lines +249 to +268
private fun createDummyMrz(
documentType: String,
issuingState: String = "ESP",
documentNumber: String,
dateOfBirthDay: String,
expirationDate: String,
nationality: String = "ESP"
): MRZInfo {
return MRZInfo(
documentType,
issuingState,
"DUMMY",
"DUMMY",
documentNumber,
"ESP",
dateOfBirthDay,
Gender.MALE,
expirationDate,
""
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix hard-coded MRZ nationality/gender

createDummyMrz always reports "ESP" and Gender.MALE, ignoring both the parsed country code and the actual gender we already parsed above. Any non-Spanish document will therefore surface with the wrong nationality (and often the wrong gender), which breaks downstream compliance flows that rely on this metadata. Please pass through the real nationality/gender instead of hard-coding them.

-    private fun createDummyMrz(
-        documentType: String,
-        issuingState: String = "ESP",
-        documentNumber: String,
-        dateOfBirthDay: String,
-        expirationDate: String,
-        nationality: String = "ESP"
-    ): MRZInfo {
-        return MRZInfo(
-            documentType,
-            issuingState,
-            "DUMMY",
-            "DUMMY",
-            documentNumber,
-            "ESP",
-            dateOfBirthDay,
-            Gender.MALE,
-            expirationDate,
-            ""
-        )
-    }
+    private fun createDummyMrz(
+        documentType: String,
+        issuingState: String = "ESP",
+        documentNumber: String,
+        dateOfBirthDay: String,
+        expirationDate: String,
+        nationality: String = issuingState,
+        gender: Gender = Gender.UNSPECIFIED
+    ): MRZInfo {
+        return MRZInfo(
+            documentType,
+            issuingState,
+            "DUMMY",
+            "DUMMY",
+            documentNumber,
+            nationality,
+            dateOfBirthDay,
+            gender,
+            expirationDate,
+            ""
+        )
+    }

Remember to thread the parsed nationality/gender into these parameters at the call sites so the MRZInfo stays faithful to the document.

🤖 Prompt for AI Agents
In
app/android/android-mrz-qr-scanner/src/main/java/com/selfxyz/mrzqrscanner/utils/OcrUtils.kt
around lines 249 to 268, createDummyMrz currently hard-codes nationality as
"ESP" and gender as Gender.MALE; change the MRZInfo construction to use the
function parameters (pass nationality and gender) instead of hard-coded values,
update the function signature to accept a nationality:String and gender:Gender
if not already present, and then update every call site to forward the parsed
nationality and parsed gender so the returned MRZInfo reflects the actual
document data.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fbea81 and 5a06e1f.

📒 Files selected for processing (2)
  • app/android/app/build.gradle (1 hunks)
  • app/android/settings.gradle (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/android/app/build.gradle
🧰 Additional context used
📓 Path-based instructions (1)
app/android/**/*

⚙️ CodeRabbit configuration file

app/android/**/*: Review Android-specific code for:

  • Platform-specific implementations
  • Performance considerations
  • Security best practices for mobile

Files:

  • app/android/settings.gradle
⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: e2e-ios
  • GitHub Check: android-build-test
  • GitHub Check: build-deps
  • GitHub Check: analyze-android
  • GitHub Check: analyze-ios
🔇 Additional comments (1)
app/android/settings.gradle (1)

13-14: LGTM: New scanner module added correctly.

The android-mrz-qr-scanner module is properly declared and follows the existing project structure conventions.

Comment on lines +12 to 15
project(':passportreader').projectDir = new File(rootProject.projectDir, './android-passport-reader/app')
include ':android-mrz-qr-scanner'
project(':android-mrz-qr-scanner').projectDir = new File(rootProject.projectDir, './android-mrz-qr-scanner')
project(':passportreader').projectDir = new File(rootProject.projectDir, './android-passport-nfc-reader/app')
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Duplicate ':passportreader' project configuration will break the build.

Lines 12 and 15 both declare the same project identifier ':passportreader' with different directory paths:

  • Line 12: ./android-passport-reader/app
  • Line 15: ./android-passport-nfc-reader/app

Gradle will either fail the build or the second declaration will silently override the first, leading to unexpected behavior. Each project must have a unique identifier.

Verify which module declaration is correct and either:

  1. Remove the duplicate line, or
  2. Rename one of the projects (e.g., ':passportreader' vs ':passport-nfc-reader')
#!/bin/bash
# Verify which directories actually exist and contain build.gradle files
echo "Checking for android-passport-reader module:"
fd --type f --max-depth 3 "build.gradle" app/android/android-passport-reader

echo -e "\nChecking for android-passport-nfc-reader module:"
fd --type f --max-depth 3 "build.gradle" app/android/android-passport-nfc-reader
🤖 Prompt for AI Agents
In app/android/settings.gradle around lines 12 to 15 there are duplicate
declarations for project(':passportreader') pointing at two different folders
which will break or silently override the build; resolve by verifying which
module is correct (check that each target folder exists and contains a
build.gradle), then either remove the incorrect duplicate line or rename one
declaration to a unique identifier (e.g., ':passport-nfc-reader'), update its
include statement accordingly, and ensure each project(...) call uses the
correct projectDir path so no two entries share the same project id.

@transphorm
Copy link
Member Author

@seshanthS since you're working on new native module logic, can we close this pull request?

@transphorm transphorm marked this pull request as draft October 9, 2025 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants