Files
orion/docs/proposals/android-terminal-implementation.md
Samir Boulahtit b27d4ba6ff
Some checks failed
CI / ruff (push) Successful in 15s
CI / pytest (push) Failing after 2h21m3s
CI / validate (push) Successful in 29s
CI / dependency-scanning (push) Successful in 32s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
docs: add Android terminal implementation plan
Full implementation plan for the RewardFlow Terminal Android app:
4 screens (Setup, PIN, Terminal, Enrollment), 6 phases (~5 days),
ASCII wireframes, data layer design, offline queue strategy,
multi-language support, and API model changes needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 22:52:00 +02:00

17 KiB

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<Int>? 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"