Add viewport persistence and default offline map region support#427
Add viewport persistence and default offline map region support#427torlando-tech merged 4 commits intomainfrom
Conversation
Greptile SummaryThis PR adds two complementary features: viewport persistence (saving/restoring the map camera position across tab switches) and default offline map region support (using an offline region as the fallback map center when GPS is unavailable). The prior review feedback on debouncing camera saves and transaction-safe default region toggling has been addressed — camera saves now use
Confidence Score: 4/5
Important Files Changed
Flowchartflowchart TD
A[Map Initializes] --> B{Saved Camera Position?}
B -- Yes --> C[Use saved lat/lng/zoom]
B -- No --> D{GPS Location Available?}
D -- Yes --> E[Use GPS coords, zoom 15]
D -- No --> F{Default Offline Region?}
F -- Yes --> G[Use region center, zoom 12]
F -- No --> H["Fallback: 0,0 zoom 2 (world view)"]
C --> I[Set Camera Position]
E --> I
G --> I
H --> I
I --> J[Register onCameraIdle Listener]
J --> K[User pans/zooms map]
K --> L[Camera becomes idle]
L --> M[Save camera position to MapState]
M --> K
Last reviewed commit: e56843a |
| suspend fun setDefaultRegion(id: Long) { | ||
| offlineMapRegionDao.clearDefaultRegion() | ||
| offlineMapRegionDao.setDefaultRegion(id) | ||
| } |
There was a problem hiding this comment.
Race condition: clearDefaultRegion() and setDefaultRegion(id) are separate database operations without transaction wrapping. If another thread calls setDefaultRegion() between these two calls, both regions could end up marked as default.
Wrap both operations in a @Transaction annotation or use a single SQL UPDATE query.
Prompt To Fix With AI
This is a comment left during a code review.
Path: data/src/main/java/com/lxmf/messenger/data/repository/OfflineMapRegionRepository.kt
Line: 299:302
Comment:
Race condition: `clearDefaultRegion()` and `setDefaultRegion(id)` are separate database operations without transaction wrapping. If another thread calls `setDefaultRegion()` between these two calls, both regions could end up marked as default.
Wrap both operations in a `@Transaction` annotation or use a single SQL UPDATE query.
How can I resolve this? If you propose a fix, please make it concise.|
@greptileai review |
| * Get the region marked as default map center. | ||
| * If no region is explicitly marked, returns the first completed region (auto-default). | ||
| */ | ||
| suspend fun getDefaultRegion(): OfflineMapRegion? { | ||
| // Try explicitly marked default first | ||
| val explicit = offlineMapRegionDao.getDefaultRegion() | ||
| if (explicit != null) return explicit.toOfflineMapRegion() | ||
|
|
||
| // Fall back to first completed region (implicit default when only one exists) | ||
| return null | ||
| } |
There was a problem hiding this comment.
Misleading KDoc contradicts implementation
The docstring says "If no region is explicitly marked, returns the first completed region (auto-default)" but the fallback path just returns null. The comment on line 291 ("Fall back to first completed region") also contradicts the actual return null. This looks like a leftover from a previous iteration where auto-defaulting was planned but removed.
Either update the KDoc to reflect the actual behavior, or implement the described fallback.
| * Get the region marked as default map center. | |
| * If no region is explicitly marked, returns the first completed region (auto-default). | |
| */ | |
| suspend fun getDefaultRegion(): OfflineMapRegion? { | |
| // Try explicitly marked default first | |
| val explicit = offlineMapRegionDao.getDefaultRegion() | |
| if (explicit != null) return explicit.toOfflineMapRegion() | |
| // Fall back to first completed region (implicit default when only one exists) | |
| return null | |
| } | |
| /** | |
| * Get the region marked as default map center. | |
| * Returns null if no region is explicitly marked as default. | |
| */ | |
| suspend fun getDefaultRegion(): OfflineMapRegion? { | |
| return offlineMapRegionDao.getDefaultRegion()?.toOfflineMapRegion() | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: data/src/main/java/com/lxmf/messenger/data/repository/OfflineMapRegionRepository.kt
Line: 283:293
Comment:
**Misleading KDoc contradicts implementation**
The docstring says "If no region is explicitly marked, returns the first completed region (auto-default)" but the fallback path just returns `null`. The comment on line 291 ("Fall back to first completed region") also contradicts the actual `return null`. This looks like a leftover from a previous iteration where auto-defaulting was planned but removed.
Either update the KDoc to reflect the actual behavior, or implement the described fallback.
```suggestion
/**
* Get the region marked as default map center.
* Returns null if no region is explicitly marked as default.
*/
suspend fun getDefaultRegion(): OfflineMapRegion? {
return offlineMapRegionDao.getDefaultRegion()?.toOfflineMapRegion()
}
```
How can I resolve this? If you propose a fix, please make it concise.| // Load default offline map region center as fallback for initial map position | ||
| viewModelScope.launch { | ||
| val defaultRegion = offlineMapRegionRepository.getDefaultRegion() | ||
| if (defaultRegion != null) { | ||
| _state.update { | ||
| it.copy( | ||
| defaultRegionCenter = SavedCameraPosition( | ||
| latitude = defaultRegion.centerLatitude, | ||
| longitude = defaultRegion.centerLongitude, | ||
| zoom = 12.0, | ||
| ), | ||
| ) | ||
| } | ||
| Log.d(TAG, "Default region loaded: ${defaultRegion.name} at ${defaultRegion.centerLatitude}, ${defaultRegion.centerLongitude}") | ||
| } | ||
| } |
There was a problem hiding this comment.
defaultRegionCenter is loaded once and never refreshed
The default region center is loaded once in init and stored in MapState. If the user navigates to the Offline Maps screen and changes the default region, MapViewModel.state.defaultRegionCenter will still hold the old value (or remain null if no default was set at startup). Since MapViewModel is @HiltViewModel and persists across tab switches, this value won't update until the next app launch.
This matters when the map initializes without a saved camera position and without GPS — it would center on the old default (or 0,0) instead of the newly selected one. Consider observing the default region reactively, or re-loading it when the map screen is resumed.
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/main/java/com/lxmf/messenger/viewmodel/MapViewModel.kt
Line: 183:198
Comment:
**`defaultRegionCenter` is loaded once and never refreshed**
The default region center is loaded once in `init` and stored in `MapState`. If the user navigates to the Offline Maps screen and changes the default region, `MapViewModel.state.defaultRegionCenter` will still hold the old value (or remain `null` if no default was set at startup). Since `MapViewModel` is `@HiltViewModel` and persists across tab switches, this value won't update until the next app launch.
This matters when the map initializes without a saved camera position and without GPS — it would center on the old default (or 0,0) instead of the newly selected one. Consider observing the default region reactively, or re-loading it when the map screen is resumed.
How can I resolve this? If you propose a fix, please make it concise.…333) When no GPS location is available, the map now uses the center coordinates of the user's default offline map region instead of hardcoding San Francisco. Changes: - Add isDefault column to offline_map_regions table (migration 35->36) - Add DAO queries to get/set/clear default region - Add star toggle UI on completed offline map cards to set default region - Show "Default" badge next to status on default region cards - Save/restore camera viewport across tab switches to prevent reset - Fall back to world view (0,0 zoom 2) when no location or default region https://claude.ai/code/session_01Ldx336WY2L1T4L34zpbQzM
…ra save - Wrap clearDefaultRegion + setDefaultRegionById in @transaction in DAO to prevent race condition where multiple regions could be marked default - Move saveCameraPosition from onCameraMoveListener to onCameraIdleListener to avoid excessive state updates during pan/zoom gestures https://claude.ai/code/session_01Ldx336WY2L1T4L34zpbQzM
- Capture delegated state.userLocation in a local val to enable smart cast - Use safe null-check on CameraPosition.target (nullable LatLng) - Suppress TooManyFunctions detekt warning on Room DAO interface Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
e56843a to
2f129be
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Constructor gained the new parameter after rebase conflict resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
This PR adds two related features to improve the map experience:
Key Changes
Map Viewport Persistence
SavedCameraPositiondata class to store latitude, longitude, and zoom levelMapScreento save camera position on every camera move viasaveCameraPosition()hasInitiallyCenteredlogic to skip re-centering when viewport is restored from saved stateDefault Offline Map Region
isDefaultboolean column toOfflineMapRegionEntity(database migration 35→36)OfflineMapRegionRepositorywith methods to get, set, and clear default regionsOfflineMapsScreento toggle default status via star icon (only for completed regions)ViewModel Updates
MapViewModelnow loads the default region center on initialization and uses it as fallbackOfflineMapsViewModelprovidessetDefaultRegion()andclearDefaultRegion()methodsdefaultRegionCenterandlastCameraPositiontoMapStateUI Improvements
OfflineMapRegionCardImplementation Details
https://claude.ai/code/session_01Ldx336WY2L1T4L34zpbQzM