Skip to content

Commit

Permalink
[v6] Major State Refactoring (#1551)
Browse files Browse the repository at this point in the history
* Split FlutterMapState in to a stateful container widget (FlutterMapStateContainer) and an immutable representation of the state of the map (FlutterMapState)

* Extract interactions to InteractionDetector

* Move gesture initialisation out of builder and stop passing the whole FlutterMapStateContainer to InteractionDetector

* Minor tidy-ups

* Re-instate linking of MapController to map state

* Trigger all FlutterMapState manipulations via FlutterMapStateController

* Reduce MapController API size and simplify gesture code

- Replaced mapState getter with the various getters which were just
proxied to MapState.
- Heavy refactoring (hopefully without changing behaviour) of gesture
  code.

In passing I have simplified

* Remove unnecessary getters now that InteractiveFlags defines convenience methods for checking single flags

* Fix double tap zoom not working when drag was enabled and prevent pinch move when only pinch zoom is enabled

* Use new InteractiveFlag convenience methods

* Combine getBoundsCenterZoom and centerZoomFitBounds

* Replace http stubbing with an in-memory TileProvider in tests

This stops the following message from being spammed in tests which was
caused by a problem with the http mocking:

type 'Null' is not a subtype of type 'Future<HttpClientRequest>'

* Separate MapOptions from FlutterMapState

In doing so I noticed that the adaptive boundary options could use a
refactor and so placed them in a dedicated class which led to a tidy up
to the boundary code in FlutterMapState.

* Combine adaptive bounds, max bounds and sw/ne pan bounds in to a single MapBounds class

* Create FrameConstraint and FrameFit abstraction

FrameConstraint unites the various methods of setting a maximum bounds
for the map frame. Previously MapOptions had three different concepts
for setting a max bounds: adaptive bounds, maxBounds and se/nw pan
boundaries. Adaptive bounds and maxBounds are now
FrameConstraint.contain whilst sw/ne pan boundaries is replaced by
FrameConstraint.containCenter.

FrameFit is a replacement for FitBoundsOptions, combinining the options
with the bounds. This means bounds/boundsOptions now become
initialFrameFit (since bounds/boundsOptions were actually initial bounds
and the options for those initial bounds). Additionally this sets up an
abstraction for different map fits since coordinate fit will be added
next.

* Add deprecations on MapControllerImpl

* Add fitting by coordinates

This commit incorporates @jjoelson's coordinate fit implementation in to
the new FrameFit abstraction.

Co-authored-by: Jonathan Joelson <[email protected]>

* Rename FlutterMapState to FlutterMapFrame, add InteractionOptions collection to tidy up options and change how options are propagated in preparation for changing the inherited widget to an inherited model

* Use InhertiedModel instead of InheritedWidget

* Remove FitCoordinates' inside parameter because fitting inside a set of coordinates doesn't have an unambiguous meaning

* Fix event names appearing minified when running web release build

* Set constraints that match the old adaptive constraints

These old adaptive constraints did not prevent the map from going
outside of the specified bounds, they stopped the center of the map from
going outside of those bounds. This commit sets the constraints
appropriately.

* Make documentation easier to read

* Use flags/options from InteractionOptions not the old deprecated values, unless InteractiveOptions is not provided

* Fix options propagation

* Add tests to make sure the InheritedModel notifies if and only if the relevnat aspect changes

* Rename FlutterMapFrame to MapFrame

* Avoid an extra Stack

* Assign AnimationControllers where they are declared

* Rename from frame to camera

* Remove flutter_map_ prefixes from files in lib/src/map/

* Move FlutterMapStateContainer in to FlutterMap's file since it is just the widget state

* Rename mapCamera variables to camera for consistency with options for MapOptions

* Documentation

* Use standard deprecations format

In passing re-ordered the methods in MapControllerImpl to match
MapController.

* Re-organized camera related source files
Improved some documentation (part 1)
Prevent public exposure of `FitCoordinates` and `FitBounds` constructors

* Add FitInsideBounds

* Move CameraFit attributes from the base class to the subclasses and tidy up documentation

None of the fields which were on the CameraFit base class were
conceptually essential for any imaginable camera fit. Moving them to
the subclasses ensures that any future camera fits will not need to
implement those options just because they already exist. It would also
allow for individual camera fits to specify different default values if
appropriate.

* Re-order imports alphabetically

* Add lint to enforce consistent import/export ordering

* Reinstate maxBounds as a deprecated option

* Remove duplicate exports and make imports consistent

The plugin API no longer exports classes which flutter_map already
exports.

Imports within this package now import the actual classes they use
rather than the whole flutter_map library. Internal code should not
depend on the exported library definition.

* Remove deprecated AnchorAlign

* Add deprecation for nonrotatedSize

* Fix deprecation

* Set default CameraFit maxZoom values to null

The other parameters for CameraFit all have default values which will
not cause changes to the calculated CameraFit (i.e. padding is zero,
forceIntegerZoomLevel is false). Setting maxZoom to null makes it
consistent with the other parameters in that it will not affect the
calculated CameraFit. I chose null over double.infinity as in my opinion
the intent is clearer, no maximum.

---------

Co-authored-by: Jonathan Joelson <[email protected]>
Co-authored-by: JaffaKetchup <[email protected]>
  • Loading branch information
3 people authored Jul 6, 2023
1 parent 8ac3ebd commit 2c60d63
Show file tree
Hide file tree
Showing 86 changed files with 4,910 additions and 2,604 deletions.
3 changes: 2 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ linter:
avoid_dynamic_calls: true
cancel_subscriptions: true
close_sinks: true
directives_ordering: true
package_api_docs: true
prefer_constructors_over_static_methods: true
prefer_final_in_for_each: true
Expand All @@ -25,4 +26,4 @@ linter:
throw_in_finally: true
type_annotate_public_apis: true
unnecessary_statements: true
use_named_constants: true
use_named_constants: true
1 change: 0 additions & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';

import 'package:flutter_map_example/pages/animated_map_controller.dart';
import 'package:flutter_map_example/pages/circle.dart';
import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart';
Expand Down
28 changes: 15 additions & 13 deletions example/lib/pages/animated_map_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ class AnimatedMapControllerPageState extends State<AnimatedMapControllerPage>
void _animatedMapMove(LatLng destLocation, double destZoom) {
// Create some tweens. These serve to split up the transition from one location to another.
// In our case, we want to split the transition be<tween> our current map center and the destination.
final camera = mapController.camera;
final latTween = Tween<double>(
begin: mapController.center.latitude, end: destLocation.latitude);
begin: camera.center.latitude, end: destLocation.latitude);
final lngTween = Tween<double>(
begin: mapController.center.longitude, end: destLocation.longitude);
final zoomTween = Tween<double>(begin: mapController.zoom, end: destZoom);
begin: camera.center.longitude, end: destLocation.longitude);
final zoomTween = Tween<double>(begin: camera.zoom, end: destZoom);

