# FarmaFinder — TSI Barcode Scanning + Capacitor 8 Upgrade ## Overview Two sequential phases: 1. **Phase A** — Upgrade Capacitor 6 → 8 (prerequisite for latest barcode scanner plugin) 2. **Phase B** — Implement real TSI barcode scanning with manual CIP entry fallback --- ## Phase A: Upgrade Capacitor 6 → 8 ### A1. Upgrade Capacitor packages (root) ```bash npm install @capacitor/cli@latest @capacitor/core@latest \ @capacitor/android@latest @capacitor/ios@latest \ @capacitor/app@latest @capacitor/splash-screen@latest \ @capacitor/status-bar@latest ``` This bumps all `@capacitor/*` from `^6.x` to `^8.x` in `package.json`. ### A2. Update `android/variables.gradle` Replace all 13 version variables to meet Capacitor 8 minimums: | Variable | Current | New | |---|---|---| | `minSdkVersion` | 22 | **24** | | `compileSdkVersion` | 34 | **36** | | `targetSdkVersion` | 34 | **36** | | `androidxActivityVersion` | 1.8.0 | **1.11.0** | | `androidxAppCompatVersion` | 1.6.1 | **1.7.1** | | `androidxCoordinatorLayoutVersion` | 1.2.0 | **1.3.0** | | `androidxCoreVersion` | 1.12.0 | **1.17.0** | | `androidxFragmentVersion` | 1.6.2 | **1.8.9** | | `coreSplashScreenVersion` | 1.0.1 | **1.2.0** | | `androidxWebkitVersion` | 1.9.0 | **1.14.0** | | `junitVersion` | 4.13.2 | 4.13.2 (same) | | `androidxJunitVersion` | 1.1.5 | **1.3.0** | | `androidxEspressoCoreVersion` | 3.5.1 | **3.7.0** | | `cordovaAndroidVersion` | 10.1.1 | **14.0.1** | ### A3. Update `ios/App/Podfile` - Line 3: Change `platform :ios, '13.0'` → `platform :ios, '15.0'` ### A4. Update `android/app/src/main/AndroidManifest.xml` - Add `density` to `android:configChanges` on line 13: ``` android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|density" ``` ### A5. Update `android/build.gradle` (Gradle plugin) - Update Android Gradle Plugin from `8.2.1` → `8.13.0` - Update Google Services plugin from `4.4.0` → `4.4.4` - Update Gradle wrapper to `8.14.3` in `android/gradle/wrapper/gradle-wrapper.properties` ### A6. Run Capacitor sync ```bash npx cap sync ``` ### A7. Verify build - Open `android/` in Android Studio — confirm Gradle sync succeeds - Open `ios/App` in Xcode — confirm pod install succeeds and build compiles --- ## Phase B: Real TSI Barcode Scanning ### B1. Install barcode scanning plugin From `frontend/`: ```bash npm install @capacitor-mlkit/barcode-scanning@^8.1.0 npm install barcode-detector # web polyfill for PWA browser fallback ``` From root: ```bash npx cap sync ``` **Plugin:** `@capacitor-mlkit/barcode-scanning` v8.1.0 — Google ML Kit engine, 91K+ weekly npm downloads, supports Code 128 (TSI physical card barcode) and QR Code (virtual TSI card). ### B2. Native manifest verification (no changes expected) | Platform | File | Required | Status | |---|---|---|---| | Android | `AndroidManifest.xml:44` | `CAMERA` permission | Already present | | iOS | `Info.plist:52-53` | `NSCameraUsageDescription` | Already present | ### B3. Rewrite `frontend/src/views/ScannerView.jsx` **Remove:** - `MOCK_TSI_CARDS` array and all mock card state/UI - `navigator.mediaDevices.getUserMedia()` camera logic (lines 40-73) - Auto-scan `setTimeout` simulation (lines 98-105) - Simulated scan fallback panel (lines 170-198) - `selectedMockCard` state **Add:** - Import `BarcodeScanner`, `BarcodeFormat`, `LensFacing` from `@capacitor-mlkit/barcode-scanning` - Import `Capacitor` from `@capacitor/core` (platform detection) - Import `barcode-detector/polyfill` at top (web fallback) **New scan flow:** ``` 1. Component mounts → show "Start Scanning" button + manual CIP input 2. User taps "Start Scanning": a. Check BarcodeScanner.isSupported() → error if not b. Check/request camera permission via BarcodeScanner.checkPermissions() / requestPermissions() c. Call BarcodeScanner.scan({ formats: [BarcodeFormat.Code128, BarcodeFormat.QrCode], autoZoom: true }) d. Extract barcodes[0].rawValue (NOTE: can be undefined per v8 breaking change — handle gracefully) e. Validate CIP format: /^[A-Z0-9]{16}$/i f. If valid → fetchPrescriptions(cip) → show prescriptions g. If invalid → show error "Invalid card barcode. Try manual entry." 3. Manual CIP input: - Text input field with placeholder "Enter CIP code manually" - "Submit" button → validates format → fetchPrescriptions(cip) 4. PWA/browser fallback: - Detect: !Capacitor.isNativePlatform() - Show message: "Barcode scanning requires the mobile app. Enter your CIP code manually below." - Show only the manual CIP input (no scan button) ``` **Phases (simplified):** - `idle` — initial state, scan button + manual input visible - `scanning` — ML Kit native UI active (camera takes over) - `prescriptions` — results display (keep existing prescription card UI) - `error` — permission denied / unsupported / invalid barcode **Keep unchanged:** - `playBeep()` function - `fetchPrescriptions(cip)` function - `handlePickPrescription(rx)` function - `handleBack()` function - Prescription display UI (lines 201-249) - `onSelectMedicine` callback prop ### B4. Rewrite `frontend/src/views/ScannerView.css` **Remove:** - `.scanner-viewport-wrap`, `.scanner-camera-container`, `.scanner-video`, `.scanner-frame`, `.scanner-laser`, `.scanner-hint` (old camera overlay) - `.scanner-simulate-panel`, `.mock-cards-list`, `.mock-card-btn`, `.simulate-scan-btn`, `.simulate-label`, `.mock-card-icon`, `.mock-card-info` (mock card panel) - `.scanner-placeholder` with spinner (old loading state) **Add:** - `.scan-start-btn` — large centered button with camera icon, teal background (`#0f766e`), full-width - `.manual-cip-section` — input group with text field + submit button - `.cip-input` — text input styled to match existing dark theme, monospace font - `.cip-submit-btn` — teal button matching scan button style - `.pwa-notice` — info banner for browser users - `.scan-error-panel` — error state with icon and retry button ### B5. No changes to other files - `HomeView.jsx` — "Scan TSI Card" button still navigates to ScannerView - `PublicView.jsx` — screen routing and `onSelectMedicine` handoff unchanged - `backend/server.js` — user handles `/api/tsi/:cip/prescriptions` - `capacitor.config.json` — no scanner-specific config needed --- ## Files Modified (summary) | File | Phase | Change | |---|---|---| | `package.json` | A1 | Bump all `@capacitor/*` to ^8.x | | `frontend/package.json` | B1 | Add `@capacitor-mlkit/barcode-scanning@^8.1.0` + `barcode-detector` | | `android/variables.gradle` | A2 | Update all 13 SDK/library versions | | `ios/App/Podfile` | A3 | iOS deployment target 13.0 → 15.0 | | `android/app/src/main/AndroidManifest.xml` | A4 | Add `density` to configChanges | | `android/build.gradle` | A5 | AGP 8.2.1 → 8.13.0, Google Services 4.4.0 → 4.4.4 | | `android/gradle/wrapper/gradle-wrapper.properties` | A5 | Gradle wrapper → 8.14.3 | | `frontend/src/views/ScannerView.jsx` | B3 | Full rewrite — real barcode scanning + manual input | | `frontend/src/views/ScannerView.css` | B4 | Remove old camera/mock styles, add new scan UI | --- ## Known Issues - **Android NullPointerException bug** (GitHub capawesome-team/capacitor-mlkit #160/#324) — affects `startScan()` in v8.0.1. Workaround: use `scan()` method (built-in native UI) instead of `startScan()` (custom WebView mode). Plan uses `scan()`, so this shouldn't hit us. - **`rawValue` can be undefined** in v8.x — handle with null check before CIP validation. - **Capacitor 8 requires Xcode 26+** — must have latest Xcode installed. - **Gradle 8.14.3 requires JDK 21+** — confirm JDK 21 is available. - **`@capacitor-mlkit/barcode-scanning` v6.x.x is deprecated** — this is why we upgrade to Capacitor 8 first (v8.x.x plugin requires Cap 8). --- ## Implementation Order | Step | Phase | Estimated Time | |---|---|---| | A1-A6 | Capacitor upgrade | ~30 min | | A7 | Verify build (Android + iOS) | ~30 min | | B1 | Install barcode plugin | ~5 min | | B3 | Rewrite ScannerView.jsx | ~2 hrs | | B4 | Update ScannerView.css | ~30 min | | B5 | Test on Android device | ~30 min | | B5 | Test on iOS device | ~30 min | Total: ~4-5 hours