Skip to content

feat(maps): Google maps improvements for network and offline tilesources#4664

Merged
jamesarich merged 8 commits intomainfrom
feat/offline-maps
Feb 27, 2026
Merged

feat(maps): Google maps improvements for network and offline tilesources#4664
jamesarich merged 8 commits intomainfrom
feat/offline-maps

Conversation

@jamesarich
Copy link
Copy Markdown
Collaborator

@jamesarich jamesarich commented Feb 27, 2026

Implements offline maps (MBTiles) and remote data layers (KML/GeoJSON) with a focus on MAD best practices, leak prevention, and robust architecture.

Key Changes

1. Architectural & Performance Stability

  • Fixed Memory Leaks: Removed GoogleMap and Layer SDK dependencies from MapViewModel. Lifecycle management is now handled in the UI via MapEffect and DisposableEffect.
  • Resource Management: MBTilesProvider now implements AutoCloseable. ViewModel ensures SQLite connections are closed on source change or onCleared.
  • UDF Compliance: Refactored MapLayerItem into an immutable data class to ensure predictable state updates.

2. New Mapping Features

  • Offline Support: Added MBTiles integration with Google Maps to TMS coordinate translation.
  • Network Layers: Support for remote KML and GeoJSON layers with automatic format detection.
  • Dynamic Refresh: UI controls added to trigger re-fetching of network-hosted data.

3. Infrastructure & UI

  • Centralized Navigation: Refactored IntroNavGraph to use IntroViewModel logic, eliminating code duplication.
  • Internationalization: Migrated all hardcoded mapping strings to strings.xml.
  • Testability: Updated visibility modifiers to support cross-module unit testing of navigation logic.

4. Testing

  • MapViewModelTest: Verifies tile provider selection and layer detection.
  • MBTilesProviderTest: Robolectric tests verifying database interaction and coordinate mapping.
  • Intro Flow: Verified existing navigation tests remain green after refactoring.

Related Issues

Resolves #4404
Resolves #4517
Resolves #3448
Closes #2352
Closes #1788
Addresses #2714

This commit introduces the capability to use local MBTiles files as a custom map tile source, in addition to the existing support for URL-based tile providers.

Key changes include:

*   **MBTiles Provider:** A new `MBTilesProvider` class has been added to read map tiles directly from a local `.mbtiles` SQLite database file. It handles the conversion from Google Maps tile coordinates to the TMS standard used in MBTiles.

*   **Data Model Update:** The `CustomTileProviderConfig` data model has been extended with a `localUri` field to store the path to the MBTiles file.

*   **File Management:** The `MapViewModel` now includes logic to:
    *   Copy a user-selected MBTiles file into the app's internal storage to ensure persistent access.
    *   Generate a unique filename for the copied file to avoid conflicts.
    *   Delete the internal file when the corresponding custom map source is removed.

*   **UI Integration:**
    *   The "Custom Tile Provider Manager" screen now features an "Add Local MBTiles File" button, which uses the system's file picker.
    *   The manager list now differentiates between remote URL sources and local MBTiles files.

*   **Map Display Logic:**
    *   The `MapView` and `MapViewModel` have been updated to create either a `UrlTileProvider` or the new `MBTilesProvider` based on the configuration of the selected custom map source.
    *   When a custom tile provider is active, the base Google Map type is now set to `NONE` to prevent the base map from rendering underneath the custom tiles.

Signed-off-by: James Rich <[email protected]>
This commit introduces the ability to add KML map layers from a network URL. Users can now add, view, and refresh map layers hosted online.

- A "Network Map Layer" feature has been added, allowing users to input a name and a URL (HTTP/HTTPS) to load a KML file onto the map.
- These network layers are persisted across app restarts.
- A refresh button is now available in the map controls when a network layer is visible, allowing for a manual refresh of all visible network layers.
- Individual network layers can also be refreshed from the "Manage Layers" sheet. A loading indicator shows the refresh progress.
- The "Manage Layers" bottom sheet has been updated with a new "Add Network Layer" button and a dialog to input the layer's details.

Signed-off-by: James Rich <[email protected]>
… URLs

This commit adds support for the `{s}` placeholder in custom map tile server URLs, enabling load balancing across multiple subdomains (e.g., 'a', 'b', 'c').

The `MapViewModel` now cycles through a list of subdomains and replaces the `{s}` placeholder in the URL template. This distributes tile requests and can improve map loading performance. The hint text for the URL template has been updated to reflect this new capability.

Signed-off-by: James Rich <[email protected]>
…rkers

This commit refactors the map feature by removing the flavor-specific `MapsInitializer` and centralizing map-related logic within the `feature/map` module. The explicit call to `initializeMaps` from `MeshUtilApplication` has been removed.

