diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-04-02 21:08:04 -1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-02 21:08:04 -1000 |
| commit | bf2223827c53fbc0e77d1af2a7d4654a7c248ee0 (patch) | |
| tree | ff3a4f5c4e9c0ec9bbefddee605821b1c80c92fd /docs/superpowers/specs | |
| parent | e9df7afa1d96fde80c482e497d7c17617c2d95c3 (diff) | |
feat(map): interactive map with auto-follow, recenter button, and UI immersive mode (#2)
* docs: add map interaction design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add map interaction implementation plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(map): add isFollowing state and gesture-driven manual mode to MapHandler
* feat(ui): add fab_recenter pill button to map layout
* feat(map): wire UI fade-out and recenter button to MapHandler.isFollowing
* fix(map): prevent fadeIn flash on cold start; consolidate fab_mob listener
* fix(map): preserve user zoom level on recenter
Capture the current camera zoom when the user gestures (entering manual
mode) and pass it back to centerOnLocation in recenter(), so tapping
Recenter returns to the user's chosen zoom rather than always snapping
to the default 14.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(map): capture lastZoom on camera idle, not gesture start
OnCameraMoveStartedListener fires before the gesture completes, so
it captured the pre-gesture zoom. OnCameraIdleListener fires after
the camera settles, giving the user's final intended zoom level.
Only update lastZoom while in manual mode (isFollowing=false).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(map): guard recenter against null island and add KDoc
- Skip recenter() if no GPS fix received (lastLat/lastLon still 0.0)
to avoid animating to 0°N, 0°E
- Add KDoc comment to recenter() consistent with other public methods
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers/specs')
| -rw-r--r-- | docs/superpowers/specs/2026-04-03-map-interaction-design.md | 59 |
1 files changed, 59 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-04-03-map-interaction-design.md b/docs/superpowers/specs/2026-04-03-map-interaction-design.md new file mode 100644 index 0000000..02e38e1 --- /dev/null +++ b/docs/superpowers/specs/2026-04-03-map-interaction-design.md @@ -0,0 +1,59 @@ +# Map Interaction Design +_2026-04-03_ + +## Overview + +Enable free map pan/zoom while keeping GPS auto-follow as the default. When the user gestures on the map, the UI hides and a Recenter button appears. Tapping Recenter restores auto-follow and the full UI. + +## State + +A single `isFollowing: Boolean` flag owned by `MapHandler`. Starts `true`. This is the only piece of new state. + +## Entering Manual Mode + +`MapLibreMap.addOnCameraMoveStartedListener` fires with `REASON_API_GESTURE` when the user initiates a pan, pinch-zoom, or rotate. On that event: + +- `MapHandler.isFollowing` → `false` +- `MapHandler` emits via a new `StateFlow<Boolean> isFollowing` exposed to MainActivity +- `MapHandler.centerOnLocation()` becomes a no-op while `!isFollowing` — GPS updates still arrive, last position is stored + +## Returning to Auto-Follow + +`fab_recenter` click handler: + +- Calls `MapHandler.recenter()` — sets `isFollowing = true`, calls `centerOnLocation(lastLat, lastLon)` +- `isFollowing` StateFlow emits `true` → MainActivity restores UI + +## UI Changes + +### New element: `fab_recenter` + +- Pill-shaped button (`wrap_content` width, 40dp height, 20dp corner radius) +- Label: "⊙ Recenter" +- Position: centered horizontally, `24dp` above the bottom of the map `ConstraintLayout` +- `android:visibility="gone"` by default +- Elevation: 20dp (above the instrument sheet) + +### Visibility toggling (MainActivity) + +When `isFollowing` → `false`: +- Fade out (alpha 0, 150ms): `instrument_bottom_sheet`, `bottom_navigation`, `fab_mob`, `fab_record_track` +- Show `fab_recenter` (visibility VISIBLE, fade in 150ms) + +When `isFollowing` → `true`: +- Hide `fab_recenter` (fade out 150ms, then GONE) +- Fade in (alpha 1, 150ms): `instrument_bottom_sheet`, `bottom_navigation`, `fab_mob`, `fab_record_track` + +## Files to Change + +| File | Change | +|------|--------| +| `MapHandler.kt` | Add `isFollowing` StateFlow, `lastLat`/`lastLon` storage, `recenter()`, guard in `centerOnLocation()`, register `OnCameraMoveStartedListener` | +| `activity_main.xml` | Add `fab_recenter` pill button | +| `MainActivity.kt` | Observe `mapHandler.isFollowing`, animate UI in/out, wire `fab_recenter` click | + +## Out of Scope + +- Tapping the map (without gesture) to restore UI — not requested +- Timeout to auto-restore UI — not requested +- Zoom-level persistence across recenter — not requested |
