This tracking document breaks the redesign into concrete, code-aware tasks with checkboxes. It’s organized into phases to keep the app working while we migrate gradually. Larger items are split into small, actionable tasks tied to specific files/classes.
Legend
- = Todo
- = Done
Process
- After completing (and ticking) each checkbox or small sub-step, create a focused commit describing exactly what changed (e.g., "map: fix MapEventListener OnMapClick wiring" or "map: add lifecycle observer for MapSensorController"). Keep commits small and logically isolated to ease review and potential rollback.
Stabilize current map to reduce risk before introducing v2.
Files: map/src/main/java/com/adsamcik/tracker/map/MapEventListener.kt
- Fix incorrect plus/minus assign conditions for OnMapClickListener.
- Add unit test verifying add/remove wiring and isolation of other listeners.
Acceptance
- Adding/removing last OnMapClickListener properly sets/unsets GoogleMap.onMapClickListener.
- No unrelated listeners are affected by click listener removal.
Files: map/.../MapSensorController.kt, map/.../fragment/FragmentMap.kt, map/.../MapOwner.kt
- Add lifecycle observer (onStart/onStop) for MapSensorController.
- Register/unregister sensors + location in onStart/onStop.
- Connect observer from FragmentMap when map is ready.
Currently both MapOwner enable/disable listeners and the lifecycle observer could trigger enable/disable. Consolidated to lifecycle observer only.
- Choose lifecycle observer as single authority.
- Remove MapOwner enable/disable wiring for sensors.
- Add idempotence guard + debug log.
Acceptance
- Only one code path invokes MapSensorController enable/disable (verified via log).
- No duplicate sensor or location requests after rapid pause/resume cycles.
- Following mode resumes after app resume and cancels on user camera move (test + manual QA).
Additional Verification
- No retained SensorManager callbacks after onStop (LeakCanary clean).
- Following/bearing resume reliably with permission granted.
Files: map/.../MapController.kt, map/.../heatmap/HeatmapTileProvider.kt
- Create
map/src/main/java/com/adsamcik/tracker/map/MapConstants.ktwithconst val MAX_ZOOM = 17f. - Replace
MapController.MAX_ZOOMusages withMapConstants.MAX_ZOOMin:-
MapController(setMaxZoomPreference) -
HeatmapTileProvider(MAX_HEAT_ZOOM)
-
- Remove
MAX_ZOOMfromMapController.Companionwhen references are updated.- Also updated additional usages in heatmap tile creators (not originally listed) to ensure compilation.
Acceptance
- Build compiles and runtime behavior unchanged.
Introduce state/registry alongside existing implementation.
Files (new): map/src/main/java/com/adsamcik/tracker/map/v2/presentation/MapViewModel.kt
- Create
MapViewModelwithStateFlowholding immutableMapState:- selectedLayerId, dateRange, quality, followMode, userLocation, searchQuery, searchResults, isLoading, error, bottomSheetState, layerParameters, tileGenerationProgress.
- Add debounced refresh utility inside ViewModel for parameter changes.
Acceptance
- ViewModel compiles; can be instantiated in tests.
Files (new): smap/.../shared/map/v2/layers/{LayerDescriptor.kt, LayerCapabilities.kt, LayerRecipe.kt, LayerParameter.kt}
Files (new): map/.../v2/layers/registry/{LayerRegistry.kt, DefaultLayerRegistry.kt}
- Define
LayerDescriptor,LayerCapabilities,LayerRecipe,LayerParameter,LayerFactoryin sharedsmapto be accessible by other modules. - Implement
LayerRegistryinterface andDefaultLayerRegistrywith manual registration. - Register descriptors mirroring current layers from
MapSheetControllerlist:- NoMap, Location Heatmap, Cell Heatmap, Wifi Heatmap, Wifi Count Heatmap, Location Polyline, Speed Heatmap.
- For now, factories can return wrapper adapters to existing
MapLayerLogicimplementations (v1) to keep parity.
Acceptance
-
DefaultLayerRegistry.getAllLayers()returns descriptors for all current layers with names/icons wired to existing resources.
Files (new): map/.../v2/ui/{MapHost.kt, LayerController.kt}
-
MapHost: lifecycle-safe wrapper aroundSupportMapFragmentusingMapOwnerpatterns; exposesmapReadyobservable. -
LayerController: minimal wrapper that enables/disables a selected layer (using descriptor factory) on the map. - Do not change UI yet; keep v1
MapSheetControlleractive.
Acceptance
- Able to initialize
MapHostfromFragmentMapwithout disturbing v1 flow. (Compilation-only scaffold; wiring deferred to 2.4.)
Files: map/.../fragment/FragmentMap.kt
- Instantiate
MapViewModelandMapHostinonViewCreated(behind a dev flag or no-op collectors). - Observe ViewModel state but don’t drive UI yet.
Acceptance
- App runs as before; no visual changes.
Add flexible query support without refactoring all DAOs.
Files (new): shared.base.database/.../dao/UnifiedGeoDao.kt (module where AppDatabase lives)
- Add
@RawQuery(observedEntities=[LocationData::class]) fun queryLocations(q: SupportSQLiteQuery): Flow<List<GeoFeatureEntity>>. (Implemented asDatabaseLocationobserved; wifi/cell placeholders commented.) - Add similar for Wifi and Cell entities if available:
WifiData,CellLocation. - Add weighted variants returning
GeoWeightedFeatureEntity(lat, lon, time, weight) for location, wifi, cell. - Ensure
AppDatabaseexposes the new DAO.
Files (new): shared.base.database/.../entity/GeoFeatureEntity.kt
-
GeoFeatureEntity(lat: Double, lon: Double, time: Long, properties: Map<String, Double>)stored as JSON (map currently used for future queries; empty by default). - Introduce
@TypeConverterto serialize/deserialize the map; verify no conflicting converters.
Files (new): map/.../v2/data/SafeQueryBuilder.kt
- Implement safe, parameterized SQL builder with allowed columns per data source; support bounds and optional time range.
- Unit tests for invalid columns/predicates.
Files (new): map/.../v2/data/GeoRepositoryImpl.kt
- Implement
GeoRepository.query(query: GeoQuery): Flow<List<GeoFeature>>usingUnifiedGeoDaoandSafeQueryBuilder(supports weighted queries). - Implement
queryWeighted(query, weightColumn)convenience API returningWeightedGeoFeaturelist. - Add client-side grid aggregation (
queryWeightedAggregated) with Sum/Avg/Max strategies. - Implement
queryWeighted(...)for tiled/weighted use-cases.
Files: map/.../heatmap/creators/LocationHeatmapTileCreator.kt
- Introduce dev flag to fetch via
GeoRepositoryfor a small, controlled scenario (Location heatmap); falls back to DAO when disabled.
Acceptance
- Unit tests green for builder and repository; v1 creators still work.
Make performance limits explicit and centralized.
Files (new): map/.../v2/perf/PerformanceManager.kt
- Provide budgets by quality (Low/Medium/High): maxPoints, maxPolylinePoints, maxCacheSize, tileRenderTimeout, decimationThreshold, batchSize.
Files (new): map/.../v2/graphics/BitmapPool.kt
- Implement simple bounded pool with
acquire(width,height)andrelease(bitmap).
Files (new): map/.../v2/graphics/PolylineOptimizer.kt
- Implement Douglas-Peucker and even spacing; API
(points, tolerance, maxPoints) -> List<LatLng>.
Files: map/.../heatmap/HeatmapTileProvider.kt
- Add optional injection/setter for
BitmapPool. - Add in-memory LRU tile cache (keyed by x,y,zoom) with bounded size from
PerformanceManager. - Enforce per-tile render timeout to avoid long stalls; fail fast to
NO_TILEon timeout. - Review locking around tile generation; reduce contention to render-critical sections only.
- Add low-memory trim path (clear tile cache + bitmap pool via FragmentMap.onLowMemory).
Acceptance
- Manual smoke: panning/zooming heavy areas shows no visible jank; median tile render < 100ms on dev device.
Introduce base classes and migrate layers incrementally.
Files (new): map/.../v2/layers/base/{BaseMapLayer.kt, HeatmapLayer.kt}
- Implement template method:
enable()callsbeforeEnable -> loadData -> processData -> render -> afterEnable. - Integrate
PerformanceManagerin processing.
Files (new): map/.../v2/tiles/OptimizedTileProvider.kt
- Implement tile provider using
BitmapPool, LRU cache, and safe rendering.
Files: create new LocationHeatmapLayer in map/.../v2/layers/impl/LocationHeatmapLayer.kt
- Use
GeoRepositoryfor data load; grid aggregation for performance. (Scaffolded; tile provider stub returns NO_TILE until full render path is wired.) - Render via
OptimizedTileProvider. (Provider created, quality propagation wired.) - Provide descriptor with parameters (date range, quality, etc.). (Deferred to 5.4 where the UI reads v2 descriptors.)
Files: map/.../MapSheetController.kt
- Add new adapter (or branch) that renders from
LayerRegistrydescriptors. - Keep existing hard-coded list for fallback; guard with dev toggle.
- When a descriptor is selected, use
LayerControllerto enable v2 layer.
Acceptance
- Location Heatmap works through v2 flow without regressions; switching layers tears down overlays cleanly.
Notes
- A dev flag
USE_V2_LAYER_LISTwas added to guard the new list; default remains legacy. - Registry currently adapts to v1
MapLayerLogicfactories to avoid behavior change; wiring v2LocationHeatmapLayerunder a flag can follow. - Follow-ups (nice-to-have): persist last-selected descriptor ID for v2 list; mirror tile-generation counter UI for v2
LayerControllerpath.
Files:
-
WifiHeatmapLogic->WifiHeatmapLayer -
WifiCountHeatmapLogic->WifiCountHeatmapLayer -
CellHeatmapLogic->CellHeatmapLayer -
SpeedHeatmapLogic->SpeedHeatmapLayer -
LocationPolylineLogic->LocationPathLayer(with decimation) -
Implement per-layer recipes and descriptors; port legends and colors from
MapLayerData(via v2 adapters; direct v2 descriptor props to follow in cleanup). -
Quick visual QA on overlays and legends (parity is not required, just acceptable visuals).
Acceptance
- All v1
MapLayerLogicfeatures are available in v2 with acceptable visuals. Cleanup/removal will proceed in 5.6 without strict parity.
Purpose: simplify codebase by making the new layer system the only path and completely removing legacy.
Files: map/.../MapSheetController.kt, map/.../ui/LayerController.kt, map/.../layers/registry/DefaultLayerRegistry.kt, smap/.../shared/map/MapLayerLogic.kt, smap/.../shared/map/MapLayer.kt, heatmap creators/providers if unused
- Make descriptors list the default; remove legacy v1 list branch.
- Replace descriptor factories to return new layers directly (drop adapters casting to MapLayerLogic).
- Update
LayerControllerto hold and control new layer types only. - Migrate legend/title/icon/colors into descriptors; stop reading from v1 layer data.
- Remove adapters in
map/layers/adapters/*(empty directories removed). - COMPLETE REMOVAL: Remove
MapLayerLogicinterface entirely (no backwards compatibility). - COMPLETE REMOVAL: Remove
MapLayercompatibility interface. - Remove legacy heatmap tile creators/providers that are no longer used.
- Remove dev flags around v2 path and delete obsolete toggles.
- Drop the "v2" naming: move packages from
.../map/v2/...to.../map/..., and rename classes to neutral names. Update imports/usages accordingly. - COMPLETE CUTOVER: Update
MapLayerInfoto use string class names instead of Class references. - Compile + smoke test map layer switching and overlays.
Acceptance
- App uses new layer system only; build green; overlays mount/unmount; NO backwards compatibility - all legacy v1 references completely removed.
Files: map/src/test/...
-
MapViewModelTest: selection, param updates, debounced refresh. -
SafeQueryBuilderTest: allowed columns, parameterization, predicate safety. -
PerformanceManagerTest: budget selection. -
PolylineOptimizerTest: reductions to within budget and tolerance.
Note
- During this phase, release unit tests for the
mapmodule are skipped to avoid R8/minify issues with mocks. A follow-up will re-enable them with appropriate keep rules.
Files (new): map/.../v2/test/tiles/TileTestHarness.kt
- Introduce headless tile harness utility
TileTestHarness(unit tests) for exercisingOptimizedTileProvider. - Add minimal smoke test using a fake provider to validate harness wiring (no golden comparison yet).
- Golden image comparisons with tolerance for selected tiles/layers (enable later via Robolectric or move to androidTest).
- Notes: Harness currently avoids Robolectric to keep CI lean; golden tests deferred.
- Layer switch smoke test ensuring overlays mount/unmount and legends update.
- Added
LayerSwitchIntegrationTestunder androidTest with a fake layer and descriptor wiring; verifies render callback on switch and legend changes viaLayerController.
- Added
- Eliminate v1 usage from map flow:
LayerControllerandDefaultLayerRegistrynow use v2 layers only; legends come from descriptors. - Physical deletion of legacy v1 interfaces and unused heatmap pipeline classes:
- Deleted:
smap/shared/map/MapLayerLogic.kt,MapLayer.kt - Deleted:
map/heatmap/HeatmapTileProvider.kt - Deleted:
map/heatmap/creators/directory and all legacy creator classes - Retained: minimal support classes (
HeatmapConfig,HeatmapTileData, WiFi constants) needed by v2 layers
- Deleted:
- Updated v2 layer imports and fixed compilation issues after cleanup.
- COMMITTED: Changes committed to git with cleanup documentation (30 files changed, 165 insertions, 952 deletions)
- CONNECTED TEST: LayerSwitchIntegrationTest passed on Medium_Phone_API_36.0(AVD) - layer switching works correctly after cleanup
- DEPRECATION CLEANUP: Removed deprecated JCenter repository references from build files - no more deprecation warnings
Acceptance
- App uses v2 path by default; map and app modules assemble; unit tests pass for map module.
- Connected Android test passes, confirming layer switching functionality works in runtime environment.
- Fragment and controllers
FragmentMap: sets upMapController,MapSensorController, andMapSheetController; usesMapOwnerlifecycle hooks.MapOwner: abstracts map creation and enable/disable events.MapController: manages activeMapLayerLogic;MAX_ZOOM = 17fused and referenced byHeatmapTileProvider.MapSensorController: handles sensors and location updates; currently no lifecycle observer.MapSheetController: hard-codes layer list; controls legend and bottom sheet, search/geocoder.
- Layers v1
MapLayerLogicinterface insmap/.../shared/map/MapLayerLogic.ktwithonEnable/onDisable/update,availableRange/dateRange/quality, andtileCountInGenerationLiveData.- Heatmaps implemented via
HeatmapLayerLogicbase class and tile creators (LocationHeatmapTileCreator,WifiHeatmapTileCreator,CellHeatmapTileCreator,SpeedHeatmapTileCreator). LocationPolylineLogicuses DAOs (LocationDataDao,SessionDataDao) to draw polylines.
- Heatmap provider
HeatmapTileProviderimplements tile generation with internal concurrency and a heat-change mechanism; referencesMapController.MAX_ZOOMasMAX_HEAT_ZOOM.
- Known bug
MapEventListener.minusAssign(OnMapClickListener)incorrectly checksonCameraMoveListenersand unsetsOnCameraIdleListenerinstead ofOnMapClickListener.
- DI: No Hilt/Dagger present. We’ll start with manual
DefaultLayerRegistry; Hilt multibindings can be adopted later without blocking progress. - DAOs:
LocationDataDao,SessionDataDaoreferenced in map module; their definitions live in the shared database module (verify paths before implementingUnifiedGeoDao). - Feature flags: Prefer simple build-time or runtime toggles to switch between v1 and v2 during migration.
- Extensibility: Add a new layer by registering a descriptor only (no core edits).
- Performance: Median tile render < 100ms under normal datasets; no visible jank when panning/zooming.
- Quality: ≥80% unit test coverage for new v2 code; 0 critical leaks (LeakCanary).
- Parity: v2 covers all existing layers and features, including legends.