diff --git a/Nuvolari/app/lib/features/map/radar_map_screen.dart b/Nuvolari/app/lib/features/map/radar_map_screen.dart index c4a641e..5978e5f 100644 --- a/Nuvolari/app/lib/features/map/radar_map_screen.dart +++ b/Nuvolari/app/lib/features/map/radar_map_screen.dart @@ -59,11 +59,16 @@ class _MapWithAttribution extends StatelessWidget { child: _RegionMap(region: region, style: style), ), // Outside the map rather than floating over it, so the credit can never - // be occluded by a map control or a gesture overlay. - AttributionBar( - region: region, - activeSourceIds: style.attributionIds, - showBaseMapNotice: style.kind == BaseMapKind.offlineFallback, + // be occluded by a map control. SafeArea keeps it clear of the system + // gesture bar as well — a credit sitting behind the navigation pill is + // a credit that is not being displayed. + SafeArea( + top: false, + child: AttributionBar( + region: region, + activeSourceIds: style.attributionIds, + showBaseMapNotice: style.kind == BaseMapKind.offlineFallback, + ), ), ], ); @@ -76,6 +81,29 @@ class _RegionMap extends StatelessWidget { final RegionConfig region; final MapStyle style; + /// Frames the whole region once the map is ready. + /// + /// The configured initial zoom is only a starting point: a single zoom number + /// cannot fit the region on both a small phone and a tablet, and on a 411dp + /// screen zoom 7.2 puts the viewport entirely inside the region, so the user + /// sees a flat expanse with no coastline, border or outline to orient by. + /// Fitting the bounds works on every screen size. + Future _frameRegion(MapLibreMapController controller) async { + final bounds = region.bounds; + await controller.moveCamera( + CameraUpdate.newLatLngBounds( + LatLngBounds( + southwest: LatLng(bounds.south, bounds.west), + northeast: LatLng(bounds.north, bounds.east), + ), + left: 16, + top: 16, + right: 16, + bottom: 16, + ), + ); + } + @override Widget build(BuildContext context) { final center = region.map.center; @@ -84,6 +112,7 @@ class _RegionMap extends StatelessWidget { return MapLibreMap( styleString: style.styleString, + onMapCreated: _frameRegion, initialCameraPosition: CameraPosition( target: LatLng(center.latitude, center.longitude), zoom: zoom.initial, diff --git a/Nuvolari/docs/roadmap.md b/Nuvolari/docs/roadmap.md index 0d77818..d11f162 100644 --- a/Nuvolari/docs/roadmap.md +++ b/Nuvolari/docs/roadmap.md @@ -54,10 +54,11 @@ network. The attribution bar lists only the sources actually rendered — credit OpenStreetMap while showing the fallback would be a false attribution — and sits below the map rather than floating over it so no map control can occlude it. -**Not visually verified.** This machine has no Android device and no emulator image, so -the map has been proven to build and its logic unit-tested, but nobody has watched it -render. Connect a phone over USB or install an emulator system image before trusting -the visual result. +**Verified on an emulator** (`nuvolari`, API 36.1, x86_64, GPU host). Running it caught +two things the unit tests could not: the configured initial zoom put the viewport +entirely inside the region so the map read as a blank expanse, and the attribution bar +sat behind the system gesture pill. Both fixed — the camera now fits the region bounds +at runtime, which works on any screen size, and the bar is inside a SafeArea. ## M3 — Animation and timeline 🔨 @@ -130,3 +131,32 @@ draft, store listing copy, Data safety draft, prominent disclosure for location. | M6 | Firebase project and `google-services.json` | | M8 | VPS / object storage endpoint and credentials | | M9 | Play Console account and upload keystore | + +--- + +## Verifying on the emulator + +An AVD named `nuvolari` is set up on this machine: API 36.1, `google_apis`, x86_64, +GPU passthrough to the host. It uses a 2 GB data partition and no SD card because disk +is tight. + +```bash +# Boot it +"$ANDROID_HOME/emulator/emulator" -avd nuvolari -no-audio -no-boot-anim -gpu host + +# Build, install, launch +cd app +flutter build apk --debug +adb install -r build/app/outputs/flutter-apk/app-debug.apk +adb shell am start -n it.nuvolari.app/.MainActivity + +# Capture what it looks like +adb exec-out screencap -p > screen.png +``` + +`flutter run -d emulator-5554` works too and gives hot reload; the steps above are what +a non-interactive session uses. + +> An unrelated AVD, `pixel_7_-_api_35`, is present but broken — its system image +> (`android-35/google_apis_playstore`) is not installed, so it cannot boot. It occupies +> about 4.8 GB. It predates this project and has been left alone.