// Create a animation controller that has a duration and a TickerProvider.
final controller = AnimationController(
Expand Down Expand Up @@ -161,10 +162,10 @@ class AnimatedMapControllerPageState extends State<AnimatedMapControllerPage>
london,
]);

mapController.fitBounds(
bounds,
options: const FitBoundsOptions(
padding: EdgeInsets.only(left: 15, right: 15),
mapController.fitCamera(
CameraFit.bounds(
bounds: bounds,
padding: const EdgeInsets.symmetric(horizontal: 15),
),
);
},
Expand All @@ -178,9 +179,10 @@ class AnimatedMapControllerPageState extends State<AnimatedMapControllerPage>
london,
]);

final centerZoom =
mapController.centerZoomFitBounds(bounds);
_animatedMapMove(centerZoom.center, centerZoom.zoom);
final constrained = CameraFit.bounds(
bounds: bounds,
).fit(mapController.camera);
_animatedMapMove(constrained.center, constrained.zoom);
},
child: const Text('Fit Bounds animated'),
),
Expand All @@ -190,9 +192,9 @@ class AnimatedMapControllerPageState extends State<AnimatedMapControllerPage>
Flexible(
child: FlutterMap(
mapController: mapController,
options: MapOptions(
center: const LatLng(51.5, -0.09),
zoom: 5,
options: const MapOptions(
initialCenter: LatLng(51.5, -0.09),
initialZoom: 5,
maxZoom: 10,
minZoom: 3),
children: [
Expand Down
6 changes: 3 additions & 3 deletions example/lib/pages/circle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class CirclePage extends StatelessWidget {
),
Flexible(
child: FlutterMap(
options: MapOptions(
center: const LatLng(51.5, -0.09),
zoom: 11,
options: const MapOptions(
initialCenter: LatLng(51.5, -0.09),
initialZoom: 11,
),
children: [
TileLayer(
Expand Down
4 changes: 2 additions & 2 deletions example/lib/pages/custom_crs/custom_crs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ class _CustomCrsPageState extends State<CustomCrsPage> {
options: MapOptions(
// Set the default CRS
crs: epsg3413CRS,
center: LatLng(point.x, point.y),
zoom: 3,
initialCenter: LatLng(point.x, point.y),
initialZoom: 3,
// Set maxZoom usually scales.length - 1 OR resolutions.length - 1
// but not greater
maxZoom: maxZoom,
Expand Down
4 changes: 2 additions & 2 deletions example/lib/pages/epsg3413_crs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ class _EPSG3413PageState extends State<EPSG3413Page> {
child: FlutterMap(
options: MapOptions(
crs: epsg3413CRS,
center: const LatLng(90, 0),
zoom: 3,
initialCenter: const LatLng(90, 0),
initialZoom: 3,
maxZoom: maxZoom,
),
nonRotatedChildren: [
Expand Down
10 changes: 5 additions & 5 deletions example/lib/pages/epsg4326_crs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ class EPSG4326Page extends StatelessWidget {
),
Flexible(
child: FlutterMap(
options: MapOptions(
options: const MapOptions(
minZoom: 0,
crs: const Epsg4326(),
center: const LatLng(0, 0),
zoom: 0,
crs: Epsg4326(),
initialCenter: LatLng(0, 0),
initialZoom: 0,
),
children: [
TileLayer(
Expand All @@ -37,7 +37,7 @@ class EPSG4326Page extends StatelessWidget {
layers: ['TOPO-OSM-WMS'],
),
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
)
),
],
),
),
Expand Down
4 changes: 2 additions & 2 deletions example/lib/pages/fallback_url.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ class FallbackUrlPage extends StatelessWidget {
Flexible(
child: FlutterMap(
options: MapOptions(
center: center,
zoom: zoom,
initialCenter: center,
initialZoom: zoom,
maxZoom: maxZoom,
minZoom: minZoom,
),
Expand Down
12 changes: 7 additions & 5 deletions example/lib/pages/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,13 @@ class _HomePageState extends State<HomePage> {
Flexible(
child: FlutterMap(
options: MapOptions(
center: const LatLng(51.5, -0.09),
zoom: 5,
maxBounds: LatLngBounds(
const LatLng(-90, -180),
const LatLng(90, 180),
initialCenter: const LatLng(51.5, -0.09),
initialZoom: 5,
cameraConstraint: CameraConstraint.contain(
bounds: LatLngBounds(
const LatLng(-90, -180),
const LatLng(90, 180),
),
),
),
nonRotatedChildren: [
Expand Down
81 changes: 62 additions & 19 deletions example/lib/pages/interactive_test_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
void onMapEvent(MapEvent mapEvent) {
if (mapEvent is! MapEventMove && mapEvent is! MapEventRotate) {
// do not flood console with move and rotate events
debugPrint(mapEvent.toString());
debugPrint(_eventName(mapEvent));
}

setState(() {
Expand Down Expand Up @@ -59,7 +59,7 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
MaterialButton(
color: InteractiveFlag.hasFlag(flags, InteractiveFlag.drag)
color: InteractiveFlag.hasDrag(flags)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
Expand All @@ -70,8 +70,7 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
child: const Text('Drag'),
),
MaterialButton(
color: InteractiveFlag.hasFlag(
flags, InteractiveFlag.flingAnimation)
color: InteractiveFlag.hasFlingAnimation(flags)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
Expand All @@ -82,10 +81,9 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
child: const Text('Fling'),
),
MaterialButton(
color:
InteractiveFlag.hasFlag(flags, InteractiveFlag.pinchMove)
? Colors.greenAccent
: Colors.redAccent,
color: InteractiveFlag.hasPinchMove(flags)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
setState(() {
updateFlags(InteractiveFlag.pinchMove);
Expand All @@ -99,8 +97,7 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
MaterialButton(
color: InteractiveFlag.hasFlag(
flags, InteractiveFlag.doubleTapZoom)
color: InteractiveFlag.hasDoubleTapZoom(flags)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
Expand All @@ -111,7 +108,7 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
child: const Text('Double tap zoom'),
),
MaterialButton(
color: InteractiveFlag.hasFlag(flags, InteractiveFlag.rotate)
color: InteractiveFlag.hasRotate(flags)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
Expand All @@ -122,10 +119,9 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
child: const Text('Rotate'),
),
MaterialButton(
color:
InteractiveFlag.hasFlag(flags, InteractiveFlag.pinchZoom)
? Colors.greenAccent
: Colors.redAccent,
color: InteractiveFlag.hasPinchZoom(flags)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
setState(() {
updateFlags(InteractiveFlag.pinchZoom);
Expand All @@ -139,7 +135,7 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
padding: const EdgeInsets.only(top: 8, bottom: 8),
child: Center(
child: Text(
'Current event: ${_latestEvent?.runtimeType ?? "none"}\nSource: ${_latestEvent?.source ?? "none"}',
'Current event: ${_eventName(_latestEvent)}\nSource: ${_latestEvent?.source.name ?? "none"}',
textAlign: TextAlign.center,
),
),
Expand All @@ -148,9 +144,11 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
child: FlutterMap(
options: MapOptions(
onMapEvent: onMapEvent,
center: const LatLng(51.5, -0.09),
zoom: 11,
interactiveFlags: flags,
initialCenter: const LatLng(51.5, -0.09),
initialZoom: 11,
interactionOptions: InteractionOptions(
flags: flags,
),
),
children: [
TileLayer(
Expand All @@ -166,4 +164,49 @@ class _InteractiveTestPageState extends State<InteractiveTestPage> {
),
);
}

String _eventName(MapEvent? event) {
switch (event) {
case MapEventTap():
return 'MapEventTap';
case MapEventSecondaryTap():
return 'MapEventSecondaryTap';
case MapEventLongPress():
return 'MapEventLongPress';
case MapEventMove():
return 'MapEventMove';
case MapEventMoveStart():
return 'MapEventMoveStart';
case MapEventMoveEnd():
return 'MapEventMoveEnd';
case MapEventFlingAnimation():
return 'MapEventFlingAnimation';
case MapEventFlingAnimationNotStarted():
return 'MapEventFlingAnimationNotStarted';
case MapEventFlingAnimationStart():
return 'MapEventFlingAnimationStart';
case MapEventFlingAnimationEnd():
return 'MapEventFlingAnimationEnd';
case MapEventDoubleTapZoom():
return 'MapEventDoubleTapZoom';
case MapEventScrollWheelZoom():
return 'MapEventScrollWheelZoom';
case MapEventDoubleTapZoomStart():
return 'MapEventDoubleTapZoomStart';
case MapEventDoubleTapZoomEnd():
return 'MapEventDoubleTapZoomEnd';
case MapEventRotate():
return 'MapEventRotate';
case MapEventRotateStart():
return 'MapEventRotateStart';
case MapEventRotateEnd():
return 'MapEventRotateEnd';
case MapEventNonRotatedSizeChange():
return 'MapEventNonRotatedSizeChange';
case null:
return 'null';
default:
return 'Unknown';
}
}
}
21 changes: 12 additions & 9 deletions example/lib/pages/latlng_to_screen_point.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_example/widgets/drawer.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:flutter_map_example/widgets/drawer.dart';
import 'package:latlong2/latlong.dart';

class LatLngScreenPointTestPage extends StatefulWidget {
Expand Down Expand Up @@ -36,23 +36,24 @@ class _LatLngScreenPointTestPageState extends State<LatLngScreenPointTestPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('LatLng To Screen Point')),
drawer: buildDrawer(context, LatLngScreenPointTestPage.route),
body: Stack(children: [
appBar: AppBar(title: const Text('LatLng To Screen Point')),
drawer: buildDrawer(context, LatLngScreenPointTestPage.route),
body: Stack(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: FlutterMap(
mapController: _mapController,
options: MapOptions(
onMapEvent: onMapEvent,
onTap: (tapPos, latLng) {
final pt1 = _mapController.latLngToScreenPoint(latLng);
final pt1 = _mapController.camera.latLngToScreenPoint(latLng);
_textPos = CustomPoint(pt1.x, pt1.y);
setState(() {});
},
center: const LatLng(51.5, -0.09),
zoom: 11,
rotation: 0,
initialCenter: const LatLng(51.5, -0.09),
initialZoom: 11,
initialRotation: 0,
),
children: [
TileLayer(
Expand All @@ -68,6 +69,8 @@ class _LatLngScreenPointTestPageState extends State<LatLngScreenPointTestPage> {
width: 20,
height: 20,
child: const FlutterLogo())
]));
],
),
);
}
}
10 changes: 6 additions & 4 deletions example/lib/pages/many_markers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ class _ManyMarkersPageState extends State<ManyMarkersPage> {
Text('$_sliderVal markers'),
Flexible(
child: FlutterMap(
options: MapOptions(
center: const LatLng(50, 20),
zoom: 5,
interactiveFlags: InteractiveFlag.all - InteractiveFlag.rotate,
options: const MapOptions(
initialCenter: LatLng(50, 20),
initialZoom: 5,
interactionOptions: InteractionOptions(
flags: InteractiveFlag.all - InteractiveFlag.rotate,
),
),
children: [
TileLayer(
Expand Down
Loading

0 comments on commit 2c60d63

Please sign in to comment.