# Android Terminal App — Implementation Plan ## Overview Native Android tablet POS app for loyalty terminal. 4 screens, landscape-only, kiosk mode. Calls the existing `/api/v1/store/loyalty/*` API — no backend changes. **Already scaffolded:** Gradle project compiles, stub screens render on Pixel Tablet emulator. Retrofit API client, Room DB, Hilt DI, all dependencies wired. --- ## Screens & Workflows ### Screen 1: Setup (one-time) ``` ┌──────────────────────────────────────────────────────┐ │ │ │ RewardFlow Terminal │ │ │ │ [Camera Preview — QR Scanner] │ │ │ │ Scan the setup QR code from your │ │ store settings page │ │ │ │ ──── or enter manually ──── │ │ │ │ API URL: [________________________] │ │ Store Code: [____________________] │ │ Auth Token: [____________________] │ │ │ │ [Connect] │ │ │ └──────────────────────────────────────────────────────┘ ``` **Flow:** 1. CameraX preview with ML Kit barcode scanning 2. QR code contains JSON: `{"api_url": "https://rewardflow.lu", "store_code": "FASHIONHUB", "auth_token": "jwt..."}` 3. Or manual entry (for dev/testing) 4. On scan/submit: call `GET /api/v1/store/loyalty/program` to verify connection 5. Download and cache: program config, branding (colors, logo), staff PINs, categories 6. Save to DataStore: api_url, auth_token, store_id, store_code 7. Navigate to PIN screen **Files:** - `ui/setup/SetupScreen.kt` — UI (exists, needs real implementation) - `ui/setup/SetupViewModel.kt` — state (exists, needs expansion) - `ui/scanner/QrScannerView.kt` — NEW, CameraX + ML Kit composable - `data/repository/DeviceConfigRepository.kt` — NEW, DataStore read/write **Web backend change needed:** - New endpoint or page: "Generate tablet setup QR" button on store settings page (generates QR with JWT + store info) --- ### Screen 2: PIN / Staff Selection ``` ┌──────────────────────────────────────────────────────┐ │ RewardFlow Terminal Fashion Hub [🌐 FR] [⚙️] │ ├──────────────────────────────────────────────────────┤ │ │ │ Select your name │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Jane │ │ Diana │ │ Eric │ │ │ │ Owner │ │ Stylist │ │ Sales │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ Enter your PIN │ │ │ │ [ • ] [ • ] [ ] [ ] │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ 1 │ │ 2 │ │ 3 │ │ │ ├─────┤ ├─────┤ ├─────┤ │ │ │ 4 │ │ 5 │ │ 6 │ │ │ ├─────┤ ├─────┤ ├─────┤ │ │ │ 7 │ │ 8 │ │ 9 │ │ │ ├─────┤ ├─────┤ ├─────┤ │ │ │ C │ │ 0 │ │ ← │ │ │ └─────┘ └─────┘ └─────┘ │ │ │ │ 3 pending transactions ⟳ │ │ │ └──────────────────────────────────────────────────────┘ ``` **Flow:** 1. Show list of staff names (from cached PINs, refreshed from API periodically) 2. Seller taps their name → name highlighted, keypad appears 3. Enter 4-digit PIN → verified locally against bcrypt hash (cached from API) 4. If correct → navigate to Terminal with staffPinId + staffName 5. If wrong → shake animation, error message, clear digits 6. Language selector (globe icon) → switches app language, saved per-seller in DataStore 7. Settings gear → shows pending sync count, device info, "Reset device" option 8. If offline → show "Offline" badge, PIN still works (cached) **Files:** - `ui/pin/PinScreen.kt` — UI (exists, needs seller list + real PIN verification) - `ui/pin/PinViewModel.kt` — NEW, loads cached PINs, verifies, manages language - `data/repository/StaffPinRepository.kt` — NEW, caches PINs from API in DataStore **Important:** PIN hashes are bcrypt — need to verify client-side. Add `implementation("at.favre.lib:bcrypt:0.10.2")` to gradle deps (pure Java bcrypt, no native deps). --- ### Screen 3: Terminal (main POS) ``` ┌──────────────────────────────────────────────────────┐ │ Staff: Diana Fashion Hub [Offline ⚡] [🔒] │ ├──────────────────────┬───────────────────────────────┤ │ │ │ │ Search Customer │ No customer selected │ │ ┌────────────────┐ │ │ │ │ 🔍 Card / Email│ │ Scan a QR code or search │ │ └────────────────┘ │ by card number / email │ │ │ │ │ [📷 Scan QR Code] │ │ │ │ │ │ ─── or ─── │ │ │ │ │ │ [+ Enroll Customer] │ │ │ │ │ ├──────────────────────┤ │ │ Recent Transactions │ │ │ ┌────────────────┐ │ │ │ │ 10:32 +50pts │ │ │ │ │ 10:15 +1 stamp │ │ │ │ │ 09:45 Enrolled │ │ │ │ └────────────────┘ │ │ │ │ │ └──────────────────────┴───────────────────────────────┘ ``` **After customer found:** ``` ┌──────────────────────────────────────────────────────┐ │ Staff: Diana Fashion Hub [Online ✓] [🔒] │ ├──────────────────────┬───────────────────────────────┤ │ │ Jane Doe │ │ Search Customer │ jane@example.com [✕] │ │ ┌────────────────┐ │ Card: 1234-5678-9012 │ │ │ 🔍 Card / Email│ │ │ │ └────────────────┘ │ ┌───────────────────────┐ │ │ │ │ 500 Points │ │ │ [📷 Scan QR Code] │ │ 3 / 10 Stamps │ │ │ │ └───────────────────────┘ │ │ ─── or ─── │ │ │ │ ┌──────────┐ ┌──────────┐ │ │ [+ Enroll Customer] │ │ + Stamp │ │ Redeem │ │ │ │ │ │ │ Stamps │ │ ├──────────────────────┤ ├──────────┤ ├──────────┤ │ │ Recent Transactions │ │ + Points │ │ Redeem │ │ │ ┌────────────────┐ │ │ EUR [__] │ │ Reward ▼ │ │ │ │ 10:32 +50pts │ │ └──────────┘ └──────────┘ │ │ │ 10:15 +1 stamp │ │ │ │ │ 09:45 Enrolled │ │ │ │ └────────────────┘ │ │ │ │ │ └──────────────────────┴───────────────────────────────┘ ``` **Flow:** 1. **Search**: text field for card number or email, searches via API 2. **QR Scan**: opens camera overlay, scans loyalty card QR → lookup 3. **Enroll**: inline form (name, email, birthday) → creates customer + card 4. **Customer found**: right panel shows card details + action buttons 5. **Actions**: each opens a bottom sheet or dialog with: - Category selector (multi-select pills — same as web terminal) - Confirm button - No separate PIN entry (already authenticated on PIN screen) 6. **Earn points**: EUR amount input (digits only) + category → API call 7. **Add stamp**: category → API call 8. **Redeem stamps**: confirm dialog → API call 9. **Redeem reward**: reward picker dropdown → API call 10. **Offline**: stamp/earn queued to Room DB, redeem shows error dialog 11. **Auto-lock**: 2 min idle → navigate back to PIN screen 12. **Lock button** (top right): immediate lock to PIN screen **Files:** - `ui/terminal/TerminalScreen.kt` — UI (exists as stub, needs full implementation) - `ui/terminal/TerminalViewModel.kt` — NEW, state management, API calls, offline queue - `ui/terminal/components/CustomerCard.kt` — NEW, customer info + balance display - `ui/terminal/components/ActionButtons.kt` — NEW, stamp/points/redeem buttons - `ui/terminal/components/CategorySelector.kt` — NEW, multi-select pill group - `ui/terminal/components/EnrollForm.kt` — NEW, inline enrollment - `ui/terminal/components/TransactionList.kt` — NEW, recent transactions - `ui/scanner/QrScannerOverlay.kt` — NEW, fullscreen camera overlay for QR scan --- ### Screen 4: Enrollment (inline or dialog) ``` ┌──────────────────────────────────────┐ │ Enroll New Customer │ │ │ │ Name * [____________________] │ │ Email * [____________________] │ │ Phone [____________________] │ │ Birthday [____________________] │ │ │ │ [Cancel] [Enroll] │ └──────────────────────────────────────┘ ``` **Flow:** 1. Shown as a bottom sheet dialog from Terminal screen 2. Name + email required, phone + birthday optional 3. On submit: `POST /store/loyalty/cards/enroll` 4. On success: auto-selects the new card in the terminal 5. Offline: queued to Room DB, card shown with "Pending sync" badge --- ## Data Layer ### DataStore (device config — persists across app restarts) - `api_url: String` - `auth_token: String` - `store_id: Int` - `store_code: String` - `store_name: String` - `program_json: String` (cached program config) - `staff_pins_json: String` (cached PINs list with bcrypt hashes) - `categories_json: String` (cached categories) - `is_device_set_up: Boolean` - `seller_language_{pin_id}: String` (per-seller language preference) ### Room DB (offline transaction queue) Already created: `PendingTransaction` entity + DAO. - type: "stamp", "points_earn", "enroll" - requestJson: full API request body - status: pending → syncing → synced / failed ### WorkManager (background sync) - `SyncWorker.kt` — NEW, runs when connectivity returns - Processes pending transactions in order (FIFO) - Retries failed ones with backoff - Registered as `OneTimeWorkRequest` with network constraint --- ## Implementation Phases ### Phase A: Core infrastructure (~1 day) 1. `DeviceConfigRepository` — DataStore read/write for all config 2. `StaffPinRepository` — cache + refresh PINs from API 3. `CategoryRepository` — cache + refresh categories 4. `NetworkMonitor` — observe connectivity state (ConnectivityManager) 5. `AuthInterceptor` — OkHttp interceptor that adds Bearer token from DataStore 6. Add bcrypt dependency for client-side PIN verification 7. String resources for EN/FR/DE/LB (all 4 locales) ### Phase B: Setup screen (~0.5 day) 1. QR scanner composable (CameraX + ML Kit) 2. Manual entry form (for dev) 3. Connection verification (call program API) 4. Download + cache initial config 5. Navigate to PIN on success ### Phase C: PIN screen (~0.5 day) 1. Staff name list (from cached PINs) 2. PIN keypad with verification (bcrypt compare) 3. Per-seller language preference 4. Language selector (globe icon) 5. Pending sync count indicator 6. Error handling (wrong PIN, locked PIN) ### Phase D: Terminal screen (~2 days) 1. Two-pane landscape layout 2. Search field + API lookup 3. QR scanner overlay (reuse from setup) 4. Customer card display (balance, stamps, rewards) 5. Action buttons (stamp, earn, redeem stamps, redeem reward) 6. Category multi-select pills 7. EUR input (digits only, same validation as web) 8. Reward dropdown picker 9. Recent transactions list 10. Offline indicator in header 11. Auto-lock timer (2 min idle) ### Phase E: Offline queue + sync (~0.5 day) 1. Queue stamp/earn/enroll to Room when offline 2. Show "Pending sync" badges 3. SyncWorker with network constraint 4. Toast on sync completion 5. Redeem shows "offline" error dialog ### Phase F: Polish + kiosk (~0.5 day) 1. Lock Task Mode setup 2. App icon + splash screen 3. Error handling (network errors, API errors, toast messages) 4. Loading states on all API calls 5. Landscape-only enforcement 6. Hide navigation/status bars (immersive mode) **Total: ~5 days** --- ## Web Backend — One Change Needed **Generate setup QR code** — new button on the store settings page that creates a QR containing `{"api_url": "...", "store_code": "...", "auth_token": "..."}`. The auth_token should be a long-lived store API JWT (or a device-specific token). This could be a simple endpoint: `POST /api/v1/store/loyalty/device-setup-token` that returns a JWT with a 1-year expiry, then the settings page renders it as a QR code. --- ## API Models to Add The existing `ApiModels.kt` is missing: - `category_ids: List?` on `StampRequest` and `PointsEarnRequest` - `CategoryResponse` model for cached categories - `GET /api/v1/store/loyalty/categories` endpoint in `LoyaltyApi.kt` --- ## String Resources (multi-language) 4 files: - `res/values/strings.xml` (EN — default) - `res/values-fr/strings.xml` - `res/values-de/strings.xml` - `res/values-lb/strings.xml` Key strings (~50): - Setup: "Scan QR code", "Connect", "Connecting..." - PIN: "Select your name", "Enter your PIN", "Wrong PIN", "PIN locked" - Terminal: "Search customer", "Scan QR", "Enroll customer", "No customer selected" - Actions: "Add stamp", "Earn points", "Redeem stamps", "Redeem reward", "Purchase amount" - Categories: "Select category" - Status: "Online", "Offline", "Syncing", "X pending" - Errors: "Connection failed", "No internet", "Try again"