From af8089ec425621d9404c933f789f6bfe62a34446 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 5 Nov 2023 01:01:24 +0100 Subject: [PATCH] #241 google maps refresh workarounds --- .../src/controller/controller_delegate.dart | 8 +- plugins/aves_services_google/lib/src/map.dart | 170 +++++++++++++----- 2 files changed, 129 insertions(+), 49 deletions(-) diff --git a/plugins/aves_magnifier/lib/src/controller/controller_delegate.dart b/plugins/aves_magnifier/lib/src/controller/controller_delegate.dart index 26955113c..8ab9907de 100644 --- a/plugins/aves_magnifier/lib/src/controller/controller_delegate.dart +++ b/plugins/aves_magnifier/lib/src/controller/controller_delegate.dart @@ -27,8 +27,8 @@ mixin AvesMagnifierControllerDelegate on State { final List _subscriptions = []; void registerDelegate(AvesMagnifier widget) { - _subscriptions.add(widget.controller.stateStream.listen(_onMagnifierStateChange)); - _subscriptions.add(widget.controller.scaleStateChangeStream.listen(_onScaleStateChange)); + _subscriptions.add(widget.controller.stateStream.listen(_onMagnifierStateChanged)); + _subscriptions.add(widget.controller.scaleStateChangeStream.listen(_onScaleStateChanged)); } void unregisterDelegate(AvesMagnifier oldWidget) { @@ -38,7 +38,7 @@ mixin AvesMagnifierControllerDelegate on State { ..clear(); } - void _onScaleStateChange(ScaleStateChange scaleStateChange) { + void _onScaleStateChanged(ScaleStateChange scaleStateChange) { if (scaleStateChange.source == ChangeSource.internal) return; if (!controller.hasScaleSateChanged) return; @@ -66,7 +66,7 @@ mixin AvesMagnifierControllerDelegate on State { _animateScale = animateScale; } - void _onMagnifierStateChange(MagnifierState state) { + void _onMagnifierStateChanged(MagnifierState state) { final boundaries = scaleBoundaries; if (boundaries == null) return; diff --git a/plugins/aves_services_google/lib/src/map.dart b/plugins/aves_services_google/lib/src/map.dart index 10887217e..cd306c546 100644 --- a/plugins/aves_services_google/lib/src/map.dart +++ b/plugins/aves_services_google/lib/src/map.dart @@ -66,12 +66,13 @@ class _EntryGoogleMapState extends State> with WidgetsBindi ZoomedBounds get bounds => boundsNotifier.value; static const uninitializedLatLng = LatLng(0, 0); + static const boundInitDelay = Duration(milliseconds: 100); @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - _sizeNotifier.addListener(_onSizeChange); + _sizeNotifier.addListener(_onSizeChanged); _registerWidget(widget); } @@ -140,21 +141,31 @@ class _EntryGoogleMapState extends State> with WidgetsBindi } Widget _buildMap() { - final _onMarkerLongPress = widget.onMarkerLongPress; return StreamBuilder( stream: _markerBitmapReadyStreamController.stream, builder: (context, _) { - final markers = {}; + final mediaMarkers = {}; _geoEntryByMarkerKey.forEach((markerKey, geoEntry) { final bytes = _markerBitmaps[markerKey]; if (bytes != null) { final point = LatLng(geoEntry.latitude!, geoEntry.longitude!); - markers.add(Marker( + mediaMarkers.add(Marker( markerId: MarkerId(geoEntry.markerId!), consumeTapEvents: true, icon: BitmapDescriptor.fromBytes(bytes), position: point, onTap: () => widget.onMarkerTap?.call(geoEntry), + // TODO TLAD [map] GoogleMap.onLongPress is not appropriate for mediaMarkers, so the call should be here when this is fixed: https://github.com/flutter/flutter/issues/107148 + // onLongPress: widget.onMarkerLongPress != null + // ? (v) { + // final pressLocation = _fromServiceLatLng(v); + // final mediaMarkers = _geoEntryByMarkerKey.values.toSet(); + // final geoEntry = ImageMarker.markerMatch(pressLocation, bounds.zoom, mediaMarkers); + // if (geoEntry != null) { + // widget.onMarkerLongPress?.call(geoEntry, pressLocation); + // } + // } + // : null, )); } }); @@ -170,7 +181,8 @@ class _EntryGoogleMapState extends State> with WidgetsBindi return LayoutBuilder( builder: (context, constraints) { _sizeNotifier.value = constraints.biggest; - return GoogleMap( + return _GoogleMap( + dotLocationNotifier: widget.dotLocationNotifier ?? ValueNotifier(null), initialCameraPosition: CameraPosition( bearing: -bounds.rotation, target: _toServiceLatLng(bounds.projectedCenter), @@ -179,30 +191,17 @@ class _EntryGoogleMapState extends State> with WidgetsBindi onMapCreated: (controller) async { _serviceMapController = controller; final zoom = await controller.getZoomLevel(); + // the visible region is sometimes incorrect when queried right after creation, + await Future.delayed(boundInitDelay); await _updateVisibleRegion(zoom: zoom, rotation: bounds.rotation); - if (mounted) { - setState(() {}); - } + // `onCameraIdle` is not always automatically triggered following map creation + _onIdle(); }, - // compass disabled to use provider agnostic controls - compassEnabled: false, - mapToolbarEnabled: false, mapType: _toMapType(widget.style), minMaxZoomPreference: MinMaxZoomPreference(widget.minZoom, widget.maxZoom), - rotateGesturesEnabled: true, - scrollGesturesEnabled: interactive, - // zoom controls disabled to use provider agnostic controls - zoomControlsEnabled: false, - zoomGesturesEnabled: interactive, - // lite mode disabled because it lacks camera animation - liteModeEnabled: false, - // tilt disabled to match leaflet - tiltGesturesEnabled: false, - myLocationEnabled: false, - myLocationButtonEnabled: false, + interactive: interactive, markers: { - // TODO TLAD workaround for dot location marker not showing the last value until this is fixed: https://github.com/flutter/flutter/issues/103686 - ...markers, + ...mediaMarkers, if (dotLocation != null && _dotMarkerBitmap != null) Marker( markerId: const MarkerId('dot'), @@ -225,16 +224,6 @@ class _EntryGoogleMapState extends State> with WidgetsBindi onCameraMove: (position) => _updateVisibleRegion(zoom: position.zoom, rotation: -position.bearing), onCameraIdle: _onIdle, onTap: (v) => widget.onMapTap?.call(_fromServiceLatLng(v)), - onLongPress: _onMarkerLongPress != null - ? (v) { - final pressLocation = _fromServiceLatLng(v); - final markers = _geoEntryByMarkerKey.values.toSet(); - final geoEntry = ImageMarker.markerMatch(pressLocation, bounds.zoom, markers); - if (geoEntry != null) { - _onMarkerLongPress(geoEntry, pressLocation); - } - } - : null, ); }, ); @@ -248,11 +237,9 @@ class _EntryGoogleMapState extends State> with WidgetsBindi // sometimes the map does not properly update after changing the widget size, // so we monitor the size and force refreshing after an arbitrary small delay - // TODO TLAD [map] this workaround no longer works with Flutter beta v3.3.0-0.0.pre - Future _onSizeChange() async { - await Future.delayed(const Duration(milliseconds: 100)); - debugPrint('refresh map for size=${_sizeNotifier.value}'); - await _serviceMapController?.setMapStyle(null); + Future _onSizeChanged() async { + await Future.delayed(boundInitDelay); + _onIdle(); } void _onIdle() { @@ -278,12 +265,6 @@ class _EntryGoogleMapState extends State> with WidgetsBindi zoom: zoom, rotation: rotation, ); - } else { - // the visible region is sometimes uninitialized when queried right after creation, - // so we query it again next frame - WidgetsBinding.instance.addPostFrameCallback((_) { - _updateVisibleRegion(zoom: zoom, rotation: rotation); - }); } } @@ -345,3 +326,102 @@ class GmsGeoTiffTileProvider extends TileProvider { return TileProvider.noTile; } } + +class _GoogleMap extends StatefulWidget { + final ValueNotifier? dotLocationNotifier; + final CameraPosition initialCameraPosition; + final MapCreatedCallback? onMapCreated; + final MapType mapType; + final MinMaxZoomPreference minMaxZoomPreference; + final bool interactive; + final Set markers; + final Set tileOverlays; + final CameraPositionCallback? onCameraMove; + final VoidCallback? onCameraIdle; + final ArgumentCallback? onTap; + + const _GoogleMap({ + required this.dotLocationNotifier, + required this.initialCameraPosition, + required this.onMapCreated, + required this.mapType, + required this.minMaxZoomPreference, + required this.interactive, + required this.markers, + required this.tileOverlays, + required this.onCameraMove, + required this.onCameraIdle, + required this.onTap, + }); + + @override + State<_GoogleMap> createState() => _GoogleMapState(); +} + +class _GoogleMapState extends State<_GoogleMap> { + @override + void initState() { + super.initState(); + _registerWidget(widget); + } + + @override + void didUpdateWidget(covariant _GoogleMap oldWidget) { + super.didUpdateWidget(oldWidget); + _unregisterWidget(oldWidget); + _registerWidget(widget); + } + + @override + void dispose() { + _unregisterWidget(widget); + super.dispose(); + } + + void _registerWidget(_GoogleMap widget) { + widget.dotLocationNotifier?.addListener(_onDotLocationChanged); + } + + void _unregisterWidget(_GoogleMap widget) { + widget.dotLocationNotifier?.removeListener(_onDotLocationChanged); + } + + // TODO TLAD [map] remove when this is fixed: https://github.com/flutter/flutter/issues/103686 + Future _onDotLocationChanged() async { + // workaround for dot location marker not always reflecting the current location, + // despite `ValueListenableBuilder` on `widget.dotLocationNotifier` + await Future.delayed(const Duration(milliseconds: 100)); + if (mounted) { + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + return GoogleMap( + initialCameraPosition: widget.initialCameraPosition, + onMapCreated: widget.onMapCreated, + // compass disabled to use provider agnostic controls + compassEnabled: false, + mapToolbarEnabled: false, + mapType: widget.mapType, + minMaxZoomPreference: widget.minMaxZoomPreference, + rotateGesturesEnabled: true, + scrollGesturesEnabled: widget.interactive, + // zoom controls disabled to use provider agnostic controls + zoomControlsEnabled: false, + zoomGesturesEnabled: widget.interactive, + // lite mode disabled because it lacks camera animation + liteModeEnabled: false, + // tilt disabled to match leaflet + tiltGesturesEnabled: false, + myLocationEnabled: false, + myLocationButtonEnabled: false, + markers: widget.markers, + tileOverlays: widget.tileOverlays, + onCameraMove: widget.onCameraMove, + onCameraIdle: widget.onCameraIdle, + onTap: widget.onTap, + ); + } +}