From 1b549a9526f328910ad8adc6bba20e7ef598cb28 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 10:24:05 +0100 Subject: [PATCH 1/6] remove package prefix from classes --- lib/flutter_map.dart | 2 +- ...ve_viewer.dart => map_interactive_viewer.dart} | 13 ++++++------- .../tile_provider/network_image_provider.dart | 15 +++++++-------- .../tile_provider/network_tile_provider.dart | 2 +- lib/src/map/camera/camera.dart | 2 +- lib/src/map/controller/internal.dart | 12 ++++++------ lib/src/map/controller/map_controller.dart | 4 ++-- .../{impl.dart => map_controller_impl.dart} | 4 ++-- lib/src/map/inherited_model.dart | 11 +++++------ lib/src/map/options/options.dart | 2 +- lib/src/map/widget.dart | 13 ++++++------- .../network_image_provider_test.dart | 13 +++++++------ 12 files changed, 45 insertions(+), 48 deletions(-) rename lib/src/gestures/{flutter_map_interactive_viewer.dart => map_interactive_viewer.dart} (98%) rename lib/src/map/controller/{impl.dart => map_controller_impl.dart} (95%) diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 907eee917..6cc6b6aea 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -49,8 +49,8 @@ export 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart'; export 'package:flutter_map/src/map/camera/camera.dart'; export 'package:flutter_map/src/map/camera/camera_constraint.dart'; export 'package:flutter_map/src/map/camera/camera_fit.dart'; -export 'package:flutter_map/src/map/controller/impl.dart'; export 'package:flutter_map/src/map/controller/map_controller.dart'; +export 'package:flutter_map/src/map/controller/map_controller_impl.dart'; export 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; export 'package:flutter_map/src/map/options/interaction.dart'; export 'package:flutter_map/src/map/options/options.dart'; diff --git a/lib/src/gestures/flutter_map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart similarity index 98% rename from lib/src/gestures/flutter_map_interactive_viewer.dart rename to lib/src/gestures/map_interactive_viewer.dart index 083deac33..e0873b1d0 100644 --- a/lib/src/gestures/flutter_map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -25,23 +25,22 @@ typedef InteractiveViewerBuilder = Widget Function( /// Applies interactions (gestures/scroll/taps etc) to the current [MapCamera] /// via the internal [controller]. -class FlutterMapInteractiveViewer extends StatefulWidget { +class MapInteractiveViewer extends StatefulWidget { final InteractiveViewerBuilder builder; - final FlutterMapInternalController controller; + final MapInternalController controller; - const FlutterMapInteractiveViewer({ + const MapInteractiveViewer({ super.key, required this.builder, required this.controller, }); @override - State createState() => - FlutterMapInteractiveViewerState(); + State createState() => MapInteractiveViewerState(); } -class FlutterMapInteractiveViewerState - extends State with TickerProviderStateMixin { +class MapInteractiveViewerState extends State + with TickerProviderStateMixin { static const int _kMinFlingVelocity = 800; static const _kDoubleTapZoomDuration = 200; static const doubleTapDelay = Duration(milliseconds: 250); diff --git a/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart b/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart index c98f81605..b3f84e4a7 100644 --- a/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart +++ b/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart @@ -11,8 +11,7 @@ import 'package:http/http.dart'; /// Note that specifying a [fallbackUrl] will prevent this image provider from /// being cached. @immutable -class FlutterMapNetworkImageProvider - extends ImageProvider { +class MapNetworkImageProvider extends ImageProvider { /// The URL to fetch the tile from (GET request) final String url; @@ -39,7 +38,7 @@ class FlutterMapNetworkImageProvider /// Supports falling back to a secondary URL, if the primary URL fetch fails. /// Note that specifying a [fallbackUrl] will prevent this image provider from /// being cached. - const FlutterMapNetworkImageProvider({ + const MapNetworkImageProvider({ required this.url, required this.fallbackUrl, required this.headers, @@ -48,7 +47,7 @@ class FlutterMapNetworkImageProvider @override ImageStreamCompleter loadImage( - FlutterMapNetworkImageProvider key, + MapNetworkImageProvider key, ImageDecoderCallback decode, ) { final chunkEvents = StreamController(); @@ -67,13 +66,13 @@ class FlutterMapNetworkImageProvider } @override - Future obtainKey( + Future obtainKey( ImageConfiguration configuration, ) => - SynchronousFuture(this); + SynchronousFuture(this); Future _loadAsync( - FlutterMapNetworkImageProvider key, + MapNetworkImageProvider key, StreamController chunkEvents, ImageDecoderCallback decode, { bool useFallback = false, @@ -101,7 +100,7 @@ class FlutterMapNetworkImageProvider @override bool operator ==(Object other) => identical(this, other) || - (other is FlutterMapNetworkImageProvider && + (other is MapNetworkImageProvider && fallbackUrl == null && url == other.url); diff --git a/lib/src/layer/tile_layer/tile_provider/network_tile_provider.dart b/lib/src/layer/tile_layer/tile_provider/network_tile_provider.dart index 208892787..5388abf1d 100644 --- a/lib/src/layer/tile_layer/tile_provider/network_tile_provider.dart +++ b/lib/src/layer/tile_layer/tile_provider/network_tile_provider.dart @@ -41,7 +41,7 @@ class NetworkTileProvider extends TileProvider { @override ImageProvider getImage(TileCoordinates coordinates, TileLayer options) => - FlutterMapNetworkImageProvider( + MapNetworkImageProvider( url: getTileUrl(coordinates, options), fallbackUrl: getTileFallbackUrl(coordinates, options), headers: headers, diff --git a/lib/src/map/camera/camera.dart b/lib/src/map/camera/camera.dart index edf731b59..520e6a438 100644 --- a/lib/src/map/camera/camera.dart +++ b/lib/src/map/camera/camera.dart @@ -81,7 +81,7 @@ class MapCamera { /// The camera of the closest [FlutterMap] ancestor. If this is called from a /// context with no [FlutterMap] ancestor null, is returned. static MapCamera? maybeOf(BuildContext context) => - FlutterMapInheritedModel.maybeCameraOf(context); + MapInheritedModel.maybeCameraOf(context); /// The camera of the closest [FlutterMap] ancestor. If this is called from a /// context with no [FlutterMap] ancestor a [StateError] will be thrown. diff --git a/lib/src/map/controller/internal.dart b/lib/src/map/controller/internal.dart index 6746c5ab8..0b4122b55 100644 --- a/lib/src/map/controller/internal.dart +++ b/lib/src/map/controller/internal.dart @@ -2,12 +2,12 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter_map/src/gestures/flutter_map_interactive_viewer.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; +import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:flutter_map/src/map/camera/camera_fit.dart'; -import 'package:flutter_map/src/map/controller/impl.dart'; +import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; import 'package:flutter_map/src/map/options/options.dart'; import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; import 'package:flutter_map/src/misc/point_extensions.dart'; @@ -16,11 +16,11 @@ import 'package:latlong2/latlong.dart'; /// This controller is for internal use. All updates to the state should be done /// by calling methods of this class to ensure consistency. -class FlutterMapInternalController extends ValueNotifier<_InternalState> { - late final FlutterMapInteractiveViewerState _interactiveViewerState; +class MapInternalController extends ValueNotifier<_InternalState> { + late final MapInteractiveViewerState _interactiveViewerState; late MapControllerImpl _mapControllerImpl; - FlutterMapInternalController(MapOptions options) + MapInternalController(MapOptions options) : super( _InternalState( options: options, @@ -31,7 +31,7 @@ class FlutterMapInternalController extends ValueNotifier<_InternalState> { /// Link the viewer state with the controller. This should be done once when /// the FlutterMapInteractiveViewerState is initialized. set interactiveViewerState( - FlutterMapInteractiveViewerState interactiveViewerState, + MapInteractiveViewerState interactiveViewerState, ) => _interactiveViewerState = interactiveViewerState; diff --git a/lib/src/map/controller/map_controller.dart b/lib/src/map/controller/map_controller.dart index 9ffb23bae..089622c8c 100644 --- a/lib/src/map/controller/map_controller.dart +++ b/lib/src/map/controller/map_controller.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:flutter_map/src/map/camera/camera_fit.dart'; -import 'package:flutter_map/src/map/controller/impl.dart'; +import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:flutter_map/src/map/widget.dart'; import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; @@ -31,7 +31,7 @@ abstract class MapController { /// from a context with no [FlutterMap] ancestor a [StateError] will be /// thrown. static MapController? maybeOf(BuildContext context) => - FlutterMapInheritedModel.maybeControllerOf(context); + MapInheritedModel.maybeControllerOf(context); /// The controller for the closest [FlutterMap] ancestor. If this is called /// from a context with no [FlutterMap] ancestor a [StateError] will be diff --git a/lib/src/map/controller/impl.dart b/lib/src/map/controller/map_controller_impl.dart similarity index 95% rename from lib/src/map/controller/impl.dart rename to lib/src/map/controller/map_controller_impl.dart index 900cacff6..b029692f6 100644 --- a/lib/src/map/controller/impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -14,12 +14,12 @@ import 'package:latlong2/latlong.dart'; /// should not be visible to the user (e.g. for setting the current camera or /// linking the internal controller). class MapControllerImpl implements MapController { - late FlutterMapInternalController _internalController; + late MapInternalController _internalController; final _mapEventStreamController = StreamController.broadcast(); MapControllerImpl(); - set internalController(FlutterMapInternalController internalController) { + set internalController(MapInternalController internalController) { _internalController = internalController; } diff --git a/lib/src/map/inherited_model.dart b/lib/src/map/inherited_model.dart index 1a3ff0979..a076d09cf 100644 --- a/lib/src/map/inherited_model.dart +++ b/lib/src/map/inherited_model.dart @@ -10,10 +10,10 @@ import 'package:flutter_map/src/map/options/options.dart'; /// Using an [InheritedModel] means dependent widgets will only rebuild when /// the aspect they reference is updated. @immutable -class FlutterMapInheritedModel extends InheritedModel<_FlutterMapAspect> { +class MapInheritedModel extends InheritedModel<_FlutterMapAspect> { final FlutterMapData data; - FlutterMapInheritedModel({ + MapInheritedModel({ super.key, required MapCamera camera, required MapController controller, @@ -29,8 +29,7 @@ class FlutterMapInheritedModel extends InheritedModel<_FlutterMapAspect> { BuildContext context, [ _FlutterMapAspect? aspect, ]) => - InheritedModel.inheritFrom(context, - aspect: aspect) + InheritedModel.inheritFrom(context, aspect: aspect) ?.data; static MapCamera? maybeCameraOf(BuildContext context) => @@ -43,12 +42,12 @@ class FlutterMapInheritedModel extends InheritedModel<_FlutterMapAspect> { _maybeOf(context, _FlutterMapAspect.options)?.options; @override - bool updateShouldNotify(FlutterMapInheritedModel oldWidget) => + bool updateShouldNotify(MapInheritedModel oldWidget) => data != oldWidget.data; @override bool updateShouldNotifyDependent( - covariant FlutterMapInheritedModel oldWidget, + covariant MapInheritedModel oldWidget, Set dependencies, ) { for (final dependency in dependencies) { diff --git a/lib/src/map/options/options.dart b/lib/src/map/options/options.dart index 900e1b8f0..3d5822b43 100644 --- a/lib/src/map/options/options.dart +++ b/lib/src/map/options/options.dart @@ -134,7 +134,7 @@ class MapOptions { /// The options of the closest [FlutterMap] ancestor. If this is called from a /// context with no [FlutterMap] ancestor, null is returned. static MapOptions? maybeOf(BuildContext context) => - FlutterMapInheritedModel.maybeOptionsOf(context); + MapInheritedModel.maybeOptionsOf(context); /// The options of the closest [FlutterMap] ancestor. If this is called from a /// context with no [FlutterMap] ancestor a [StateError] will be thrown. diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart index 622a4e5e2..2b5dae5fe 100644 --- a/lib/src/map/widget.dart +++ b/lib/src/map/widget.dart @@ -2,13 +2,13 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/gestures/flutter_map_interactive_viewer.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; +import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart'; import 'package:flutter_map/src/layer/general/translucent_pointer.dart'; -import 'package:flutter_map/src/map/controller/impl.dart'; import 'package:flutter_map/src/map/controller/internal.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; +import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; import 'package:flutter_map/src/map/options/options.dart'; import 'package:logger/logger.dart'; @@ -60,15 +60,14 @@ class _FlutterMapStateContainer extends State with AutomaticKeepAliveClientMixin { bool _initialCameraFitApplied = false; - late final FlutterMapInternalController _flutterMapInternalController; + late final MapInternalController _flutterMapInternalController; late MapControllerImpl _mapController; late bool _mapControllerCreatedInternally; @override void initState() { super.initState(); - _flutterMapInternalController = - FlutterMapInternalController(widget.options); + _flutterMapInternalController = MapInternalController(widget.options); _initializeAndLinkMapController(); WidgetsBinding.instance @@ -133,10 +132,10 @@ class _FlutterMapStateContainer extends State builder: (context, constraints) { _updateAndEmitSizeIfConstraintsChanged(constraints); - return FlutterMapInteractiveViewer( + return MapInteractiveViewer( controller: _flutterMapInternalController, builder: (context, options, camera) { - return FlutterMapInheritedModel( + return MapInheritedModel( controller: _mapController, options: options, camera: camera, diff --git a/test/layer/tile_layer/tile_provider/network_image_provider_test.dart b/test/layer/tile_layer/tile_provider/network_image_provider_test.dart index 53b22a30a..724e462e8 100644 --- a/test/layer/tile_layer/tile_provider/network_image_provider_test.dart +++ b/test/layer/tile_layer/tile_provider/network_image_provider_test.dart @@ -35,6 +35,7 @@ Future getImageInfo(ImageProvider provider) { /// Returns a random URL to use for testing. Due to Flutter caching images /// we need to use a different URL each time. int _urlId = 0; + Uri randomUrl({bool fallback = false}) { _urlId++; if (fallback) { @@ -66,7 +67,7 @@ void main() { return testWhiteTileBytes; }); - final provider = FlutterMapNetworkImageProvider( + final provider = MapNetworkImageProvider( url: url.toString(), fallbackUrl: null, headers: headers, @@ -93,7 +94,7 @@ void main() { ); }); - final provider = FlutterMapNetworkImageProvider( + final provider = MapNetworkImageProvider( url: url.toString(), fallbackUrl: null, headers: headers, @@ -125,7 +126,7 @@ void main() { return testWhiteTileBytes; }); - final provider = FlutterMapNetworkImageProvider( + final provider = MapNetworkImageProvider( url: url.toString(), fallbackUrl: fallbackUrl.toString(), headers: headers, @@ -153,7 +154,7 @@ void main() { ); }); - final provider = FlutterMapNetworkImageProvider( + final provider = MapNetworkImageProvider( url: url.toString(), fallbackUrl: fallbackUrl.toString(), headers: headers, @@ -177,7 +178,7 @@ void main() { return Uint8List.fromList(utf8.encode('Server Error')); }); - final provider = FlutterMapNetworkImageProvider( + final provider = MapNetworkImageProvider( url: url.toString(), fallbackUrl: null, headers: headers, @@ -207,7 +208,7 @@ void main() { return testWhiteTileBytes; }); - final provider = FlutterMapNetworkImageProvider( + final provider = MapNetworkImageProvider( url: url.toString(), fallbackUrl: fallbackUrl.toString(), headers: headers, From f942f82bbb5faf75419181b834feddea85c3dc70 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 12:15:49 +0100 Subject: [PATCH 2/6] merge `MapInternalController` into `MapControllerImpl` --- lib/src/gestures/map_interactive_viewer.dart | 44 +- lib/src/map/controller/internal.dart | 484 ----------------- .../map/controller/map_controller_impl.dart | 496 +++++++++++++++++- lib/src/map/widget.dart | 51 +- 4 files changed, 504 insertions(+), 571 deletions(-) delete mode 100644 lib/src/map/controller/internal.dart diff --git a/lib/src/gestures/map_interactive_viewer.dart b/lib/src/gestures/map_interactive_viewer.dart index e0873b1d0..3b8cb3202 100644 --- a/lib/src/gestures/map_interactive_viewer.dart +++ b/lib/src/gestures/map_interactive_viewer.dart @@ -4,18 +4,7 @@ import 'dart:math' as math; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/gestures/interactive_flag.dart'; -import 'package:flutter_map/src/gestures/latlng_tween.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; -import 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; -import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; -import 'package:flutter_map/src/map/camera/camera.dart'; -import 'package:flutter_map/src/map/controller/internal.dart'; -import 'package:flutter_map/src/map/options/cursor_keyboard_rotation.dart'; -import 'package:flutter_map/src/map/options/interaction.dart'; -import 'package:flutter_map/src/map/options/options.dart'; -import 'package:flutter_map/src/misc/point_extensions.dart'; -import 'package:latlong2/latlong.dart'; +import 'package:flutter_map/flutter_map.dart'; typedef InteractiveViewerBuilder = Widget Function( BuildContext context, @@ -27,7 +16,7 @@ typedef InteractiveViewerBuilder = Widget Function( /// via the internal [controller]. class MapInteractiveViewer extends StatefulWidget { final InteractiveViewerBuilder builder; - final MapInternalController controller; + final MapControllerImpl controller; const MapInteractiveViewer({ super.key, @@ -334,11 +323,10 @@ class MapInteractiveViewerState extends State if (_interactionOptions.cursorKeyboardRotationOptions.setNorthOnClick && _ckrTriggered.value && _ckrInitialDegrees == _camera.rotation) { - widget.controller.rotate( + widget.controller.rotateRaw( getCursorRotationDegrees(event.localPosition), hasGesture: true, source: MapEventSource.cursorKeyboardRotation, - id: null, ); } @@ -370,14 +358,13 @@ class MapInteractiveViewerState extends State final baseSetNorth = getCursorRotationDegrees(event.localPosition) - _ckrClickDegrees; - widget.controller.rotate( + widget.controller.rotateRaw( _interactionOptions.cursorKeyboardRotationOptions.behaviour == CursorRotationBehaviour.setNorth ? baseSetNorth : (_ckrInitialDegrees + baseSetNorth) % 360, hasGesture: true, source: MapEventSource.cursorKeyboardRotation, - id: null, ); if (_interactionOptions.cursorKeyboardRotationOptions.behaviour == @@ -406,13 +393,11 @@ class MapInteractiveViewerState extends State pointerSignal.localPosition.toPoint(), newZoom, ); - widget.controller.move( + widget.controller.moveRaw( newCenter, newZoom, - offset: Offset.zero, hasGesture: true, source: MapEventSource.scrollWheel, - id: null, ); }, ); @@ -617,13 +602,11 @@ class MapInteractiveViewerState extends State } if (_pinchZoomStarted || _pinchMoveStarted) { - widget.controller.move( + widget.controller.moveRaw( newCenter, newZoom, - offset: Offset.zero, hasGesture: true, source: MapEventSource.onMultiFinger, - id: null, ); } } @@ -662,14 +645,13 @@ class MapInteractiveViewerState extends State final rotatedVector = vector.rotate(degToRadian(rotationDiff)); final newCenter = rotationCenter + rotatedVector; - widget.controller.moveAndRotate( + widget.controller.moveAndRotateRaw( _camera.unproject(newCenter), _camera.zoom, _camera.rotation + rotationDiff, offset: Offset.zero, hasGesture: true, source: MapEventSource.onMultiFinger, - id: null, ); } } @@ -842,13 +824,11 @@ class MapInteractiveViewerState extends State } void _handleDoubleTapZoomAnimation() { - widget.controller.move( + widget.controller.moveRaw( _doubleTapCenterAnimation.value, _doubleTapZoomAnimation.value, - offset: Offset.zero, hasGesture: true, source: MapEventSource.doubleTapZoomAnimationController, - id: null, ); } @@ -872,13 +852,11 @@ class MapInteractiveViewerState extends State final max = _options.maxZoom ?? double.infinity; final actualZoom = math.max(min, math.min(max, newZoom)); - widget.controller.move( + widget.controller.moveRaw( _camera.center, actualZoom, - offset: Offset.zero, hasGesture: true, source: MapEventSource.doubleTapHold, - id: null, ); } } @@ -894,13 +872,11 @@ class MapInteractiveViewerState extends State _flingAnimation.value.toPoint().rotate(_camera.rotationRad); final newCenter = _camera.unproject(newCenterPoint); - widget.controller.move( + widget.controller.moveRaw( newCenter, _camera.zoom, - offset: Offset.zero, hasGesture: true, source: MapEventSource.flingAnimationController, - id: null, ); } diff --git a/lib/src/map/controller/internal.dart b/lib/src/map/controller/internal.dart deleted file mode 100644 index 0b4122b55..000000000 --- a/lib/src/map/controller/internal.dart +++ /dev/null @@ -1,484 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; -import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; -import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; -import 'package:flutter_map/src/map/camera/camera.dart'; -import 'package:flutter_map/src/map/camera/camera_fit.dart'; -import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; -import 'package:flutter_map/src/map/options/options.dart'; -import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; -import 'package:flutter_map/src/misc/point_extensions.dart'; -import 'package:flutter_map/src/misc/position.dart'; -import 'package:latlong2/latlong.dart'; - -/// This controller is for internal use. All updates to the state should be done -/// by calling methods of this class to ensure consistency. -class MapInternalController extends ValueNotifier<_InternalState> { - late final MapInteractiveViewerState _interactiveViewerState; - late MapControllerImpl _mapControllerImpl; - - MapInternalController(MapOptions options) - : super( - _InternalState( - options: options, - camera: MapCamera.initialCamera(options), - ), - ); - - /// Link the viewer state with the controller. This should be done once when - /// the FlutterMapInteractiveViewerState is initialized. - set interactiveViewerState( - MapInteractiveViewerState interactiveViewerState, - ) => - _interactiveViewerState = interactiveViewerState; - - MapOptions get options => value.options; - - MapCamera get camera => value.camera; - - void linkMapController(MapControllerImpl mapControllerImpl) { - _mapControllerImpl = mapControllerImpl; - _mapControllerImpl.internalController = this; - } - - /// This setter should only be called in this class or within tests. Changes - /// to the [FlutterMapInternalState] should be done via methods in this class. - @visibleForTesting - @override - // ignore: library_private_types_in_public_api - set value(_InternalState value) => super.value = value; - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - bool move( - LatLng newCenter, - double newZoom, { - required Offset offset, - required bool hasGesture, - required MapEventSource source, - required String? id, - }) { - // Algorithm thanks to https://github.com/tlserver/flutter_map_location_marker - if (offset != Offset.zero) { - final newPoint = camera.project(newCenter, newZoom); - newCenter = camera.unproject( - camera.rotatePoint( - newPoint, - newPoint - Point(offset.dx, offset.dy), - ), - newZoom, - ); - } - - MapCamera? newCamera = camera.withPosition( - center: newCenter, - zoom: camera.clampZoom(newZoom), - ); - - newCamera = options.cameraConstraint.constrain(newCamera); - if (newCamera == null || - (newCamera.center == camera.center && newCamera.zoom == camera.zoom)) { - return false; - } - - final oldCamera = camera; - value = value.withMapCamera(newCamera); - - final movementEvent = MapEventWithMove.fromSource( - oldCamera: oldCamera, - camera: camera, - hasGesture: hasGesture, - source: source, - id: id, - ); - if (movementEvent != null) _emitMapEvent(movementEvent); - - options.onPositionChanged?.call( - MapPosition( - center: newCenter, - bounds: camera.visibleBounds, - zoom: newZoom, - hasGesture: hasGesture, - ), - hasGesture, - ); - - return true; - } - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - bool rotate( - double newRotation, { - required bool hasGesture, - required MapEventSource source, - required String? id, - }) { - if (newRotation != camera.rotation) { - final newCamera = options.cameraConstraint.constrain( - camera.withRotation(newRotation), - ); - if (newCamera == null) return false; - - final oldCamera = camera; - - // Update camera then emit events and callbacks - value = value.withMapCamera(newCamera); - - _emitMapEvent( - MapEventRotate( - id: id, - source: source, - oldCamera: oldCamera, - camera: camera, - ), - ); - return true; - } - - return false; - } - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - MoveAndRotateResult rotateAroundPoint( - double degree, { - required Point? point, - required Offset? offset, - required bool hasGesture, - required MapEventSource source, - required String? id, - }) { - if (point != null && offset != null) { - throw ArgumentError('Only one of `point` or `offset` may be non-null'); - } - if (point == null && offset == null) { - throw ArgumentError('One of `point` or `offset` must be non-null'); - } - - if (degree == camera.rotation) { - return const (moveSuccess: false, rotateSuccess: false); - } - - if (offset == Offset.zero) { - return ( - moveSuccess: true, - rotateSuccess: rotate( - degree, - hasGesture: hasGesture, - source: source, - id: id, - ), - ); - } - - final rotationDiff = degree - camera.rotation; - final rotationCenter = camera.project(camera.center) + - (point != null - ? (point - (camera.nonRotatedSize / 2.0)) - : Point(offset!.dx, offset.dy)) - .rotate(camera.rotationRad); - - return ( - moveSuccess: move( - camera.unproject( - rotationCenter + - (camera.project(camera.center) - rotationCenter) - .rotate(degToRadian(rotationDiff)), - ), - camera.zoom, - offset: Offset.zero, - hasGesture: hasGesture, - source: source, - id: id, - ), - rotateSuccess: rotate( - camera.rotation + rotationDiff, - hasGesture: hasGesture, - source: source, - id: id, - ), - ); - } - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - MoveAndRotateResult moveAndRotate( - LatLng newCenter, - double newZoom, - double newRotation, { - required Offset offset, - required bool hasGesture, - required MapEventSource source, - required String? id, - }) => - ( - moveSuccess: move( - newCenter, - newZoom, - offset: offset, - hasGesture: hasGesture, - source: source, - id: id, - ), - rotateSuccess: - rotate(newRotation, id: id, source: source, hasGesture: hasGesture), - ); - - /// Note: All named parameters are required to prevent inconsistent default - /// values since this method can be called by MapController which declares - /// defaults. - bool fitCamera( - CameraFit cameraFit, { - required Offset offset, - }) { - final fitted = cameraFit.fit(camera); - - return move( - fitted.center, - fitted.zoom, - offset: offset, - hasGesture: false, - source: MapEventSource.mapController, - id: null, - ); - } - - bool setNonRotatedSizeWithoutEmittingEvent( - Point nonRotatedSize, - ) { - if (nonRotatedSize != MapCamera.kImpossibleSize && - nonRotatedSize != camera.nonRotatedSize) { - value = value.withMapCamera(camera.withNonRotatedSize(nonRotatedSize)); - return true; - } - - return false; - } - - void setOptions(MapOptions newOptions) { - assert( - newOptions != value.options, - 'Should not update options unless they change', - ); - - final newCamera = camera.withOptions(newOptions); - - assert( - newOptions.cameraConstraint.constrain(newCamera) == newCamera, - 'MapCamera is no longer within the cameraConstraint after an option change.', - ); - - if (options.interactionOptions != newOptions.interactionOptions) { - _interactiveViewerState.updateGestures( - options.interactionOptions, - newOptions.interactionOptions, - ); - } - - value = _InternalState( - options: newOptions, - camera: newCamera, - ); - } - - /// To be called when a gesture that causes movement starts. - void moveStarted(MapEventSource source) { - _emitMapEvent( - MapEventMoveStart( - camera: camera, - source: source, - ), - ); - } - - /// To be called when an ongoing drag movement updates. - void dragUpdated(MapEventSource source, Offset offset) { - final oldCenterPt = camera.project(camera.center); - - final newCenterPt = oldCenterPt + offset.toPoint(); - final newCenter = camera.unproject(newCenterPt); - - move( - newCenter, - camera.zoom, - offset: Offset.zero, - hasGesture: true, - source: source, - id: null, - ); - } - - /// To be called when a drag gesture ends. - void moveEnded(MapEventSource source) { - _emitMapEvent( - MapEventMoveEnd( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a rotation gesture starts. - void rotateStarted(MapEventSource source) { - _emitMapEvent( - MapEventRotateStart( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a rotation gesture ends. - void rotateEnded(MapEventSource source) { - _emitMapEvent( - MapEventRotateEnd( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a fling gesture starts. - void flingStarted(MapEventSource source) { - _emitMapEvent( - MapEventFlingAnimationStart( - camera: camera, - source: MapEventSource.flingAnimationController, - ), - ); - } - - /// To be called when a fling gesture ends. - void flingEnded(MapEventSource source) { - _emitMapEvent( - MapEventFlingAnimationEnd( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a fling gesture does not start. - void flingNotStarted(MapEventSource source) { - _emitMapEvent( - MapEventFlingAnimationNotStarted( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a double tap zoom starts. - void doubleTapZoomStarted(MapEventSource source) { - _emitMapEvent( - MapEventDoubleTapZoomStart( - camera: camera, - source: source, - ), - ); - } - - /// To be called when a double tap zoom ends. - void doubleTapZoomEnded(MapEventSource source) { - _emitMapEvent( - MapEventDoubleTapZoomEnd( - camera: camera, - source: source, - ), - ); - } - - void tapped( - MapEventSource source, - TapPosition tapPosition, - LatLng position, - ) { - options.onTap?.call(tapPosition, position); - _emitMapEvent( - MapEventTap( - tapPosition: position, - camera: camera, - source: source, - ), - ); - } - - void secondaryTapped( - MapEventSource source, - TapPosition tapPosition, - LatLng position, - ) { - options.onSecondaryTap?.call(tapPosition, position); - _emitMapEvent( - MapEventSecondaryTap( - tapPosition: position, - camera: camera, - source: source, - ), - ); - } - - void longPressed( - MapEventSource source, - TapPosition tapPosition, - LatLng position, - ) { - options.onLongPress?.call(tapPosition, position); - _emitMapEvent( - MapEventLongPress( - tapPosition: position, - camera: camera, - source: MapEventSource.longPress, - ), - ); - } - - /// To be called when the map's size constraints change. - void nonRotatedSizeChange( - MapEventSource source, - MapCamera oldCamera, - MapCamera newCamera, - ) { - _emitMapEvent( - MapEventNonRotatedSizeChange( - source: MapEventSource.nonRotatedSizeChange, - oldCamera: oldCamera, - camera: newCamera, - ), - ); - } - - void _emitMapEvent(MapEvent event) { - if (event.source == MapEventSource.mapController && event is MapEventMove) { - _interactiveViewerState.interruptAnimatedMovement(event); - } - - options.onMapEvent?.call(event); - - _mapControllerImpl.mapEventSink.add(event); - } -} - -@immutable -class _InternalState { - final MapCamera camera; - final MapOptions options; - - const _InternalState({ - required this.options, - required this.camera, - }); - - _InternalState withMapCamera(MapCamera camera) => _InternalState( - options: options, - camera: camera, - ); -} diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index b029692f6..e708605a8 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -2,32 +2,67 @@ import 'dart:async'; import 'dart:math'; import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; -import 'package:flutter_map/src/map/camera/camera.dart'; -import 'package:flutter_map/src/map/camera/camera_fit.dart'; -import 'package:flutter_map/src/map/controller/internal.dart'; -import 'package:flutter_map/src/map/controller/map_controller.dart'; -import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; -import 'package:latlong2/latlong.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; /// Implements [MapController] whilst exposing methods for internal use which -/// should not be visible to the user (e.g. for setting the current camera or -/// linking the internal controller). -class MapControllerImpl implements MapController { - late MapInternalController _internalController; +/// should not be visible to the user (e.g. for setting the current camera). +/// This controller is for internal use. All updates to the state should be done +/// by calling methods of this class to ensure consistency. +class MapControllerImpl extends ValueNotifier<_MapControllerState> + implements MapController { final _mapEventStreamController = StreamController.broadcast(); - MapControllerImpl(); + late final MapInteractiveViewerState _interactiveViewerState; - set internalController(MapInternalController internalController) { - _internalController = internalController; - } + MapControllerImpl([MapOptions? options]) + : super( + _MapControllerState( + options: options, + camera: options == null ? null : MapCamera.initialCamera(options), + ), + ); + + /// Link the viewer state with the controller. This should be done once when + /// the FlutterMapInteractiveViewerState is initialized. + set interactiveViewerState( + MapInteractiveViewerState interactiveViewerState, + ) => + _interactiveViewerState = interactiveViewerState; - StreamSink get mapEventSink => _mapEventStreamController.sink; + StreamSink get _mapEventSink => _mapEventStreamController.sink; @override Stream get mapEventStream => _mapEventStreamController.stream; + MapOptions get options { + assert( + value.options != null, + 'You need to have the FlutterMap widget rendered at least once before using the MapController.', + ); + return value.options ?? + (throw Exception( + 'You need to have the FlutterMap widget rendered at least once before using the MapController.')); + } + + @override + MapCamera get camera { + assert( + value.camera != null, + 'You need to have the FlutterMap widget rendered at least once before using the MapController.', + ); + return value.camera ?? + (throw Exception( + 'You need to have the FlutterMap widget rendered at least once before using the MapController.')); + } + + /// This setter should only be called in this class or within tests. Changes + /// to the [_MapControllerState] should be done via methods in this class. + @visibleForTesting + @override + // ignore: library_private_types_in_public_api + set value(_MapControllerState value) => super.value = value; + @override bool move( LatLng center, @@ -35,7 +70,7 @@ class MapControllerImpl implements MapController { Offset offset = Offset.zero, String? id, }) => - _internalController.move( + moveRaw( center, zoom, offset: offset, @@ -45,7 +80,7 @@ class MapControllerImpl implements MapController { ); @override - bool rotate(double degree, {String? id}) => _internalController.rotate( + bool rotate(double degree, {String? id}) => rotateRaw( degree, hasGesture: false, source: MapEventSource.mapController, @@ -59,7 +94,7 @@ class MapControllerImpl implements MapController { Offset? offset, String? id, }) => - _internalController.rotateAroundPoint( + rotateAroundPointRaw( degree, point: point, offset: offset, @@ -75,7 +110,7 @@ class MapControllerImpl implements MapController { double degree, { String? id, }) => - _internalController.moveAndRotate( + moveAndRotateRaw( center, zoom, degree, @@ -86,16 +121,427 @@ class MapControllerImpl implements MapController { ); @override - bool fitCamera(CameraFit cameraFit) => _internalController.fitCamera( - cameraFit, - offset: Offset.zero, + bool fitCamera(CameraFit cameraFit) => fitCameraRaw(cameraFit); + + bool moveRaw( + LatLng newCenter, + double newZoom, { + Offset offset = Offset.zero, + required bool hasGesture, + required MapEventSource source, + String? id, + }) { + // Algorithm thanks to https://github.com/tlserver/flutter_map_location_marker + if (offset != Offset.zero) { + final newPoint = camera.project(newCenter, newZoom); + newCenter = camera.unproject( + camera.rotatePoint( + newPoint, + newPoint - Point(offset.dx, offset.dy), + ), + newZoom, ); + } - @override - MapCamera get camera => _internalController.camera; + MapCamera? newCamera = camera.withPosition( + center: newCenter, + zoom: camera.clampZoom(newZoom), + ); + + newCamera = options.cameraConstraint.constrain(newCamera); + if (newCamera == null || + (newCamera.center == camera.center && newCamera.zoom == camera.zoom)) { + return false; + } + + final oldCamera = camera; + value = value.withMapCamera(newCamera); + + final movementEvent = MapEventWithMove.fromSource( + oldCamera: oldCamera, + camera: camera, + hasGesture: hasGesture, + source: source, + id: id, + ); + if (movementEvent != null) _emitMapEvent(movementEvent); + + options.onPositionChanged?.call( + MapPosition( + center: newCenter, + bounds: camera.visibleBounds, + zoom: newZoom, + hasGesture: hasGesture, + ), + hasGesture, + ); + + return true; + } + + bool rotateRaw( + double newRotation, { + required bool hasGesture, + required MapEventSource source, + String? id, + }) { + if (newRotation != camera.rotation) { + final newCamera = options.cameraConstraint.constrain( + camera.withRotation(newRotation), + ); + if (newCamera == null) return false; + + final oldCamera = camera; + + // Update camera then emit events and callbacks + value = value.withMapCamera(newCamera); + + _emitMapEvent( + MapEventRotate( + id: id, + source: source, + oldCamera: oldCamera, + camera: camera, + ), + ); + return true; + } + + return false; + } + + MoveAndRotateResult rotateAroundPointRaw( + double degree, { + required Point? point, + required Offset? offset, + required bool hasGesture, + required MapEventSource source, + String? id, + }) { + if (point != null && offset != null) { + throw ArgumentError('Only one of `point` or `offset` may be non-null'); + } + if (point == null && offset == null) { + throw ArgumentError('One of `point` or `offset` must be non-null'); + } + + if (degree == camera.rotation) { + return const (moveSuccess: false, rotateSuccess: false); + } + + if (offset == Offset.zero) { + return ( + moveSuccess: true, + rotateSuccess: rotateRaw( + degree, + hasGesture: hasGesture, + source: source, + id: id, + ), + ); + } + + final rotationDiff = degree - camera.rotation; + final rotationCenter = camera.project(camera.center) + + (point != null + ? (point - (camera.nonRotatedSize / 2.0)) + : Point(offset!.dx, offset.dy)) + .rotate(camera.rotationRad); + + return ( + moveSuccess: moveRaw( + camera.unproject( + rotationCenter + + (camera.project(camera.center) - rotationCenter) + .rotate(degToRadian(rotationDiff)), + ), + camera.zoom, + hasGesture: hasGesture, + source: source, + id: id, + ), + rotateSuccess: rotateRaw( + camera.rotation + rotationDiff, + hasGesture: hasGesture, + source: source, + id: id, + ), + ); + } + + MoveAndRotateResult moveAndRotateRaw( + LatLng newCenter, + double newZoom, + double newRotation, { + required Offset offset, + required bool hasGesture, + required MapEventSource source, + String? id, + }) => + ( + moveSuccess: moveRaw( + newCenter, + newZoom, + offset: offset, + hasGesture: hasGesture, + source: source, + id: id, + ), + rotateSuccess: rotateRaw( + newRotation, + id: id, + source: source, + hasGesture: hasGesture, + ), + ); + + bool fitCameraRaw( + CameraFit cameraFit, { + Offset offset = Offset.zero, + }) { + final fitted = cameraFit.fit(camera); + return moveRaw( + fitted.center, + fitted.zoom, + offset: offset, + hasGesture: false, + source: MapEventSource.mapController, + ); + } + + bool setNonRotatedSizeWithoutEmittingEvent( + Point nonRotatedSize, + ) { + if (nonRotatedSize != MapCamera.kImpossibleSize && + nonRotatedSize != camera.nonRotatedSize) { + value = value.withMapCamera(camera.withNonRotatedSize(nonRotatedSize)); + return true; + } + + return false; + } + + set options(MapOptions newOptions) { + assert( + newOptions != value.options, + 'Should not update options unless they change', + ); + + final newCamera = value.camera?.withOptions(newOptions) ?? + MapCamera.initialCamera(newOptions); + + assert( + newOptions.cameraConstraint.constrain(newCamera) == newCamera, + 'MapCamera is no longer within the cameraConstraint after an option change.', + ); + + if (options.interactionOptions != newOptions.interactionOptions) { + _interactiveViewerState.updateGestures( + options.interactionOptions, + newOptions.interactionOptions, + ); + } + + value = _MapControllerState( + options: newOptions, + camera: newCamera, + ); + } + + /// To be called when a gesture that causes movement starts. + void moveStarted(MapEventSource source) { + _emitMapEvent( + MapEventMoveStart( + camera: camera, + source: source, + ), + ); + } + + /// To be called when an ongoing drag movement updates. + void dragUpdated(MapEventSource source, Offset offset) { + final oldCenterPt = camera.project(camera.center); + + final newCenterPt = oldCenterPt + offset.toPoint(); + final newCenter = camera.unproject(newCenterPt); + + moveRaw( + newCenter, + camera.zoom, + hasGesture: true, + source: source, + ); + } + + /// To be called when a drag gesture ends. + void moveEnded(MapEventSource source) { + _emitMapEvent( + MapEventMoveEnd( + camera: camera, + source: source, + ), + ); + } + + /// To be called when a rotation gesture starts. + void rotateStarted(MapEventSource source) { + _emitMapEvent( + MapEventRotateStart( + camera: camera, + source: source, + ), + ); + } + + /// To be called when a rotation gesture ends. + void rotateEnded(MapEventSource source) { + _emitMapEvent( + MapEventRotateEnd( + camera: camera, + source: source, + ), + ); + } + + /// To be called when a fling gesture starts. + void flingStarted(MapEventSource source) { + _emitMapEvent( + MapEventFlingAnimationStart( + camera: camera, + source: MapEventSource.flingAnimationController, + ), + ); + } + + /// To be called when a fling gesture ends. + void flingEnded(MapEventSource source) { + _emitMapEvent( + MapEventFlingAnimationEnd( + camera: camera, + source: source, + ), + ); + } + + /// To be called when a fling gesture does not start. + void flingNotStarted(MapEventSource source) { + _emitMapEvent( + MapEventFlingAnimationNotStarted( + camera: camera, + source: source, + ), + ); + } + + /// To be called when a double tap zoom starts. + void doubleTapZoomStarted(MapEventSource source) { + _emitMapEvent( + MapEventDoubleTapZoomStart( + camera: camera, + source: source, + ), + ); + } + + /// To be called when a double tap zoom ends. + void doubleTapZoomEnded(MapEventSource source) { + _emitMapEvent( + MapEventDoubleTapZoomEnd( + camera: camera, + source: source, + ), + ); + } + + void tapped( + MapEventSource source, + TapPosition tapPosition, + LatLng position, + ) { + options.onTap?.call(tapPosition, position); + _emitMapEvent( + MapEventTap( + tapPosition: position, + camera: camera, + source: source, + ), + ); + } + + void secondaryTapped( + MapEventSource source, + TapPosition tapPosition, + LatLng position, + ) { + options.onSecondaryTap?.call(tapPosition, position); + _emitMapEvent( + MapEventSecondaryTap( + tapPosition: position, + camera: camera, + source: source, + ), + ); + } + + void longPressed( + MapEventSource source, + TapPosition tapPosition, + LatLng position, + ) { + options.onLongPress?.call(tapPosition, position); + _emitMapEvent( + MapEventLongPress( + tapPosition: position, + camera: camera, + source: MapEventSource.longPress, + ), + ); + } + + /// To be called when the map's size constraints change. + void nonRotatedSizeChange( + MapEventSource source, + MapCamera oldCamera, + MapCamera newCamera, + ) { + _emitMapEvent( + MapEventNonRotatedSizeChange( + source: MapEventSource.nonRotatedSizeChange, + oldCamera: oldCamera, + camera: newCamera, + ), + ); + } + + void _emitMapEvent(MapEvent event) { + if (event.source == MapEventSource.mapController && event is MapEventMove) { + _interactiveViewerState.interruptAnimatedMovement(event); + } + + options.onMapEvent?.call(event); + + _mapEventSink.add(event); + } @override void dispose() { _mapEventStreamController.close(); + super.dispose(); } } + +@immutable +class _MapControllerState { + final MapCamera? camera; + final MapOptions? options; + + const _MapControllerState({ + required this.options, + required this.camera, + }); + + _MapControllerState withMapCamera(MapCamera camera) => _MapControllerState( + options: options, + camera: camera, + ); +} diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart index 2b5dae5fe..8d4877206 100644 --- a/lib/src/map/widget.dart +++ b/lib/src/map/widget.dart @@ -6,7 +6,6 @@ import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart'; import 'package:flutter_map/src/layer/general/translucent_pointer.dart'; -import 'package:flutter_map/src/map/controller/internal.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; @@ -60,15 +59,14 @@ class _FlutterMapStateContainer extends State with AutomaticKeepAliveClientMixin { bool _initialCameraFitApplied = false; - late final MapInternalController _flutterMapInternalController; late MapControllerImpl _mapController; - late bool _mapControllerCreatedInternally; + + bool get _controllerCreatedInternally => widget.mapController == null; @override void initState() { super.initState(); - _flutterMapInternalController = MapInternalController(widget.options); - _initializeAndLinkMapController(); + _setOrUpdateMapController(); WidgetsBinding.instance .addPostFrameCallback((_) => widget.options.onMapReady?.call()); @@ -85,29 +83,21 @@ class _FlutterMapStateContainer extends State @override void didUpdateWidget(FlutterMap oldWidget) { - if (oldWidget.options != widget.options) { - _flutterMapInternalController.setOptions(widget.options); - } if (oldWidget.mapController != widget.mapController) { - _initializeAndLinkMapController(); + _setOrUpdateMapController(); + } + if (oldWidget.options != widget.options) { + _mapController.options = widget.options; } super.didUpdateWidget(oldWidget); } @override void dispose() { - if (_mapControllerCreatedInternally) _mapController.dispose(); - _flutterMapInternalController.dispose(); + if (_controllerCreatedInternally) _mapController.dispose(); super.dispose(); } - void _initializeAndLinkMapController() { - _mapController = - (widget.mapController ?? MapController()) as MapControllerImpl; - _mapControllerCreatedInternally = widget.mapController == null; - _flutterMapInternalController.linkMapController(_mapController); - } - @override Widget build(BuildContext context) { super.build(context); @@ -133,7 +123,7 @@ class _FlutterMapStateContainer extends State _updateAndEmitSizeIfConstraintsChanged(constraints); return MapInteractiveViewer( - controller: _flutterMapInternalController, + controller: _mapController, builder: (context, options, camera) { return MapInheritedModel( controller: _mapController, @@ -156,10 +146,7 @@ class _FlutterMapStateContainer extends State _parentConstraintsAreSet(context, constraints)) { _initialCameraFitApplied = true; - _flutterMapInternalController.fitCamera( - widget.options.initialCameraFit!, - offset: Offset.zero, - ); + _mapController.fitCamera(widget.options.initialCameraFit!); } } @@ -168,16 +155,15 @@ class _FlutterMapStateContainer extends State constraints.maxWidth, constraints.maxHeight, ); - final oldCamera = _flutterMapInternalController.camera; - if (_flutterMapInternalController - .setNonRotatedSizeWithoutEmittingEvent(nonRotatedSize)) { - final newMapCamera = _flutterMapInternalController.camera; + final oldCamera = _mapController.camera; + if (_mapController.setNonRotatedSizeWithoutEmittingEvent(nonRotatedSize)) { + final newMapCamera = _mapController.camera; // Avoid emitting the event during build otherwise if the user calls // setState in the onMapEvent callback it will throw. WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { - _flutterMapInternalController.nonRotatedSizeChange( + _mapController.nonRotatedSizeChange( MapEventSource.nonRotatedSizeChange, oldCamera, newMapCamera, @@ -200,4 +186,13 @@ class _FlutterMapStateContainer extends State @override bool get wantKeepAlive => widget.options.keepAlive; + + void _setOrUpdateMapController() { + if (_controllerCreatedInternally) { + _mapController = MapControllerImpl(widget.options); + } else { + _mapController = widget.mapController! as MapControllerImpl; + _mapController.options = widget.options; + } + } } From 954bc91d63948884e2c54018f7c66cbf5d01b4f6 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 12:20:36 +0100 Subject: [PATCH 3/6] don't update interactionOptions when setting `MapOptions` for the first time --- lib/src/map/controller/map_controller_impl.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index e708605a8..75b4218c0 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -335,9 +335,10 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> 'MapCamera is no longer within the cameraConstraint after an option change.', ); - if (options.interactionOptions != newOptions.interactionOptions) { + if (value.options != null && + value.options!.interactionOptions != newOptions.interactionOptions) { _interactiveViewerState.updateGestures( - options.interactionOptions, + value.options!.interactionOptions, newOptions.interactionOptions, ); } From dccc86a9262c200cc433cfe8b5dfb036ab3737db Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Sun, 26 Nov 2023 12:34:15 +0100 Subject: [PATCH 4/6] clean up --- lib/src/map/controller/map_controller.dart | 8 +------- lib/src/map/widget.dart | 13 ++++--------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/lib/src/map/controller/map_controller.dart b/lib/src/map/controller/map_controller.dart index 089622c8c..245173d4f 100644 --- a/lib/src/map/controller/map_controller.dart +++ b/lib/src/map/controller/map_controller.dart @@ -2,14 +2,8 @@ import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; -import 'package:flutter_map/src/map/camera/camera.dart'; -import 'package:flutter_map/src/map/camera/camera_fit.dart'; -import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; +import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; -import 'package:flutter_map/src/map/widget.dart'; -import 'package:flutter_map/src/misc/move_and_rotate_result.dart'; -import 'package:latlong2/latlong.dart'; /// Controller to programmatically interact with [FlutterMap], such as /// controlling it and accessing some of its properties. diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart index 8d4877206..76d7291fa 100644 --- a/lib/src/map/widget.dart +++ b/lib/src/map/widget.dart @@ -2,14 +2,9 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; +import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/map_interactive_viewer.dart'; -import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart'; -import 'package:flutter_map/src/layer/general/translucent_pointer.dart'; -import 'package:flutter_map/src/map/controller/map_controller.dart'; -import 'package:flutter_map/src/map/controller/map_controller_impl.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; -import 'package:flutter_map/src/map/options/options.dart'; import 'package:logger/logger.dart'; /// An interactive geographical map @@ -66,7 +61,7 @@ class _FlutterMapStateContainer extends State @override void initState() { super.initState(); - _setOrUpdateMapController(); + _setMapController(); WidgetsBinding.instance .addPostFrameCallback((_) => widget.options.onMapReady?.call()); @@ -84,7 +79,7 @@ class _FlutterMapStateContainer extends State @override void didUpdateWidget(FlutterMap oldWidget) { if (oldWidget.mapController != widget.mapController) { - _setOrUpdateMapController(); + _setMapController(); } if (oldWidget.options != widget.options) { _mapController.options = widget.options; @@ -187,7 +182,7 @@ class _FlutterMapStateContainer extends State @override bool get wantKeepAlive => widget.options.keepAlive; - void _setOrUpdateMapController() { + void _setMapController() { if (_controllerCreatedInternally) { _mapController = MapControllerImpl(widget.options); } else { From de5d34b69e862f0452d4d53f165af7c4de3510f1 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Tue, 28 Nov 2023 18:27:31 +0100 Subject: [PATCH 5/6] clean up asserts --- lib/src/map/controller/map_controller_impl.dart | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/src/map/controller/map_controller_impl.dart b/lib/src/map/controller/map_controller_impl.dart index 75b4218c0..871376e86 100644 --- a/lib/src/map/controller/map_controller_impl.dart +++ b/lib/src/map/controller/map_controller_impl.dart @@ -36,24 +36,16 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState> Stream get mapEventStream => _mapEventStreamController.stream; MapOptions get options { - assert( - value.options != null, - 'You need to have the FlutterMap widget rendered at least once before using the MapController.', - ); return value.options ?? - (throw Exception( - 'You need to have the FlutterMap widget rendered at least once before using the MapController.')); + (throw Exception('You need to have the FlutterMap widget rendered at ' + 'least once before using the MapController.')); } @override MapCamera get camera { - assert( - value.camera != null, - 'You need to have the FlutterMap widget rendered at least once before using the MapController.', - ); return value.camera ?? - (throw Exception( - 'You need to have the FlutterMap widget rendered at least once before using the MapController.')); + (throw Exception('You need to have the FlutterMap widget rendered at ' + 'least once before using the MapController.')); } /// This setter should only be called in this class or within tests. Changes From 5fe00570891202e9d46dd744714e9bf318f5f9e0 Mon Sep 17 00:00:00 2001 From: Joscha <34318751+josxha@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:56:58 +0100 Subject: [PATCH 6/6] fix import --- lib/src/map/controller/map_controller.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/map/controller/map_controller.dart b/lib/src/map/controller/map_controller.dart index 245173d4f..4a6113fee 100644 --- a/lib/src/map/controller/map_controller.dart +++ b/lib/src/map/controller/map_controller.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/inherited_model.dart'; +import 'package:latlong2/latlong.dart'; /// Controller to programmatically interact with [FlutterMap], such as /// controlling it and accessing some of its properties.