Phase 3.2 docs + Phase 3.1 testing fixes
- Add phase3.2.md requirement: overlay UX, mitra activity log - Add phase3.2-plan.md implementation plan - Fix stale request validation: add GET /:sessionId/status endpoint - Fix notification tap flow: setIncomingFromNotification + onChatRequestTapped - IncomingRequestSheet shows stale message instead of auto-dismiss - Home screen validates on resume, shows immediately on fresh WS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
137
requirement/phase3.2.md
Normal file
137
requirement/phase3.2.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# PRD: Mitra Chat Request Overlay & Pairing UX
|
||||
|
||||
# Overview
|
||||
|
||||
**Goal:** Reliable incoming chat request experience for Mitra — always visible, non-blocking, works in all app states (foreground, background, killed)
|
||||
|
||||
**Success looks like:** When a customer requests a chat, the mitra is always notified and can accept/reject without leaving their current screen. The notification works regardless of whether the app is in foreground, background, or opened from a push notification.
|
||||
|
||||
## Background
|
||||
- Current bottom sheet approach (`showModalBottomSheet`) fails silently when the app is backgrounded
|
||||
- Flutter UI cannot render when the app is not in foreground
|
||||
- Push notifications work in background but the in-app response (bottom sheet) doesn't show reliably when the user taps the notification
|
||||
- The state change from WebSocket gets "consumed" by listeners while backgrounded, so returning to foreground shows stale data
|
||||
- Need a solution that is non-blocking (doesn't interrupt active chat sessions) and works on any page
|
||||
|
||||
|
||||
# Functional Requirement
|
||||
|
||||
## Incoming Chat Request Overlay
|
||||
|
||||
### Trigger
|
||||
- Overlay watches `chatRequestProvider` state — it does NOT depend on any specific page or lifecycle event
|
||||
- Three sources can trigger the overlay:
|
||||
1. **WebSocket** — real-time delivery when app is in foreground
|
||||
2. **FCM notification tap** — user taps push notification, app opens, state is set from notification payload
|
||||
3. **App resume** — app returns to foreground, validates pending request with backend
|
||||
|
||||
### Appearance
|
||||
- Slides up from the bottom of the screen, like a bottom sheet
|
||||
- Rounded top corners
|
||||
- Page behind is partially dimmed to get mitra's attention
|
||||
- Shows on top of ANY page (home, chat, history, settings, etc.)
|
||||
|
||||
### Content
|
||||
- Session information (duration, etc.)
|
||||
- Accept button
|
||||
- Reject button
|
||||
- Swipe down to close (= ignore, NOT reject)
|
||||
|
||||
### Behavior
|
||||
- **Accept** → overlay dismisses, navigate to chat session with customer
|
||||
- **Reject** → overlay dismisses, mitra continues what they were doing
|
||||
- **Swipe down / close** → overlay dismisses, request is ignored (no explicit reject sent to backend)
|
||||
- **Request cancelled by customer** → overlay updates to show "Permintaan dibatalkan oleh customer"
|
||||
- **Request accepted by other mitra** → overlay updates to show "Permintaan diterima oleh Bestie lain"
|
||||
- **Request expired (60s timeout)** → overlay updates to show "Permintaan kedaluwarsa"
|
||||
- Stale messages do NOT auto-dismiss — mitra must acknowledge by tapping OK or swiping down
|
||||
|
||||
### Multiple Requests
|
||||
- Show one request at a time
|
||||
- Requests are queued — when current request is resolved (accepted/rejected/ignored/expired), next queued request appears
|
||||
- Future enhancement: dedicated `/chat-requests` page with full list
|
||||
|
||||
## Push Notification (Background/Killed)
|
||||
|
||||
### When App is Backgrounded
|
||||
- Local notification with sound + vibration (already implemented via WebSocket listener)
|
||||
- FCM push notification as fallback when WebSocket is disconnected
|
||||
|
||||
### When Notification is Tapped
|
||||
- App opens → `chatRequestProvider` state is set from notification payload (`session_id`)
|
||||
- Overlay appears with the request detail
|
||||
- Backend validation confirms request is still pending before showing accept/reject
|
||||
|
||||
### Stale Notification Handling
|
||||
- If user taps a notification for an already-resolved request, overlay shows appropriate message ("dibatalkan" / "diterima Bestie lain" / "kedaluwarsa") then auto-dismisses
|
||||
|
||||
|
||||
## Mitra Request Activity Log
|
||||
|
||||
### Goal
|
||||
Track every mitra's response to incoming chat requests for QC measurement — how many accepted, rejected, ignored (expired without action), and how many active sessions they had at the time.
|
||||
|
||||
### Logged Data
|
||||
For each incoming request delivered to a mitra, log:
|
||||
- `mitra_id` — which mitra received the request
|
||||
- `session_id` — which chat request
|
||||
- `response` — `accepted`, `rejected`, `ignored` (expired without action), `missed` (taken by other mitra before response)
|
||||
- `response_time_seconds` — how long it took the mitra to respond (null if ignored)
|
||||
- `active_session_count` — how many active sessions the mitra had at the time of the request
|
||||
- `notified_at` — when the request was delivered to the mitra
|
||||
- `responded_at` — when the mitra responded (null if ignored)
|
||||
|
||||
### When to Log
|
||||
All logging is **backend-side and event-driven** — no polling or constant monitoring needed. The frontend only triggers `accepted` and `rejected` through existing API calls.
|
||||
|
||||
| Response | Triggered by | Frontend action? |
|
||||
|---|---|---|
|
||||
| `accepted` | Backend — when mitra calls `POST /:sessionId/accept` | Yes — mitra taps Accept |
|
||||
| `rejected` | Backend — when mitra calls `POST /:sessionId/decline` | Yes — mitra taps Reject |
|
||||
| `missed` | Backend — when `acceptPairingRequest` marks other mitras' notifications (another mitra accepted first) | No — fully backend |
|
||||
| `ignored` | Backend — when `expirePairingRequest` fires after 60s timeout with no response | No — fully backend |
|
||||
|
||||
### Existing Infrastructure
|
||||
The `chat_request_notifications` table already tracks `notified_at`, `response`, `responded_at`. Current changes needed:
|
||||
- Distinguish `missed` from `ignored` (currently both stored as `ignored`)
|
||||
- Add `active_session_count` column — recorded when the notification is created
|
||||
- `response_time_seconds` can be calculated from `responded_at - notified_at` (no new column needed)
|
||||
|
||||
### Control Center
|
||||
- New dedicated page: Mitra Request Activity
|
||||
- Dashboard: mitra acceptance rate, average response time, rejection count, ignore count per mitra
|
||||
- Filter by date range and mitra
|
||||
- Auto-flagging mitras with high rejection/ignore rate is **out of scope** for this phase (planned for future)
|
||||
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Overlay Component
|
||||
- Single `OverlayEntry` managed from `main.dart`
|
||||
- Wrapped around `MaterialApp.router` in a `Stack`
|
||||
- Watches `chatRequestProvider` — shows/hides based on state
|
||||
- Uses `AnimatedPositioned` or `SlideTransition` for bottom-up animation
|
||||
- `GestureDetector` for swipe-to-dismiss
|
||||
|
||||
### State Flow
|
||||
```
|
||||
WebSocket ──→
|
||||
FCM tap ──→ chatRequestProvider ──→ Overlay shows/hides
|
||||
App resume ──→
|
||||
```
|
||||
|
||||
### No Changes Required
|
||||
- Backend pairing service (already sends WS + FCM)
|
||||
- Push notification payload (already contains session_id + type)
|
||||
- Chat request notifier (already manages state from WS + FCM)
|
||||
|
||||
### Cleanup from Phase 3.1
|
||||
- Remove `showModalBottomSheet` for incoming requests from home screen
|
||||
- Remove `IncomingRequestSheet` widget
|
||||
- Remove `didChangeAppLifecycleState` incoming request check from home screen
|
||||
|
||||
|
||||
# Tech Stack
|
||||
- Flutter `Overlay` / `OverlayEntry` or `Stack` with `AnimatedPositioned`
|
||||
- Existing Riverpod `chatRequestProvider`
|
||||
- No backend changes
|
||||
Reference in New Issue
Block a user