OfflineTerrainRepository
Owns the one downloaded offline-terrain region: its manifest, and the TerrainTileStore holding its tiles.
Not a Koin @Single. MapLibreMapViewProvider's own doc comment already states this module's rule — "free of any assumption about how the host app wires its graph" — and it turns out to bind harder than usual here: a @Module @ComponentScan in this package would only take effect once some host's Koin startup imports it, and every host that does that (androidApp/src/main's MainKoinModule) compiles into both Android flavors, so registering it there would pull a MapLibre-only type into the Google flavor's dependency graph, exactly what this module's own build.gradle.kts forbids. default is how every caller reaches the same instance instead — a plain lazily-initialized singleton, not a DI one.
Single region, not a list: see OfflineTerrainRegion's own doc comment for why. A new download therefore replaces whatever was there before — deleting the old tiles and manifest before fetching the first new one, not after. This is a known, deliberate tradeoff, not an oversight: there is nowhere to stage the new region side-by-side with the old one without doubling disk usage for the download's duration, so a download that later fails or is cancelled leaves nothing downloaded rather than restoring what was there — region reports null, the same as if nothing had ever been fetched.
Properties
The most recent state startDownload has reported, surviving a UI that goes away and comes back.
The current region, or null if nothing has been downloaded. Populated by refresh.
For reading tile bytes directly — org.meshtastic.feature.map.maplibre.layers.ContourLayer's own use.
Functions
Loads the manifest from disk if this is the first call. Idempotent, and cheap after the first: everything past that point reads region's already-loaded value. Callers that only ever read region from a composable should call this once from a LaunchedEffect; see TerrainLayers.
Starts a download on this repository's own scope and mirrors its emissions into downloadState, so a caller observes progress instead of collecting a Flow tied to its own lifecycle — see scope's own comment for why that distinction matters here. A no-op while a download is already running, closing the double-tap window a UI-side "is it downloading" check can't close on its own (the first TerrainDownloadState.InProgress only arrives after several tiles have already been fetched).
The file:// URL template rememberRasterDemTileSource (hillshade) can use to reach source's downloaded tiles directly, matching TerrainTileStore's own <baseDir>/<source>/<zoom>/<x>/<y>.webp layout.