Marker rendering has been improved to prioritize the user's node and favorite nodes. These nodes now have a higher `zIndex` to ensure they are always displayed on top of other nodes and clusters, preventing them from being obscured.

A minor bug in custom map layer loading from a network URL has been fixed by switching from `Okio.source` to `java.io.BufferedInputStream` to prevent potential stream closing issues.

**BREAKING CHANGE:** The flavor-specific `MapsInitializer.kt` files (`google` and `fdroid` variants) have been deleted. Map initialization is now expected to be handled within the map feature module itself or through a different mechanism. This simplifies the application's entry point but may require adjustments if other parts of the app relied on this explicit, flavor-based initialization.

Signed-off-by: James Rich <[email protected]>
Signed-off-by: James Rich <[email protected]>
…anist

This commit refactors the application's permission handling by replacing the `nordic-common-permissions` library with `com.google.accompanist.permissions`.

The `AppIntroductionScreen` has been updated to use Accompanist's `rememberPermissionState` and `rememberMultiplePermissionsState` for managing runtime permissions for notifications, Bluetooth, and location. This change simplifies the permission request flow, removing the previous state management logic and custom composables like `RequireBluetooth` and `RequireLocation`.

Additionally, the `maxSdkVersion` attribute has been removed from location permissions in `AndroidManifest.xml` to ensure compatibility with the new permission handling logic across all Android versions.

Signed-off-by: James Rich <[email protected]>
This commit introduces a ViewModel to manage the application introduction flow and modularizes the navigation logic.

Key changes include:
- **IntroViewModel**: Created a new ViewModel to encapsulate the navigation logic, specifically determining the next step in the flow based on granted permissions.
- **Navigation Refactoring**: Extracted the navigation graph into a standalone `introNavGraph` function. This improves readability of the `AppIntroductionScreen` and separates UI state hoisting from navigation structure.
- **Flow Adjustment**: Reordered the introduction sequence to follow a new hierarchy: Welcome -> Bluetooth -> Location -> Notifications -> Critical Alerts.
- **Testing**: Added unit tests for `IntroViewModel` to verify the navigation sequence logic.
- **Dependencies**: Updated `build.gradle.kts` to include testing libraries such as JUnit, MockK, and Robolectric, and enabled Android resource inclusion for unit tests.

Signed-off-by: James Rich <[email protected]>
This commit refactors the map layer lifecycle management and streamlines the introduction navigation flow.

Key changes include:
- **Map Layer Management**: Decoupled Google Maps `Layer` objects (KML/GeoJSON) from the `MapViewModel` to prevent leaking view-related dependencies. Layer state is now managed within the composition using a new `MapLayerOverlay` component, which utilizes `MapEffect` and `DisposableEffect` for proper resource cleanup.
- **MBTiles Support**: Enhanced `MBTilesProvider` by implementing `AutoCloseable` to ensure the underlying SQLite database is properly closed. The `MapViewModel` now caches the current tile provider to avoid redundant allocations.
- **Improved Layer Detection**: Network map layers now automatically detect GeoJSON format based on file extensions (.geojson, .json), defaulting to KML otherwise.
- **Intro Flow Refactoring**: Centralized navigation logic in `IntroViewModel` by exposing `getNextKey` and updating `IntroNavGraph` to use this logic consistently across screens.
- **Testing**: Added comprehensive unit tests for `MapViewModel` and `MBTilesProvider` using Robolectric and MockK.
- **UI/Resources**: Updated string resources for better clarity regarding "Network" and "Map" layers, and added error messages for MBTiles file operations.

```kotlin
// Example of the new Composable-based layer management
@composable
private fun MapLayerOverlay(layerItem: MapLayerItem, mapViewModel: MapViewModel) {
    MapEffect(layerItem.id, layerItem.isRefreshing) { map ->
        // Layer initialization and attachment to the map
    }
    DisposableEffect(layerItem.id) {
        onDispose { /* Cleanup layer from map */ }
    }
}
```

Signed-off-by: James Rich <[email protected]>
@github-actions github-actions bot added the enhancement New feature or request label Feb 27, 2026
@jamesarich jamesarich marked this pull request as ready for review February 27, 2026 14:20
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 15.36%. Comparing base (728ef0c) to head (8a4d8c9).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4664   +/-   ##
=======================================
  Coverage   15.36%   15.36%           
=======================================
  Files          83       83           
  Lines        4342     4342           
  Branches      734      734           
=======================================
  Hits          667      667           
  Misses       3551     3551           
  Partials      124      124           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jamesarich jamesarich added this pull request to the merge queue Feb 27, 2026
Merged via the queue into main with commit 0a6fcc8 Feb 27, 2026
11 checks passed
@jamesarich jamesarich deleted the feat/offline-maps branch February 27, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment