# 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
