|
| 1 | +# Tap-to-Hide Controls in CallView — Progress |
| 2 | + |
| 3 | +## Vertical Slices |
| 4 | + |
| 5 | +Each slice is independently demoable and builds on the previous one. |
| 6 | + |
| 7 | +| Order | Title | Type | Blocked by | Status | |
| 8 | +|-------|-------|------|------------|--------| |
| 9 | +| 1 | feat: add `controlsVisible` state and actions to call store | AFK | None | [x] | |
| 10 | +| 2 | feat: tap CallerInfo to toggle controls visibility | AFK | #1 | [x] | |
| 11 | +| 3 | feat: animate CallButtons slide-down + fade on toggle | AFK | #2 | [x] | |
| 12 | +| 4 | feat: animate MediaCallHeader slide-up + fade on toggle | AFK | #2 | [x] | |
| 13 | +| 5 | feat: auto-show controls on call state changes | AFK | #1 | [x] | |
| 14 | +| 6 | chore: update tests and snapshots for controls animation | AFK | #2, #3, #4 | [x] | |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +### Slice 1: feat: add `controlsVisible` state and actions to call store |
| 19 | + |
| 20 | +**User stories:** 1, 2, 8 |
| 21 | + |
| 22 | +**What to do:** |
| 23 | +- Add `controlsVisible: boolean` (default `true`) to `CallStoreState` in useCallStore |
| 24 | +- Add `toggleControlsVisible()` action — flips the boolean |
| 25 | +- Add `showControls()` action — sets `controlsVisible: true` |
| 26 | +- Add `CONTROLS_ANIMATION_DURATION = 300` constant to CallView styles |
| 27 | +- Add convenience selector `useControlsVisible` |
| 28 | + |
| 29 | +**Demo:** Call `toggleControlsVisible()` from store in tests/devtools and verify state flips. |
| 30 | + |
| 31 | +--- |
| 32 | + |
| 33 | +### Slice 2: feat: tap CallerInfo to toggle controls visibility |
| 34 | + |
| 35 | +**User stories:** 1, 2, 6, 7 |
| 36 | + |
| 37 | +**What to do:** |
| 38 | +- Wrap CallerInfo outer `<View>` with `<Pressable onPress={toggleControlsVisible} testID='caller-info-toggle'>` |
| 39 | +- Wrap caller name `callerRow` in `<Animated.View>` with fade + slight slide animation driven by `controlsVisible` |
| 40 | +- Avatar stays unwrapped — always visible and centered |
| 41 | +- Use `useAnimatedStyle` + `withTiming` reading `controlsVisible` directly from store (no useEffect) |
| 42 | + |
| 43 | +**Demo:** Tap the center of CallView — caller name fades out, avatar stays. Tap again — name fades back in. |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +### Slice 3: feat: animate CallButtons slide-down + fade on toggle |
| 48 | + |
| 49 | +**User stories:** 1, 2, 5, 10 |
| 50 | + |
| 51 | +**What to do:** |
| 52 | +- Replace outer `<View>` in CallButtons with `<Animated.View>` |
| 53 | +- Add `useAnimatedStyle` for opacity (1→0) and translateY (0→100) driven by `controlsVisible` |
| 54 | +- Set `pointerEvents={controlsVisible ? 'auto' : 'none'}` to block ghost taps |
| 55 | + |
| 56 | +**Demo:** Tap center — buttons slide down and fade out, invisible buttons are not tappable. Tap again — buttons slide back up. |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +### Slice 4: feat: animate MediaCallHeader slide-up + fade on toggle |
| 61 | + |
| 62 | +**User stories:** 1, 2, 5, 11 |
| 63 | + |
| 64 | +**What to do:** |
| 65 | +- Replace call-active `<View>` in MediaCallHeader with `<Animated.View>` |
| 66 | +- Add `useAnimatedStyle` for opacity (1→0) and translateY (0→-100) driven by `controlsVisible` |
| 67 | +- Guard: only animate when `focused === true`. When `focused === false` (collapsed header bar), always show. |
| 68 | +- Set `pointerEvents={shouldHide ? 'none' : 'auto'}` |
| 69 | + |
| 70 | +**Demo:** Tap center in CallView — header slides up and disappears. Collapse to header bar — header is always visible regardless of `controlsVisible`. |
| 71 | + |
| 72 | +--- |
| 73 | + |
| 74 | +### Slice 5: feat: auto-show controls on call state changes |
| 75 | + |
| 76 | +**User stories:** 3, 4, 8, 9 |
| 77 | + |
| 78 | +**What to do:** |
| 79 | +- In `handleStateChange` (inside `setCall`): add `set({ controlsVisible: true })` |
| 80 | +- In `handleTrackStateChange` (inside `setCall`): add `set({ controlsVisible: true })` |
| 81 | +- In `toggleFocus`: set `controlsVisible: true` when toggling (always reveal on focus change) |
| 82 | +- `reset()` already covers call-end via `initialState` spread |
| 83 | + |
| 84 | +**Demo:** Hide controls → trigger remote hold from another client → controls auto-reveal. Collapse to header → re-expand → controls are visible. |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +### Slice 6: chore: update tests and snapshots for controls animation |
| 89 | + |
| 90 | +**User stories:** All (verification) |
| 91 | + |
| 92 | +**What to do:** |
| 93 | +- **Store tests:** `toggleControlsVisible` flips value, `showControls` sets true, auto-show on stateChange/trackStateChange, reset restores true, toggleFocus sets true |
| 94 | +- **CallerInfo tests:** pressing `caller-info-toggle` calls `toggleControlsVisible`, snapshot update |
| 95 | +- **CallButtons tests:** `pointerEvents='none'` when `controlsVisible=false`, snapshot update |
| 96 | +- **MediaCallHeader tests:** `pointerEvents='none'` when `focused=true && controlsVisible=false`, `pointerEvents='auto'` when `focused=false`, snapshot update |
| 97 | + |
| 98 | +**Demo:** `yarn test -- --testPathPattern='CallView|MediaCallHeader|useCallStore'` passes. |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +## Design Decisions Log |
| 103 | + |
| 104 | +| Question | Decision | |
| 105 | +|----------|----------| |
| 106 | +| What hides? | Everything except avatar — header, buttons, caller name/text | |
| 107 | +| Auto-hide timer? | No — explicit tap only | |
| 108 | +| Animation style? | Slide + fade, ~300ms with `withTiming` | |
| 109 | +| Auto-show on state changes? | Yes — stateChange + trackStateChange events | |
| 110 | +| New state or reuse `focused`? | New `controlsVisible` boolean, orthogonal to `focused` | |
| 111 | +| MediaCallHeader location? | Stays at app root, subscribes to store independently | |
| 112 | +| Tap target? | Center CallerInfo area only (not buttons) | |
| 113 | + |
| 114 | +## References |
| 115 | + |
| 116 | +- PRD: `prd-controls-animation.md` |
| 117 | +- Plan: `.claude/plans/calm-brewing-fox.md` |
0 commit comments