Some checks failed
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>
338 lines
17 KiB
Markdown
338 lines
17 KiB
Markdown
# 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"
|