#241 google maps refresh workarounds
This commit is contained in:
parent
2a424ca1e1
commit
af8089ec42
2 changed files with 129 additions and 49 deletions
|
@ -27,8 +27,8 @@ mixin AvesMagnifierControllerDelegate on State<AvesMagnifier> {
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
|
|
||||||
void registerDelegate(AvesMagnifier widget) {
|
void registerDelegate(AvesMagnifier widget) {
|
||||||
_subscriptions.add(widget.controller.stateStream.listen(_onMagnifierStateChange));
|
_subscriptions.add(widget.controller.stateStream.listen(_onMagnifierStateChanged));
|
||||||
_subscriptions.add(widget.controller.scaleStateChangeStream.listen(_onScaleStateChange));
|
_subscriptions.add(widget.controller.scaleStateChangeStream.listen(_onScaleStateChanged));
|
||||||
}
|
}
|
||||||
|
|
||||||
void unregisterDelegate(AvesMagnifier oldWidget) {
|
void unregisterDelegate(AvesMagnifier oldWidget) {
|
||||||
|
@ -38,7 +38,7 @@ mixin AvesMagnifierControllerDelegate on State<AvesMagnifier> {
|
||||||
..clear();
|
..clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onScaleStateChange(ScaleStateChange scaleStateChange) {
|
void _onScaleStateChanged(ScaleStateChange scaleStateChange) {
|
||||||
if (scaleStateChange.source == ChangeSource.internal) return;
|
if (scaleStateChange.source == ChangeSource.internal) return;
|
||||||
if (!controller.hasScaleSateChanged) return;
|
if (!controller.hasScaleSateChanged) return;
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ mixin AvesMagnifierControllerDelegate on State<AvesMagnifier> {
|
||||||
_animateScale = animateScale;
|
_animateScale = animateScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onMagnifierStateChange(MagnifierState state) {
|
void _onMagnifierStateChanged(MagnifierState state) {
|
||||||
final boundaries = scaleBoundaries;
|
final boundaries = scaleBoundaries;
|
||||||
if (boundaries == null) return;
|
if (boundaries == null) return;
|
||||||
|
|
||||||
|
|
|
@ -66,12 +66,13 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
|
||||||
ZoomedBounds get bounds => boundsNotifier.value;
|
ZoomedBounds get bounds => boundsNotifier.value;
|
||||||
|
|
||||||
static const uninitializedLatLng = LatLng(0, 0);
|
static const uninitializedLatLng = LatLng(0, 0);
|
||||||
|
static const boundInitDelay = Duration(milliseconds: 100);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_sizeNotifier.addListener(_onSizeChange);
|
_sizeNotifier.addListener(_onSizeChanged);
|
||||||
_registerWidget(widget);
|
_registerWidget(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,21 +141,31 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMap() {
|
Widget _buildMap() {
|
||||||
final _onMarkerLongPress = widget.onMarkerLongPress;
|
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
stream: _markerBitmapReadyStreamController.stream,
|
stream: _markerBitmapReadyStreamController.stream,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
final markers = <Marker>{};
|
final mediaMarkers = <Marker>{};
|
||||||
_geoEntryByMarkerKey.forEach((markerKey, geoEntry) {
|
_geoEntryByMarkerKey.forEach((markerKey, geoEntry) {
|
||||||
final bytes = _markerBitmaps[markerKey];
|
final bytes = _markerBitmaps[markerKey];
|
||||||
if (bytes != null) {
|
if (bytes != null) {
|
||||||
final point = LatLng(geoEntry.latitude!, geoEntry.longitude!);
|
final point = LatLng(geoEntry.latitude!, geoEntry.longitude!);
|
||||||
markers.add(Marker(
|
mediaMarkers.add(Marker(
|
||||||
markerId: MarkerId(geoEntry.markerId!),
|
markerId: MarkerId(geoEntry.markerId!),
|
||||||
consumeTapEvents: true,
|
consumeTapEvents: true,
|
||||||
icon: BitmapDescriptor.fromBytes(bytes),
|
icon: BitmapDescriptor.fromBytes(bytes),
|
||||||
position: point,
|
position: point,
|
||||||
onTap: () => widget.onMarkerTap?.call(geoEntry),
|
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(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
_sizeNotifier.value = constraints.biggest;
|
_sizeNotifier.value = constraints.biggest;
|
||||||
return GoogleMap(
|
return _GoogleMap(
|
||||||
|
dotLocationNotifier: widget.dotLocationNotifier ?? ValueNotifier(null),
|
||||||
initialCameraPosition: CameraPosition(
|
initialCameraPosition: CameraPosition(
|
||||||
bearing: -bounds.rotation,
|
bearing: -bounds.rotation,
|
||||||
target: _toServiceLatLng(bounds.projectedCenter),
|
target: _toServiceLatLng(bounds.projectedCenter),
|
||||||
|
@ -179,30 +191,17 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
|
||||||
onMapCreated: (controller) async {
|
onMapCreated: (controller) async {
|
||||||
_serviceMapController = controller;
|
_serviceMapController = controller;
|
||||||
final zoom = await controller.getZoomLevel();
|
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);
|
await _updateVisibleRegion(zoom: zoom, rotation: bounds.rotation);
|
||||||
if (mounted) {
|
// `onCameraIdle` is not always automatically triggered following map creation
|
||||||
setState(() {});
|
_onIdle();
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// compass disabled to use provider agnostic controls
|
|
||||||
compassEnabled: false,
|
|
||||||
mapToolbarEnabled: false,
|
|
||||||
mapType: _toMapType(widget.style),
|
mapType: _toMapType(widget.style),
|
||||||
minMaxZoomPreference: MinMaxZoomPreference(widget.minZoom, widget.maxZoom),
|
minMaxZoomPreference: MinMaxZoomPreference(widget.minZoom, widget.maxZoom),
|
||||||
rotateGesturesEnabled: true,
|
interactive: interactive,
|
||||||
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,
|
|
||||||
markers: {
|
markers: {
|
||||||
// TODO TLAD workaround for dot location marker not showing the last value until this is fixed: https://github.com/flutter/flutter/issues/103686
|
...mediaMarkers,
|
||||||
...markers,
|
|
||||||
if (dotLocation != null && _dotMarkerBitmap != null)
|
if (dotLocation != null && _dotMarkerBitmap != null)
|
||||||
Marker(
|
Marker(
|
||||||
markerId: const MarkerId('dot'),
|
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),
|
onCameraMove: (position) => _updateVisibleRegion(zoom: position.zoom, rotation: -position.bearing),
|
||||||
onCameraIdle: _onIdle,
|
onCameraIdle: _onIdle,
|
||||||
onTap: (v) => widget.onMapTap?.call(_fromServiceLatLng(v)),
|
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,
|
// 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
|
// 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> _onSizeChanged() async {
|
||||||
Future<void> _onSizeChange() async {
|
await Future.delayed(boundInitDelay);
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
_onIdle();
|
||||||
debugPrint('refresh map for size=${_sizeNotifier.value}');
|
|
||||||
await _serviceMapController?.setMapStyle(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onIdle() {
|
void _onIdle() {
|
||||||
|
@ -278,12 +265,6 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
|
||||||
zoom: zoom,
|
zoom: zoom,
|
||||||
rotation: rotation,
|
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;
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue