#241 google maps refresh workarounds

This commit is contained in:
Thibault Deckers 2023-11-05 01:01:24 +01:00
parent 2a424ca1e1
commit af8089ec42
2 changed files with 129 additions and 49 deletions

View file

@ -27,8 +27,8 @@ mixin AvesMagnifierControllerDelegate on State<AvesMagnifier> {
final List<StreamSubscription> _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<AvesMagnifier> {
..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<AvesMagnifier> {
_animateScale = animateScale;
}
void _onMagnifierStateChange(MagnifierState state) {
void _onMagnifierStateChanged(MagnifierState state) {
final boundaries = scaleBoundaries;
if (boundaries == null) return;

View file

@ -66,12 +66,13 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> 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<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
}
Widget _buildMap() {
final _onMarkerLongPress = widget.onMarkerLongPress;
return StreamBuilder(
stream: _markerBitmapReadyStreamController.stream,
builder: (context, _) {
final markers = <Marker>{};
final mediaMarkers = <Marker>{};
_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<T> extends State<EntryGoogleMap<T>> 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<T> extends State<EntryGoogleMap<T>> 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<T> extends State<EntryGoogleMap<T>> 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<T> extends State<EntryGoogleMap<T>> 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<void> _onSizeChange() async {
await Future.delayed(const Duration(milliseconds: 100));
debugPrint('refresh map for size=${_sizeNotifier.value}');
await _serviceMapController?.setMapStyle(null);
Future<void> _onSizeChanged() async {
await Future.delayed(boundInitDelay);
_onIdle();
}
void _onIdle() {
@ -278,12 +265,6 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> 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<ll.LatLng?>? dotLocationNotifier;
final CameraPosition initialCameraPosition;
final MapCreatedCallback? onMapCreated;
final MapType mapType;
final MinMaxZoomPreference minMaxZoomPreference;
final bool interactive;
final Set<Marker> markers;
final Set<TileOverlay> tileOverlays;
final CameraPositionCallback? onCameraMove;
final VoidCallback? onCameraIdle;
final ArgumentCallback<LatLng>? 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<void> _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,
);
}
}