diff --git a/CHANGELOG.md b/CHANGELOG.md index ede305da3..09acf58e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## [0.13.0] - xx/xx/2021 +This version has support for sound null safety. For this purpose, some inactive packages were exchanged with active forks. + +- Sound null safety migration (#851, #870) + - requires flutter version 2.0.0 or higher + - latlong is replaced with latlong2 + - ready-flag on map has been removed +- Remove the package flutter_image and add http instead (#894) + - http has to be version 0.13.2 or higher for this package (#894) +- Removed deprecated properties + - debug property has been removed + - interactive has been replaced by interactiveFlags +- Bounds getCenter has been replaced by center getter + +Thanks to escamoteur, ThexXTURBOXx, Sata51, tazik561, kengu, passsy, Ahmed-gubara, johnpryan, josxha and andreandersson for this release! + ## [0.12.0] - 3/16/2021 TileLayerOptions now takes some additional options, templateFunction, tileBuilder, tilesContainerBuilder, and evictErrorTileStrategy diff --git a/README.md b/README.md index 50f16d94f..79b803ec0 100644 --- a/README.md +++ b/README.md @@ -243,18 +243,71 @@ See the `example/` folder for a working example app. To run it, in a terminal cd into the folder. Then execute `ulimit -S -n 2048` ([ref][ulimit-comment]). Then execute `flutter run` with a running emulator. -## Preconfigured offline maps +## Downloading and caching offline maps -This method is only to use preconfigured and prepackaged offline maps. For -advanced caching and ability to dynamically download an area, see the -[flutter_map_tile_caching][flutter_map_tile_caching] plugin. +This section provides an overview of the available caching tile providers. If +you would like to provide preconfigured and prepackaged map tiles to your app +users, see the 'Preconfigured Offline Maps' section below. -First, find which tiles you would like to use through a service such as -[tilemill][tilemill]. You should have tiles exported into the `.mbtiles` format. +The two available options included in flutter_map -Then use [mbtilesToPng][mbtilesToPngs] to unpack into `/{z}/{x}/{y}.png`. Move -this to the assets folder and add the asset directories to `pubspec.yaml`. The -minimum required fields for offline maps are: +### 1. Use `NetworkImage` by using `NonCachingNetworkTileProvider` + +Whilst the name might make you think differently, it is designed to prevent you +from using it and expecting it to cache; because it doesn't. + +The `FlutterMap` `NonCachingNetworkTileProvider` implementaion uses +`NetworkImage` which should cache images in memory until the app restart +(through `Image.network`). See the [Image.network][Image.network] docs and +[NetworkImage][NetworkImage-caching] docs for more details. + +### 2. Using the `cached_network_image` dependency + +This dependency has an `ImageProvider` that caches images to disk, which means +the cache persists through an app restart. You'll need to [include the +package](https://pub.dev/packages/cached_network_image/install) in your +`pubspec.yaml`. + +Create your own provider using the code below: +```dart +import 'package:cached_network_image/cached_network_image.dart'; +class CachedTileProvider extends TileProvider { + const CachedTileProvider(); + @override + ImageProvider getImage(Coords coords, TileLayerOptions options) { + return CachedNetworkImageProvider( + getTileUrl(coords, options), + //Now you can set options that determine how the image gets cached via whichever plugin you use. + ); + } +} +``` +Then, add the `CachedTileProvider` `TileProvider` to the appropriate +`TileLayerOptions`: + +```dart +TileLayerOptions( + urlTemplate: 'https://example.com/{x}/{y}/{z}', + tileProvider: const CachedTileProvider() +) +``` + +## Offline Maps using TileMill + +This section provides instructions for preconfigured and prepackaged offline +maps. To see how to setup caching and downloading, see the 'Dynamically +Downloading & Caching Offline Maps' section above. + +This guide uses an open source program called [TileMill][tilemill-homepage]. + +First, [install TileMill][install-tilemill] on your machine. Then, follow [these +instructions][tilemill]. + +Once you have your map exported to `.mbtiles`, you can use +[mbtilesToPng][mbTilesToPngs] to unpack into `/{z}/{x}/{y}.png`. + +Move this to assets folder and add the appropriate asset directories to +`pubspec.yaml`. Minimum required fields for this solution are: ```dart Widget build(ctx) { @@ -300,13 +353,17 @@ See also `FileTileProvider()`, which loads tiles from the filesystem. For the latest roadmap, please see the [Issue Tracker] [Issue Tracker]: https://github.com/johnpryan/flutter_map/issues -[Leaflet]: http://leafletjs.com/ +[Leaflet]: https://leafletjs.com/ [Mapbox]: https://www.mapbox.com/ [azure-maps-instructions]: https://docs.microsoft.com/en-us/azure/azure-maps/quick-demo-map-app [custom-crs-readme]: ./example/lib/pages/custom_crs/Readme.md [flutter_map_tile_caching]: https://github.com/JaffaKetchup/flutter_map_tile_caching [mbTilesToPng]: https://github.com/alfanhui/mbtilesToPngs [open-street-map]: https://openstreetmap.org +[proj4dart]: https://github.com/maRci002/proj4dart [tilemill]: https://tilemill-project.github.io/tilemill/docs/guides/osm-bright-mac-quickstart/ +[install-tilemill]: https://tilemill-project.github.io/tilemill/docs/install/ [ulimit-comment]: https://github.com/trentpiercy/trace/issues/1#issuecomment-404494469 -[proj4dart]: https://github.com/maRci002/proj4dart +[Image.network]: https://api.flutter.dev/flutter/widgets/Image/Image.network.html +[NetworkImage-caching]: https://flutter.dev/docs/cookbook/images/network-image#placeholders-and-caching +[tilemill-homepage]: https://tilemill-project.github.io/tilemill/ diff --git a/analysis_options.yaml b/analysis_options.yaml index f2a12ab81..202093b55 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -36,6 +36,7 @@ linter: - prefer_null_aware_operators - prefer_single_quotes - prefer_typing_uninitialized_variables + - sort_pub_dependencies - test_types_in_equals - throw_in_finally - unnecessary_brace_in_string_interps diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index 592ceee85..ec97fc6f3 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 592ceee85..c4855bfe2 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 6725d457a..ab9fc9ecf 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 127B2300FA5CE87549434CC2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A72EECE5117AE803156E244 /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; @@ -31,7 +32,9 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3A72EECE5117AE803156E244 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4928082F7E2BC0BADA4A6955 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -42,6 +45,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CF9AFCBF84244C1D44C2E795 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + E1DF330ADC8450B8B567025E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +54,32 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 127B2300FA5CE87549434CC2 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 13FB0627975E846BA76A343C /* Pods */ = { + isa = PBXGroup; + children = ( + CF9AFCBF84244C1D44C2E795 /* Pods-Runner.debug.xcconfig */, + 4928082F7E2BC0BADA4A6955 /* Pods-Runner.release.xcconfig */, + E1DF330ADC8450B8B567025E /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 1DB0744A0D67D7FBC7C14ADA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3A72EECE5117AE803156E244 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +97,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 13FB0627975E846BA76A343C /* Pods */, + 1DB0744A0D67D7FBC7C14ADA /* Frameworks */, ); sourceTree = ""; }; @@ -105,12 +132,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 2655651DCB6AEB0882B90CC1 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + C1C85694F6D3CCB2E45E23F9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -169,6 +198,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2655651DCB6AEB0882B90CC1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -197,6 +248,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + C1C85694F6D3CCB2E45E23F9 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16e..21a3cc14c 100644 --- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/example/lib/main.dart b/example/lib/main.dart index fe41a68eb..00327691b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_map_example/pages/network_tile_provider.dart'; import './pages/animated_map_controller.dart'; import './pages/circle.dart'; @@ -40,6 +41,7 @@ class MyApp extends StatelessWidget { ), home: HomePage(), routes: { + NetworkTileProviderPage.route: (context) => NetworkTileProviderPage(), WidgetsPage.route: (context) => WidgetsPage(), TapToAddPage.route: (context) => TapToAddPage(), EsriPage.route: (context) => EsriPage(), diff --git a/example/lib/pages/animated_map_controller.dart b/example/lib/pages/animated_map_controller.dart index f18074cb0..00de5c0f8 100644 --- a/example/lib/pages/animated_map_controller.dart +++ b/example/lib/pages/animated_map_controller.dart @@ -29,7 +29,7 @@ class AnimatedMapControllerPageState extends State static LatLng paris = LatLng(48.8566, 2.3522); static LatLng dublin = LatLng(53.3498, -6.2603); - MapController mapController; + late final MapController mapController; @override void initState() { diff --git a/example/lib/pages/custom_crs/custom_crs.dart b/example/lib/pages/custom_crs/custom_crs.dart index 95dc14189..e670f316a 100644 --- a/example/lib/pages/custom_crs/custom_crs.dart +++ b/example/lib/pages/custom_crs/custom_crs.dart @@ -14,30 +14,30 @@ class CustomCrsPage extends StatefulWidget { } class _CustomCrsPageState extends State { - Proj4Crs epsg3413CRS; + late final Proj4Crs epsg3413CRS; - double maxZoom; + double? maxZoom; // Define start center proj4.Point point = proj4.Point(x: 65.05166470332148, y: -19.171744826394896); String initText = 'Map centered to'; - proj4.Projection epsg4326; + late final proj4.Projection epsg4326; - proj4.Projection epsg3413; + late final proj4.Projection epsg3413; @override void initState() { super.initState(); // EPSG:4326 is a predefined projection ships with proj4dart - epsg4326 = proj4.Projection('EPSG:4326'); + epsg4326 = proj4.Projection.get('EPSG:4326')!; // EPSG:3413 is a user-defined projection from a valid Proj4 definition string // From: http://epsg.io/3413, proj definition: http://epsg.io/3413.proj4 // Find Projection by name or define it if not exists - epsg3413 = proj4.Projection('EPSG:3413') ?? + epsg3413 = proj4.Projection.get('EPSG:3413') ?? proj4.Projection.add('EPSG:3413', '+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs'); diff --git a/example/lib/pages/interactive_test_page.dart b/example/lib/pages/interactive_test_page.dart index c3c684de4..40fdc50e4 100644 --- a/example/lib/pages/interactive_test_page.dart +++ b/example/lib/pages/interactive_test_page.dart @@ -16,12 +16,12 @@ class InteractiveTestPage extends StatefulWidget { } class _InteractiveTestPageState extends State { - MapController mapController; + late final MapController mapController; // Enable pinchZoom and doubleTapZoomBy by default int flags = InteractiveFlag.pinchZoom | InteractiveFlag.doubleTapZoom; - StreamSubscription subscription; + late final StreamSubscription subscription; @override void initState() { @@ -159,7 +159,7 @@ class _InteractiveTestPageState extends State { } return Text( - 'Current event: ${snapshot.data.runtimeType}\nSource: ${snapshot.data.source}', + 'Current event: ${snapshot.data.runtimeType}\nSource: ${snapshot.data!.source}', textAlign: TextAlign.center, ); }, diff --git a/example/lib/pages/live_location.dart b/example/lib/pages/live_location.dart index e5d14b017..4d2f4e34e 100644 --- a/example/lib/pages/live_location.dart +++ b/example/lib/pages/live_location.dart @@ -14,13 +14,13 @@ class LiveLocationPage extends StatefulWidget { } class _LiveLocationPageState extends State { - LocationData _currentLocation; - MapController _mapController; + LocationData? _currentLocation; + late final MapController _mapController; bool _liveUpdate = false; bool _permission = false; - String _serviceError = ''; + String? _serviceError = ''; var interActiveFlags = InteractiveFlag.all; @@ -35,11 +35,11 @@ class _LiveLocationPageState extends State { void initLocationService() async { await _locationService.changeSettings( - accuracy: LocationAccuracy.HIGH, + accuracy: LocationAccuracy.high, interval: 1000, ); - LocationData location; + LocationData? location; bool serviceEnabled; bool serviceRequestResult; @@ -48,13 +48,12 @@ class _LiveLocationPageState extends State { if (serviceEnabled) { var permission = await _locationService.requestPermission(); - _permission = permission == PermissionStatus.GRANTED; + _permission = permission == PermissionStatus.granted; if (_permission) { location = await _locationService.getLocation(); _currentLocation = location; - _locationService - .onLocationChanged() + _locationService.onLocationChanged .listen((LocationData result) async { if (mounted) { setState(() { @@ -63,8 +62,8 @@ class _LiveLocationPageState extends State { // If Live Update is enabled, move map center if (_liveUpdate) { _mapController.move( - LatLng(_currentLocation.latitude, - _currentLocation.longitude), + LatLng(_currentLocation!.latitude!, + _currentLocation!.longitude!), _mapController.zoom); } }); @@ -97,7 +96,7 @@ class _LiveLocationPageState extends State { // by default or store previous location value to show. if (_currentLocation != null) { currentLatLng = - LatLng(_currentLocation.latitude, _currentLocation.longitude); + LatLng(_currentLocation!.latitude!, _currentLocation!.longitude!); } else { currentLatLng = LatLng(0, 0); } @@ -125,7 +124,7 @@ class _LiveLocationPageState extends State { children: [ Padding( padding: EdgeInsets.only(top: 8.0, bottom: 8.0), - child: _serviceError.isEmpty + child: _serviceError!.isEmpty ? Text('This is a map that is showing ' '(${currentLatLng.latitude}, ${currentLatLng.longitude}).') : Text( diff --git a/example/lib/pages/map_controller.dart b/example/lib/pages/map_controller.dart index 676b642ee..ec3e4658e 100644 --- a/example/lib/pages/map_controller.dart +++ b/example/lib/pages/map_controller.dart @@ -21,7 +21,7 @@ class MapControllerPageState extends State { static LatLng paris = LatLng(48.8566, 2.3522); static LatLng dublin = LatLng(53.3498, -6.2603); - MapController mapController; + late final MapController mapController; double rotation = 0.0; @override @@ -119,7 +119,7 @@ class MapControllerPageState extends State { Builder(builder: (BuildContext context) { return MaterialButton( onPressed: () { - final bounds = mapController.bounds; + final bounds = mapController.bounds!; ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( @@ -178,8 +178,8 @@ class MapControllerPageState extends State { class CurrentLocation extends StatefulWidget { const CurrentLocation({ - Key key, - @required this.mapController, + Key? key, + required this.mapController, }) : super(key: key); final MapController mapController; @@ -192,7 +192,7 @@ class _CurrentLocationState extends State { int _eventKey = 0; var icon = Icons.gps_not_fixed; - StreamSubscription mapEventSubscription; + late final StreamSubscription mapEventSubscription; @override void initState() { @@ -229,7 +229,7 @@ class _CurrentLocationState extends State { try { var currentLocation = await location.getLocation(); var moved = widget.mapController.move( - LatLng(currentLocation.latitude, currentLocation.longitude), + LatLng(currentLocation.latitude!, currentLocation.longitude!), 18, id: _eventKey.toString(), ); diff --git a/example/lib/pages/marker_anchor.dart b/example/lib/pages/marker_anchor.dart index e59435561..8f5c4e482 100644 --- a/example/lib/pages/marker_anchor.dart +++ b/example/lib/pages/marker_anchor.dart @@ -13,7 +13,7 @@ class MarkerAnchorPage extends StatefulWidget { } class MarkerAnchorPageState extends State { - AnchorPos anchorPos; + late AnchorPos anchorPos; @override void initState() { diff --git a/example/lib/pages/marker_rotate.dart b/example/lib/pages/marker_rotate.dart index 95cc72ced..a63ba2bef 100644 --- a/example/lib/pages/marker_rotate.dart +++ b/example/lib/pages/marker_rotate.dart @@ -13,9 +13,9 @@ class MarkerRotatePage extends StatefulWidget { } class MarkerRotatePageState extends State { - bool rotateMarkerLondon; - bool rotateMarkerDublin; - bool rotateMarkerParis; + bool? rotateMarkerLondon; + bool? rotateMarkerDublin; + bool? rotateMarkerParis; bool rotateMarkerLayerOptions = false; @override @@ -33,7 +33,7 @@ class MarkerRotatePageState extends State { setState(() { if (rotateMarkerLondon == null) { rotateMarkerLondon = true; - } else if (rotateMarkerLondon) { + } else if (rotateMarkerLondon != null) { rotateMarkerLondon = false; } else { rotateMarkerLondon = null; @@ -45,7 +45,7 @@ class MarkerRotatePageState extends State { setState(() { if (rotateMarkerDublin == null) { rotateMarkerDublin = true; - } else if (rotateMarkerDublin) { + } else if (rotateMarkerDublin != null) { rotateMarkerDublin = false; } else { rotateMarkerDublin = null; @@ -57,7 +57,7 @@ class MarkerRotatePageState extends State { setState(() { if (rotateMarkerParis == null) { rotateMarkerParis = true; - } else if (rotateMarkerParis) { + } else if (rotateMarkerParis != null) { rotateMarkerParis = false; } else { rotateMarkerParis = null; diff --git a/example/lib/pages/moving_markers.dart b/example/lib/pages/moving_markers.dart index c561db011..7d015a288 100644 --- a/example/lib/pages/moving_markers.dart +++ b/example/lib/pages/moving_markers.dart @@ -16,8 +16,8 @@ class MovingMarkersPage extends StatefulWidget { } class _MovingMarkersPageState extends State { - Marker _marker; - Timer _timer; + Marker? _marker; + late final Timer _timer; int _markerIndex = 0; @override @@ -62,7 +62,7 @@ class _MovingMarkersPageState extends State { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c']), - MarkerLayerOptions(markers: [_marker]) + MarkerLayerOptions(markers: [_marker!]) ], ), ), diff --git a/example/lib/pages/network_tile_provider.dart b/example/lib/pages/network_tile_provider.dart new file mode 100644 index 000000000..b93c6a858 --- /dev/null +++ b/example/lib/pages/network_tile_provider.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; + +import '../widgets/drawer.dart'; + +class NetworkTileProviderPage extends StatelessWidget { + static const String route = 'NetworkTileProvider'; + + @override + Widget build(BuildContext context) { + var markers = [ + Marker( + width: 80.0, + height: 80.0, + point: LatLng(51.5, -0.09), + builder: (ctx) => Container( + child: FlutterLogo( + textColor: Colors.blue, + key: ObjectKey(Colors.blue), + ), + ), + ), + Marker( + width: 80.0, + height: 80.0, + point: LatLng(53.3498, -6.2603), + builder: (ctx) => Container( + child: FlutterLogo( + textColor: Colors.green, + key: ObjectKey(Colors.green), + ), + ), + ), + Marker( + width: 80.0, + height: 80.0, + point: LatLng(48.8566, 2.3522), + builder: (ctx) => Container( + child: FlutterLogo( + textColor: Colors.purple, + key: ObjectKey(Colors.purple), + ), + ), + ), + ]; + + return Scaffold( + appBar: AppBar(title: Text('NetworkTileProvider')), + drawer: buildDrawer(context, route), + body: Padding( + padding: EdgeInsets.all(8.0), + child: Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 8.0, bottom: 8.0), + child: Wrap(children: [ + Text('This Provider does not provide caching.'), + Text( + 'For further options about that, check flutter_map\'s README on GitHub.'), + ]), + ), + Flexible( + child: FlutterMap( + options: MapOptions( + center: LatLng(51.5, -0.09), + zoom: 5.0, + ), + layers: [ + TileLayerOptions( + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + // For example purposes. It is recommended to use + // TileProvider with a caching and retry strategy, like + // NetworkTileProvider or CachedNetworkTileProvider + tileProvider: NetworkTileProvider(), + ), + MarkerLayerOptions(markers: markers) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/pages/plugin_api.dart b/example/lib/pages/plugin_api.dart index da894a06b..7c87fe600 100644 --- a/example/lib/pages/plugin_api.dart +++ b/example/lib/pages/plugin_api.dart @@ -46,9 +46,9 @@ class PluginPage extends StatelessWidget { class MyCustomPluginOptions extends LayerOptions { final String text; MyCustomPluginOptions({ - Key key, + Key? key, this.text = '', - Stream rebuild, + Stream? rebuild, }) : super(key: key, rebuild: rebuild); } diff --git a/example/lib/pages/scale_layer_plugin_option.dart b/example/lib/pages/scale_layer_plugin_option.dart index 14bdf1201..99e83025f 100644 --- a/example/lib/pages/scale_layer_plugin_option.dart +++ b/example/lib/pages/scale_layer_plugin_option.dart @@ -8,18 +8,18 @@ import 'package:flutter_map/plugin_api.dart'; import './scalebar_utils.dart' as util; class ScaleLayerPluginOption extends LayerOptions { - TextStyle textStyle; + TextStyle? textStyle; Color lineColor; double lineWidth; - final EdgeInsets padding; + final EdgeInsets? padding; ScaleLayerPluginOption({ - Key key, + Key? key, this.textStyle, this.lineColor = Colors.white, this.lineWidth = 2, this.padding, - Stream rebuild, + Stream? rebuild, }) : super(key: key, rebuild: rebuild); } @@ -84,7 +84,7 @@ class ScaleLayer extends StatelessWidget { var displayDistance = distance > 999 ? '${(distance / 1000).toStringAsFixed(0)} km' : '${distance.toStringAsFixed(0)} m'; - double width = (end.x - start.x); + var width = (end.x - (start.x as double)); return CustomPaint( painter: ScalePainter( @@ -103,36 +103,38 @@ class ScalePainter extends CustomPainter { ScalePainter(this.width, this.text, {this.padding, this.textStyle, this.lineWidth, this.lineColor}); final double width; - final EdgeInsets padding; + final EdgeInsets? padding; final String text; - TextStyle textStyle; - double lineWidth; - Color lineColor; + TextStyle? textStyle; + double? lineWidth; + Color? lineColor; @override void paint(ui.Canvas canvas, ui.Size size) { final paint = Paint() - ..color = lineColor + ..color = lineColor! ..strokeCap = StrokeCap.square - ..strokeWidth = lineWidth; + ..strokeWidth = lineWidth!; var sizeForStartEnd = 4; - var paddingLeft = padding == null ? 0 : padding.left + sizeForStartEnd / 2; - var paddingTop = padding == null ? 0 : padding.top; + var paddingLeft = padding == null ? 0 : padding!.left + sizeForStartEnd / 2; + var paddingTop = padding == null ? 0 : padding!.top; var textSpan = TextSpan(style: textStyle, text: text); var textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr)..layout(); - textPainter.paint(canvas, - Offset(width / 2 - textPainter.width / 2 + paddingLeft, paddingTop)); + textPainter.paint( + canvas, + Offset(width / 2 - textPainter.width / 2 + paddingLeft, + paddingTop as double)); paddingTop += textPainter.height; - var p1 = Offset(paddingLeft, sizeForStartEnd + paddingTop); + var p1 = Offset(paddingLeft as double, sizeForStartEnd + paddingTop); var p2 = Offset(paddingLeft + width, sizeForStartEnd + paddingTop); // draw start line canvas.drawLine(Offset(paddingLeft, paddingTop), Offset(paddingLeft, sizeForStartEnd + paddingTop), paint); // draw middle line - var middleX = width / 2 + paddingLeft - lineWidth / 2; + var middleX = width / 2 + paddingLeft - lineWidth! / 2; canvas.drawLine(Offset(middleX, paddingTop + sizeForStartEnd / 2), Offset(middleX, sizeForStartEnd + paddingTop), paint); // draw end line diff --git a/example/lib/pages/scalebar_utils.dart b/example/lib/pages/scalebar_utils.dart index 204232445..f888e2e8d 100644 --- a/example/lib/pages/scalebar_utils.dart +++ b/example/lib/pages/scalebar_utils.dart @@ -1,4 +1,5 @@ import 'dart:math'; + import 'package:latlong2/latlong.dart'; const double piOver180 = pi / 180.0; diff --git a/example/lib/pages/stateful_markers.dart b/example/lib/pages/stateful_markers.dart index d7fedb762..c48469bd7 100644 --- a/example/lib/pages/stateful_markers.dart +++ b/example/lib/pages/stateful_markers.dart @@ -14,7 +14,7 @@ class StatefulMarkersPage extends StatefulWidget { } class _StatefulMarkersPageState extends State { - List _markers; + late List _markers; final Random _random = Random(); @override @@ -84,14 +84,14 @@ class _StatefulMarkersPageState extends State { } class _ColorMarker extends StatefulWidget { - _ColorMarker({Key key}) : super(key: key); + _ColorMarker({Key? key}) : super(key: key); @override _ColorMarkerState createState() => _ColorMarkerState(); } class _ColorMarkerState extends State<_ColorMarker> { - Color color; + late final Color color; @override void initState() { diff --git a/example/lib/pages/tile_builder_example.dart b/example/lib/pages/tile_builder_example.dart index 44cf6d2ca..bdf5f5b55 100644 --- a/example/lib/pages/tile_builder_example.dart +++ b/example/lib/pages/tile_builder_example.dart @@ -43,7 +43,7 @@ class _TileBuilderPageState extends State { tile.loaded == null ? 'Loading' // sometimes result is negative which shouldn't happen, abs() corrects it - : '${(tile.loaded.millisecond - tile.loadStarted.millisecond).abs()} ms', + : '${(tile.loaded!.millisecond - tile.loadStarted.millisecond).abs()} ms', style: Theme.of(context).textTheme.headline5, ), ], diff --git a/example/lib/pages/tile_loading_error_handle.dart b/example/lib/pages/tile_loading_error_handle.dart index 9ff249af9..03c2392c0 100644 --- a/example/lib/pages/tile_loading_error_handle.dart +++ b/example/lib/pages/tile_loading_error_handle.dart @@ -48,7 +48,7 @@ class _TileLoadingErrorHandleState extends State { tileProvider: NonCachingNetworkTileProvider(), errorTileCallback: (Tile tile, error) { if (_needLoadingError) { - WidgetsBinding.instance.addPostFrameCallback((_) { + WidgetsBinding.instance!.addPostFrameCallback((_) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( duration: Duration(seconds: 1), content: Text( diff --git a/example/lib/pages/widgets.dart b/example/lib/pages/widgets.dart index 0c7da07f7..458a26a10 100644 --- a/example/lib/pages/widgets.dart +++ b/example/lib/pages/widgets.dart @@ -74,8 +74,8 @@ class MovingWithoutRefreshAllMapMarkers extends StatefulWidget { class _MovingWithoutRefreshAllMapMarkersState extends State { - Marker _marker; - Timer _timer; + Marker? _marker; + Timer? _timer; int _markerIndex = 0; @override @@ -99,7 +99,7 @@ class _MovingWithoutRefreshAllMapMarkersState @override Widget build(BuildContext context) { return MarkerLayerWidget( - options: MarkerLayerOptions(markers: [_marker]), + options: MarkerLayerOptions(markers: [_marker!]), ); } } diff --git a/example/lib/pages/zoombuttons_plugin_option.dart b/example/lib/pages/zoombuttons_plugin_option.dart index 9f27b7021..fa879d223 100644 --- a/example/lib/pages/zoombuttons_plugin_option.dart +++ b/example/lib/pages/zoombuttons_plugin_option.dart @@ -8,15 +8,15 @@ class ZoomButtonsPluginOption extends LayerOptions { final bool mini; final double padding; final Alignment alignment; - final Color zoomInColor; - final Color zoomInColorIcon; - final Color zoomOutColor; - final Color zoomOutColorIcon; + final Color? zoomInColor; + final Color? zoomInColorIcon; + final Color? zoomOutColor; + final Color? zoomOutColorIcon; final IconData zoomInIcon; final IconData zoomOutIcon; ZoomButtonsPluginOption({ - Key key, + Key? key, this.minZoom = 1, this.maxZoom = 18, this.mini = true, @@ -28,7 +28,7 @@ class ZoomButtonsPluginOption extends LayerOptions { this.zoomOutColor, this.zoomOutColorIcon, this.zoomOutIcon = Icons.zoom_out, - Stream rebuild, + Stream? rebuild, }) : super(key: key, rebuild: rebuild); } @@ -82,7 +82,8 @@ class ZoomButtons extends StatelessWidget { if (zoom < zoomButtonsOpts.minZoom) { zoom = zoomButtonsOpts.minZoom as double; } else { - map.move(centerZoom.center, zoom); + map.move(centerZoom.center, zoom, + source: MapEventSource.custom); } }, child: Icon(zoomButtonsOpts.zoomInIcon, @@ -104,7 +105,8 @@ class ZoomButtons extends StatelessWidget { if (zoom > zoomButtonsOpts.maxZoom) { zoom = zoomButtonsOpts.maxZoom as double; } else { - map.move(centerZoom.center, zoom); + map.move(centerZoom.center, zoom, + source: MapEventSource.custom); } }, child: Icon(zoomButtonsOpts.zoomOutIcon, diff --git a/example/lib/widgets/drawer.dart b/example/lib/widgets/drawer.dart index a824bf190..799ce50c8 100644 --- a/example/lib/widgets/drawer.dart +++ b/example/lib/widgets/drawer.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_map_example/pages/marker_rotate.dart'; +import 'package:flutter_map_example/pages/network_tile_provider.dart'; import '../pages/animated_map_controller.dart'; import '../pages/circle.dart'; @@ -59,6 +60,12 @@ Drawer buildDrawer(BuildContext context, String currentRoute) { HomePage.route, currentRoute, ), + _buildMenuItem( + context, + const Text('NetworkTileProvider'), + NetworkTileProviderPage.route, + currentRoute, + ), _buildMenuItem( context, const Text('WMS Layer'), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 1589dd712..78027d9c4 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -18,22 +18,22 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: - flutter: - sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + flutter: + sdk: flutter flutter_map: path: ../ - location: ^2.5.0 + location: ^4.1.1 dev_dependencies: - pedantic: ^1.11.0 flutter_test: sdk: flutter + pedantic: ^1.11.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/flutter_map.iml b/flutter_map.iml index 6048a33bd..e9a86d25d 100644 --- a/flutter_map.iml +++ b/flutter_map.iml @@ -16,4 +16,4 @@ - \ No newline at end of file + diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 25939818c..7fade1536 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -66,18 +66,17 @@ class FlutterMap extends StatefulWidget { final MapOptions options; /// A [MapController], used to control the map. - final MapControllerImpl _mapController; + final MapControllerImpl? _mapController; FlutterMap({ - Key key, - @required this.options, + Key? key, + required this.options, this.layers = const [], this.nonRotatedLayers = const [], this.children = const [], this.nonRotatedChildren = const [], - MapController mapController, - }) : assert(options != null, 'MapOptions cannot be null!'), - _mapController = mapController, + MapController? mapController, + }) : _mapController = mapController as MapControllerImpl?, super(key: key); @override @@ -101,7 +100,7 @@ abstract class MapController { /// returns `true` if move was success (for example it won't be success if /// navigating to same place with same zoom or if center is out of bounds and /// [MapOptions.slideOnBoundaries] isn't enabled) - bool move(LatLng center, double zoom, {String id}); + bool move(LatLng center, double zoom, {String? id}); /// Sets the map rotation to a certain degrees angle (in decimal). /// @@ -112,24 +111,22 @@ abstract class MapController { /// /// returns `true` if rotate was success (it won't be success if rotate is /// same as the old rotate) - bool rotate(double degree, {String id}); + bool rotate(double degree, {String? id}); /// Calls [move] and [rotate] together however layers will rebuild just once /// instead of twice MoveAndRotateResult moveAndRotate(LatLng center, double zoom, double degree, - {String id}); + {String? id}); /// Fits the map bounds. Optional constraints can be defined /// through the [options] parameter. - void fitBounds(LatLngBounds bounds, {FitBoundsOptions options}); - - bool get ready; + void fitBounds(LatLngBounds bounds, {FitBoundsOptions? options}); Future get onReady; LatLng get center; - LatLngBounds get bounds; + LatLngBounds? get bounds; double get zoom; @@ -220,39 +217,35 @@ class MapOptions { /// gestures will take effect see [MultiFingerGesture] for custom settings final int pinchMoveWinGestures; - final double minZoom; - final double maxZoom; - @deprecated - final bool debug; // TODO no usage outside of constructor. Marked for removal? - @Deprecated('use interactiveFlags instead') - final bool interactive; + final double? minZoom; + final double? maxZoom; /// see [InteractiveFlag] for custom settings final int interactiveFlags; final bool allowPanning; - final TapCallback onTap; - final LongPressCallback onLongPress; - final PositionCallback onPositionChanged; - final MapCreatedCallback onMapCreated; + final TapCallback? onTap; + final LongPressCallback? onLongPress; + final PositionCallback? onPositionChanged; + final MapCreatedCallback? onMapCreated; final List plugins; final bool slideOnBoundaries; - final Size screenSize; + final Size? screenSize; final bool adaptiveBoundaries; - final MapController controller; + final MapController? controller; final LatLng center; - final LatLngBounds bounds; + final LatLngBounds? bounds; final FitBoundsOptions boundsOptions; - final LatLng swPanBoundary; - final LatLng nePanBoundary; + final LatLng? swPanBoundary; + final LatLng? nePanBoundary; - _SafeArea _safeAreaCache; - double _safeAreaZoom; + _SafeArea? _safeAreaCache; + double? _safeAreaZoom; MapOptions({ this.crs = const Epsg3857(), - LatLng center, + LatLng? center, this.bounds, this.boundsOptions = const FitBoundsOptions(), this.zoom = 13.0, @@ -269,12 +262,7 @@ class MapOptions { MultiFingerGesture.pinchZoom | MultiFingerGesture.pinchMove, this.minZoom, this.maxZoom, - @Deprecated('') this.debug = false, - @Deprecated('Use interactiveFlags instead') this.interactive, - // TODO: Change when [interactive] is removed. - // Change this to [this.interactiveFlags = InteractiveFlag.all] and remove - // [interactiveFlags] from initializer list - int interactiveFlags, + this.interactiveFlags = InteractiveFlag.all, this.allowPanning = true, this.onTap, this.onLongPress, @@ -287,9 +275,7 @@ class MapOptions { this.controller, this.swPanBoundary, this.nePanBoundary, - }) : interactiveFlags = interactiveFlags ?? - (interactive == false ? InteractiveFlag.none : InteractiveFlag.all), - center = center ?? LatLng(50.5, 30.51), + }) : center = center ?? LatLng(50.5, 30.51), assert(rotationThreshold >= 0.0), assert(pinchZoomThreshold >= 0.0), assert(pinchMoveThreshold >= 0.0) { @@ -303,18 +289,18 @@ class MapOptions { } //if there is a pan boundary, do not cross - bool isOutOfBounds(LatLng center) { + bool isOutOfBounds(LatLng? center) { if (adaptiveBoundaries) { - return !_safeArea.contains(center); + return !_safeArea!.contains(center); } if (swPanBoundary != null && nePanBoundary != null) { if (center == null) { return true; - } else if (center.latitude < swPanBoundary.latitude || - center.latitude > nePanBoundary.latitude) { + } else if (center.latitude < swPanBoundary!.latitude || + center.latitude > nePanBoundary!.latitude) { return true; - } else if (center.longitude < swPanBoundary.longitude || - center.longitude > nePanBoundary.longitude) { + } else if (center.longitude < swPanBoundary!.longitude || + center.longitude > nePanBoundary!.longitude) { return true; } } @@ -323,25 +309,26 @@ class MapOptions { LatLng containPoint(LatLng point, LatLng fallback) { if (adaptiveBoundaries) { - return _safeArea.containPoint(point, fallback); + return _safeArea!.containPoint(point, fallback); } else { return LatLng( - point.latitude.clamp(swPanBoundary.latitude, nePanBoundary.latitude), - point.longitude.clamp(swPanBoundary.longitude, nePanBoundary.longitude), + point.latitude.clamp(swPanBoundary!.latitude, nePanBoundary!.latitude), + point.longitude + .clamp(swPanBoundary!.longitude, nePanBoundary!.longitude), ); } } - _SafeArea get _safeArea { + _SafeArea? get _safeArea { final controllerZoom = _getControllerZoom(); if (controllerZoom != _safeAreaZoom || _safeAreaCache == null) { _safeAreaZoom = controllerZoom; final halfScreenHeight = _calculateScreenHeightInDegrees() / 2; final halfScreenWidth = _calculateScreenWidthInDegrees() / 2; - final southWestLatitude = swPanBoundary.latitude + halfScreenHeight; - final southWestLongitude = swPanBoundary.longitude + halfScreenWidth; - final northEastLatitude = nePanBoundary.latitude - halfScreenHeight; - final northEastLongitude = nePanBoundary.longitude - halfScreenWidth; + final southWestLatitude = swPanBoundary!.latitude + halfScreenHeight; + final southWestLongitude = swPanBoundary!.longitude + halfScreenWidth; + final northEastLatitude = nePanBoundary!.latitude - halfScreenHeight; + final northEastLongitude = nePanBoundary!.longitude - halfScreenWidth; _safeAreaCache = _SafeArea( LatLng( southWestLatitude, @@ -359,19 +346,19 @@ class MapOptions { double _calculateScreenWidthInDegrees() { final zoom = _getControllerZoom(); final degreesPerPixel = 360 / pow(2, zoom + 8); - return screenSize.width * degreesPerPixel; + return screenSize!.width * degreesPerPixel; } double _calculateScreenHeightInDegrees() => - screenSize.height * 170.102258 / pow(2, _getControllerZoom() + 8); + screenSize!.height * 170.102258 / pow(2, _getControllerZoom() + 8); - double _getControllerZoom() => controller.ready ? controller.zoom : zoom; + double _getControllerZoom() => controller!.zoom; } class FitBoundsOptions { final EdgeInsets padding; final double maxZoom; - final double zoom; + final double? zoom; const FitBoundsOptions({ this.padding = const EdgeInsets.all(0.0), @@ -382,9 +369,9 @@ class FitBoundsOptions { /// Position's type for [PositionCallback]. class MapPosition { - final LatLng center; - final LatLngBounds bounds; - final double zoom; + final LatLng? center; + final LatLngBounds? bounds; + final double? zoom; final bool hasGesture; MapPosition({this.center, this.bounds, this.zoom, this.hasGesture = false}); diff --git a/lib/src/core/bounds.dart b/lib/src/core/bounds.dart index 5111ac3d7..c1408faf8 100644 --- a/lib/src/core/bounds.dart +++ b/lib/src/core/bounds.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; + import 'package:flutter_map/src/core/point.dart'; /// Rectangular bound delimited by orthogonal lines passing through two @@ -18,31 +19,24 @@ class Bounds { /// Creates a new [Bounds] obtained by expanding the current ones with a new /// point. Bounds extend(CustomPoint point) { - CustomPoint newMin; - CustomPoint newMax; - if (min == null && max == null) { - newMin = point; - newMax = point; - } else { - var minX = math.min(point.x, min.x); - var maxX = math.max(point.x, max.x); - var minY = math.min(point.y, min.y); - var maxY = math.max(point.y, max.y); - newMin = CustomPoint(minX, minY); - newMax = CustomPoint(maxX, maxY); - } - return Bounds._(newMin, newMax); - } - - /// This [Bounds] cental point. - CustomPoint getCenter() { - //TODO should this be a getter? - return CustomPoint( - (min.x + max.x) / 2, - (min.y + max.y) / 2, + return Bounds._( + CustomPoint( + math.min(point.x, min.x), + math.min(point.y, min.y), + ), + CustomPoint( + math.max(point.x, max.x), + math.max(point.y, max.y), + ), ); } + /// This [Bounds] central point. + CustomPoint get center => CustomPoint( + (min.x + max.x) / 2, + (min.y + max.y) / 2, + ); + /// Bottom-Left corner's point. CustomPoint get bottomLeft => CustomPoint(min.x, max.y); diff --git a/lib/src/core/center_zoom.dart b/lib/src/core/center_zoom.dart index 334abf8dc..527768d47 100644 --- a/lib/src/core/center_zoom.dart +++ b/lib/src/core/center_zoom.dart @@ -3,5 +3,5 @@ import 'package:latlong2/latlong.dart'; class CenterZoom { final LatLng center; final double zoom; - CenterZoom({this.center, this.zoom}); + CenterZoom({required this.center, required this.zoom}); } diff --git a/lib/src/core/point.dart b/lib/src/core/point.dart index de28370b1..19b6cef0e 100644 --- a/lib/src/core/point.dart +++ b/lib/src/core/point.dart @@ -1,7 +1,7 @@ import 'dart:math' as math; class CustomPoint extends math.Point { - const CustomPoint(num x, num y) : super(x, y); + const CustomPoint(num x, num y) : super(x as T, y as T); CustomPoint operator /(num /*T|int*/ factor) { return CustomPoint(x / factor, y / factor); @@ -39,8 +39,8 @@ class CustomPoint extends math.Point { } CustomPoint round() { - var x = this.x is double ? this.x.round() : this.x; - var y = this.y is double ? this.y.round() : this.y; + final x = this.x is double ? this.x.round() : this.x; + final y = this.y is double ? this.y.round() : this.y; return CustomPoint(x, y); } diff --git a/lib/src/core/util.dart b/lib/src/core/util.dart index 004a281b7..f7a1de8a7 100644 --- a/lib/src/core/util.dart +++ b/lib/src/core/util.dart @@ -6,10 +6,16 @@ var _templateRe = RegExp(r'\{ *([\w_-]+) *\}'); /// Replaces the templating placeholders with the provided data map. /// +/// Example input: https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png +/// /// Throws an [Exception] if any placeholder remains unresolved. String template(String str, Map data) { return str.replaceAllMapped(_templateRe, (Match match) { - var value = data[match.group(1)]; + var firstMatch = match.group(1); + if (firstMatch == null) { + throw Exception('incorrect URL template: $str'); + } + var value = data[firstMatch]; if (value == null) { throw Exception('No value provided for variable ${match.group(1)}'); } else { @@ -18,7 +24,7 @@ String template(String str, Map data) { }); } -double wrapNum(double x, Tuple2 range, [bool includeMax]) { +double wrapNum(double x, Tuple2 range, [bool? includeMax]) { var max = range.item2; var min = range.item1; var d = max - min; @@ -27,11 +33,12 @@ double wrapNum(double x, Tuple2 range, [bool includeMax]) { StreamTransformer throttleStreamTransformerWithTrailingCall( Duration duration) { - Timer timer; + Timer? timer; T recentData; var trailingCall = false; - void Function(T data, EventSink sink) throttleHandler; + late final void Function(T data, EventSink sink) throttleHandler; + throttleHandler = (T data, EventSink sink) { recentData = data; diff --git a/lib/src/geo/crs/crs.dart b/lib/src/geo/crs/crs.dart index e66e14de9..522701bd7 100644 --- a/lib/src/geo/crs/crs.dart +++ b/lib/src/geo/crs/crs.dart @@ -34,7 +34,7 @@ abstract class Crs { } /// Converts a map point to the sphere coordinate (at a certain zoom). - LatLng pointToLatLng(CustomPoint point, double zoom) { + LatLng? pointToLatLng(CustomPoint point, double zoom) { var scale = this.scale(zoom); var untransformedPoint = transformation.untransform(point, scale.toDouble()); @@ -56,10 +56,10 @@ abstract class Crs { } /// Rescales the bounds to a given zoom value. - Bounds getProjectedBounds(double zoom) { + Bounds? getProjectedBounds(double zoom) { if (infinite) return null; - var b = projection.bounds; + var b = projection.bounds!; var s = scale(zoom); var min = transformation.transform(b.min, s.toDouble()); var max = transformation.transform(b.max, s.toDouble()); @@ -68,9 +68,9 @@ abstract class Crs { bool get infinite; - Tuple2 get wrapLng; + Tuple2? get wrapLng; - Tuple2 get wrapLat; + Tuple2? get wrapLat; } // Custom CRS for non geographical maps @@ -93,10 +93,10 @@ class CrsSimple extends Crs { bool get infinite => false; @override - Tuple2 get wrapLat => null; + Tuple2? get wrapLat => null; @override - Tuple2 get wrapLng => null; + Tuple2? get wrapLng => null; } abstract class Earth extends Crs { @@ -107,7 +107,7 @@ abstract class Earth extends Crs { final Tuple2 wrapLng = const Tuple2(-180.0, 180.0); @override - final Tuple2 wrapLat = null; + final Tuple2? wrapLat = null; const Earth() : super(); } @@ -167,42 +167,37 @@ class Proj4Crs extends Crs { final bool infinite; @override - final Tuple2 wrapLat = null; + final Tuple2? wrapLat = null; @override - final Tuple2 wrapLng = null; + final Tuple2? wrapLng = null; - final List _transformations; + final List? _transformations; final List _scales; Proj4Crs._({ - @required this.code, - @required this.projection, - @required this.transformation, - @required this.infinite, - @required List transformations, - @required List scales, - }) : assert(null != code), - assert(null != projection), - assert(null != transformation || null != transformations), - assert(null != infinite), - assert(null != scales), - _transformations = transformations, + required this.code, + required this.projection, + required this.transformation, + required this.infinite, + List? transformations, + required List scales, + }) : _transformations = transformations, _scales = scales; factory Proj4Crs.fromFactory({ - @required String code, - @required proj4.Projection proj4Projection, - Transformation transformation, - List origins, - Bounds bounds, - List scales, - List resolutions, + required String code, + required proj4.Projection proj4Projection, + Transformation? transformation, + List? origins, + Bounds? bounds, + List? scales, + List? resolutions, }) { final projection = _Proj4Projection(proj4Projection: proj4Projection, bounds: bounds); - List transformations; + List? transformations; var infinite = null == bounds; List finalScales; @@ -231,7 +226,7 @@ class Proj4Crs extends Crs { return Proj4Crs._( code: code, projection: projection, - transformation: transformation, + transformation: transformation!, infinite: infinite, transformations: transformations, scales: finalScales, @@ -255,7 +250,7 @@ class Proj4Crs extends Crs { /// Converts a map point to the sphere coordinate (at a certain zoom). @override - LatLng pointToLatLng(CustomPoint point, double zoom) { + LatLng? pointToLatLng(CustomPoint point, double zoom) { var scale = this.scale(zoom); var transformation = _getTransformationByZoom(zoom); @@ -270,10 +265,10 @@ class Proj4Crs extends Crs { /// Rescales the bounds to a given zoom value. @override - Bounds getProjectedBounds(double zoom) { + Bounds? getProjectedBounds(double zoom) { if (infinite) return null; - var b = projection.bounds; + var b = projection.bounds!; var s = scale(zoom); var transformation = _getTransformationByZoom(zoom); @@ -304,27 +299,25 @@ class Proj4Crs extends Crs { num zoom(double scale) { // Find closest number in _scales, down var downScale = _closestElement(_scales, scale); + if (downScale == null) { + return double.negativeInfinity; + } var downZoom = _scales.indexOf(downScale); // Check if scale is downScale => return array index if (scale == downScale) { return downZoom; } - if (downScale == null) { - return double.negativeInfinity; - } // Interpolate var nextZoom = downZoom + 1; var nextScale = _scales[nextZoom]; - if (nextScale == null) { - return double.infinity; - } + var scaleDiff = nextScale - downScale; return (scale - downScale) / scaleDiff + downZoom; } /// Get the closest lowest element in an array - double _closestElement(List array, double element) { - double low; + double? _closestElement(List array, double element) { + double? low; for (var i = array.length - 1; i >= 0; i--) { var curr = array[i]; @@ -342,24 +335,24 @@ class Proj4Crs extends Crs { } var iZoom = zoom.round(); - var lastIdx = _transformations.length - 1; + var lastIdx = _transformations!.length - 1; - return _transformations[iZoom > lastIdx ? lastIdx : iZoom]; + return _transformations![iZoom > lastIdx ? lastIdx : iZoom]; } } abstract class Projection { const Projection(); - Bounds get bounds; + Bounds? get bounds; CustomPoint project(LatLng latlng); LatLng unproject(CustomPoint point); double _inclusive(Comparable start, Comparable end, double value) { - if (value.compareTo(start) < 0) return start; - if (value.compareTo(end) > 0) return end; + if (value.compareTo(start as num) < 0) return start as double; + if (value.compareTo(end as num) > 0) return end as double; return value; } @@ -391,7 +384,8 @@ class _LonLat extends Projection { @override LatLng unproject(CustomPoint point) { - return LatLng(inclusiveLat(point.y), inclusiveLng(point.x)); + return LatLng( + inclusiveLat(point.y as double), inclusiveLng(point.x as double)); } } @@ -436,13 +430,12 @@ class _Proj4Projection extends Projection { final proj4.Projection proj4Projection; @override - final Bounds bounds; + final Bounds? bounds; _Proj4Projection({ - @required this.proj4Projection, - @required this.bounds, - }) : assert(null != proj4Projection), - epsg4326 = proj4.Projection.WGS84; + required this.proj4Projection, + this.bounds, + }) : epsg4326 = proj4.Projection.WGS84; @override CustomPoint project(LatLng latlng) { @@ -455,7 +448,7 @@ class _Proj4Projection extends Projection { @override LatLng unproject(CustomPoint point) { var point2 = proj4Projection.transform( - epsg4326, proj4.Point(x: point.x, y: point.y)); + epsg4326, proj4.Point(x: point.x as double, y: point.y as double)); return LatLng(inclusiveLat(point2.y), inclusiveLng(point2.x)); } @@ -469,14 +462,14 @@ class Transformation { const Transformation(this.a, this.b, this.c, this.d); - CustomPoint transform(CustomPoint point, double scale) { + CustomPoint transform(CustomPoint point, double? scale) { scale ??= 1.0; var x = scale * (a * point.x + b); var y = scale * (c * point.y + d); return CustomPoint(x, y); } - CustomPoint untransform(CustomPoint point, double scale) { + CustomPoint untransform(CustomPoint point, double? scale) { scale ??= 1.0; var x = (point.x / scale - b) / a; var y = (point.y / scale - d) / c; diff --git a/lib/src/geo/latlng_bounds.dart b/lib/src/geo/latlng_bounds.dart index ad3983411..b27e8b324 100644 --- a/lib/src/geo/latlng_bounds.dart +++ b/lib/src/geo/latlng_bounds.dart @@ -1,21 +1,22 @@ import 'dart:math' as math; + import 'package:latlong2/latlong.dart'; class LatLngBounds { - LatLng _sw; - LatLng _ne; + LatLng? _sw; + LatLng? _ne; - LatLngBounds([LatLng corner1, LatLng corner2]) { + LatLngBounds([LatLng? corner1, LatLng? corner2]) { extend(corner1); extend(corner2); } LatLngBounds.fromPoints(List points) { - if (points != null && points.isNotEmpty) { - num minX; - num maxX; - num minY; - num maxY; + if (points.isNotEmpty) { + num? minX; + num? maxX; + num? minY; + num? maxY; for (var point in points) { num x = point.longitudeInRad; @@ -38,12 +39,12 @@ class LatLngBounds { } } - _sw = LatLng(radianToDeg(minY), radianToDeg(minX)); - _ne = LatLng(radianToDeg(maxY), radianToDeg(maxX)); + _sw = LatLng(radianToDeg(minY as double), radianToDeg(minX as double)); + _ne = LatLng(radianToDeg(maxY as double), radianToDeg(maxX as double)); } } - void extend(LatLng latlng) { + void extend(LatLng? latlng) { if (latlng == null) { return; } @@ -54,25 +55,25 @@ class LatLngBounds { _extend(bounds._sw, bounds._ne); } - void _extend(LatLng sw2, LatLng ne2) { + void _extend(LatLng? sw2, LatLng? ne2) { if (_sw == null && _ne == null) { - _sw = LatLng(sw2.latitude, sw2.longitude); - _ne = LatLng(ne2.latitude, ne2.longitude); + _sw = LatLng(sw2!.latitude, sw2.longitude); + _ne = LatLng(ne2!.latitude, ne2.longitude); } else { - _sw.latitude = math.min(sw2.latitude, _sw.latitude); - _sw.longitude = math.min(sw2.longitude, _sw.longitude); - _ne.latitude = math.max(ne2.latitude, _ne.latitude); - _ne.longitude = math.max(ne2.longitude, _ne.longitude); + _sw!.latitude = math.min(sw2!.latitude, _sw!.latitude); + _sw!.longitude = math.min(sw2.longitude, _sw!.longitude); + _ne!.latitude = math.max(ne2!.latitude, _ne!.latitude); + _ne!.longitude = math.max(ne2.longitude, _ne!.longitude); } } - double get west => southWest.longitude; - double get south => southWest.latitude; - double get east => northEast.longitude; - double get north => northEast.latitude; + double get west => southWest!.longitude; + double get south => southWest!.latitude; + double get east => northEast!.longitude; + double get north => northEast!.latitude; - LatLng get southWest => _sw; - LatLng get northEast => _ne; + LatLng? get southWest => _sw; + LatLng? get northEast => _ne; LatLng get northWest => LatLng(north, west); LatLng get southEast => LatLng(south, east); @@ -80,7 +81,7 @@ class LatLngBounds { return _sw != null && _ne != null; } - bool contains(LatLng point) { + bool contains(LatLng? point) { if (!isValid) { return false; } @@ -90,34 +91,34 @@ class LatLngBounds { } bool containsBounds(LatLngBounds bounds) { - var sw2 = bounds._sw; + var sw2 = bounds._sw!; var ne2 = bounds._ne; - return (sw2.latitude >= _sw.latitude) && - (ne2.latitude <= _ne.latitude) && - (sw2.longitude >= _sw.longitude) && - (ne2.longitude <= _ne.longitude); + return (sw2.latitude >= _sw!.latitude) && + (ne2!.latitude <= _ne!.latitude) && + (sw2.longitude >= _sw!.longitude) && + (ne2.longitude <= _ne!.longitude); } - bool isOverlapping(LatLngBounds bounds) { + bool isOverlapping(LatLngBounds? bounds) { if (!isValid) { return false; } // check if bounding box rectangle is outside the other, if it is then it's // considered not overlapping - if (_sw.latitude > bounds._ne.latitude || - _ne.latitude < bounds._sw.latitude || - _ne.longitude < bounds._sw.longitude || - _sw.longitude > bounds._ne.longitude) { + if (_sw!.latitude > bounds!._ne!.latitude || + _ne!.latitude < bounds._sw!.latitude || + _ne!.longitude < bounds._sw!.longitude || + _sw!.longitude > bounds._ne!.longitude) { return false; } return true; } void pad(double bufferRatio) { - var heightBuffer = (_sw.latitude - _ne.latitude).abs() * bufferRatio; - var widthBuffer = (_sw.longitude - _ne.longitude).abs() * bufferRatio; + var heightBuffer = (_sw!.latitude - _ne!.latitude).abs() * bufferRatio; + var widthBuffer = (_sw!.longitude - _ne!.longitude).abs() * bufferRatio; - _sw = LatLng(_sw.latitude - heightBuffer, _sw.longitude - widthBuffer); - _ne = LatLng(_ne.latitude + heightBuffer, _ne.longitude + widthBuffer); + _sw = LatLng(_sw!.latitude - heightBuffer, _sw!.longitude - widthBuffer); + _ne = LatLng(_ne!.latitude + heightBuffer, _ne!.longitude + widthBuffer); } } diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 2b116b2d6..47b7822ad 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -2,13 +2,13 @@ import 'dart:async'; import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/interactive_flag.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart'; import 'package:positioned_tap_detector_2/positioned_tap_detector_2.dart'; -import 'package:flutter/physics.dart'; abstract class MapGestureMixin extends State with TickerProviderStateMixin { @@ -31,25 +31,25 @@ abstract class MapGestureMixin extends State // Helps to reset ScaleUpdateDetails.scale back to 1.0 when a multi finger // gesture wins - double _scaleCorrector; + late final double _scaleCorrector; - double _lastRotation; - double _lastScale; - Offset _lastFocalLocal; + late final double _lastRotation; + late final double _lastScale; + late final Offset _lastFocalLocal; - LatLng _mapCenterStart; - double _mapZoomStart; - Offset _focalStartLocal; + late final LatLng _mapCenterStart; + late final double _mapZoomStart; + late final Offset _focalStartLocal; - AnimationController _flingController; - Animation _flingAnimation; + late final AnimationController _flingController; + late final Animation _flingAnimation; - AnimationController _doubleTapController; - Animation _doubleTapZoomAnimation; - Animation _doubleTapCenterAnimation; + late final AnimationController _doubleTapController; + late final Animation _doubleTapZoomAnimation; + late final Animation _doubleTapCenterAnimation; int _tapUpCounter = 0; - Timer _doubleTapHoldMaxDelay; + Timer? _doubleTapHoldMaxDelay; @override FlutterMap get widget; @@ -162,7 +162,8 @@ abstract class MapGestureMixin extends State } } - int _getMultiFingerGestureFlags({int gestureWinner, MapOptions mapOptions}) { + int _getMultiFingerGestureFlags( + {int? gestureWinner, MapOptions? mapOptions}) { gestureWinner ??= _gestureWinner; mapOptions ??= options; @@ -480,9 +481,10 @@ abstract class MapGestureMixin extends State } var direction = details.velocity.pixelsPerSecond / magnitude; - var distance = - (Offset.zero & Size(mapState.originalSize.x, mapState.originalSize.y)) - .shortestSide; + var distance = (Offset.zero & + Size(mapState.originalSize!.x as double, + mapState.originalSize!.y as double)) + .shortestSide; var _flingOffset = _focalStartLocal - _lastFocalLocal; _flingAnimation = Tween( @@ -505,10 +507,10 @@ abstract class MapGestureMixin extends State closeFlingAnimationController(MapEventSource.tap); closeDoubleTapController(MapEventSource.tap); - final latlng = _offsetToCrs(position.relative); + final latlng = _offsetToCrs(position.relative!); if (options.onTap != null) { // emit the event - options.onTap(latlng); + options.onTap!(latlng); } mapState.emitMapEvent( @@ -527,10 +529,10 @@ abstract class MapGestureMixin extends State closeFlingAnimationController(MapEventSource.longPress); closeDoubleTapController(MapEventSource.longPress); - final latlng = _offsetToCrs(position.relative); + final latlng = _offsetToCrs(position.relative!); if (options.onLongPress != null) { // emit the event - options.onLongPress(latlng); + options.onLongPress!(latlng); } mapState.emitMapEvent( @@ -545,7 +547,7 @@ abstract class MapGestureMixin extends State LatLng _offsetToCrs(Offset offset) { final focalStartPt = mapState.project(mapState.center, mapState.zoom); - final point = (_offsetToPoint(offset) - (mapState.originalSize / 2.0)) + final point = (_offsetToPoint(offset) - (mapState.originalSize! / 2.0)) .rotate(mapState.rotationRad); var newCenterPt = focalStartPt + point; @@ -564,10 +566,10 @@ abstract class MapGestureMixin extends State if (InteractiveFlag.hasFlag( options.interactiveFlags, InteractiveFlag.doubleTapZoom)) { - final centerPos = _pointToOffset(mapState.originalSize) / 2.0; + final centerPos = _pointToOffset(mapState.originalSize!) / 2.0; final newZoom = _getZoomForScale(mapState.zoom, 2.0); final focalDelta = _getDoubleTapFocalDelta( - centerPos, tapPosition.relative, newZoom - mapState.zoom); + centerPos, tapPosition.relative!, newZoom - mapState.zoom); final newCenter = _offsetToCrs(centerPos + focalDelta); _startDoubleTapAnimation(newZoom, newCenter); } diff --git a/lib/src/gestures/latlng_tween.dart b/lib/src/gestures/latlng_tween.dart index e815b1b1c..145778da8 100644 --- a/lib/src/gestures/latlng_tween.dart +++ b/lib/src/gestures/latlng_tween.dart @@ -1,14 +1,13 @@ import 'package:flutter/animation.dart'; -import 'package:flutter/foundation.dart'; import 'package:latlong2/latlong.dart'; class LatLngTween extends Tween { - LatLngTween({@required LatLng begin, @required LatLng end}) + LatLngTween({required LatLng begin, required LatLng end}) : super(begin: begin, end: end); @override LatLng lerp(double t) => LatLng( - begin.latitude + (end.latitude - begin.latitude) * t, - begin.longitude + (end.longitude - begin.longitude) * t, + begin!.latitude + (end!.latitude - begin!.latitude) * t, + begin!.longitude + (end!.longitude - begin!.longitude) * t, ); } diff --git a/lib/src/gestures/map_events.dart b/lib/src/gestures/map_events.dart index 656ce8ee2..084b8c178 100644 --- a/lib/src/gestures/map_events.dart +++ b/lib/src/gestures/map_events.dart @@ -15,6 +15,9 @@ enum MapEventSource { flingAnimationController, doubleTapZoomAnimationController, interactiveFlagsChanged, + fitBounds, + initialization, + custom } abstract class MapEvent { @@ -25,7 +28,7 @@ abstract class MapEvent { // current zoom when event is emitted final double zoom; - MapEvent({this.source, this.center, this.zoom}); + MapEvent({required this.source, required this.center, required this.zoom}); } abstract class MapEventWithMove extends MapEvent { @@ -33,11 +36,11 @@ abstract class MapEventWithMove extends MapEvent { final double targetZoom; MapEventWithMove({ - this.targetCenter, - this.targetZoom, - MapEventSource source, - LatLng center, - double zoom, + required this.targetCenter, + required this.targetZoom, + required MapEventSource source, + required LatLng center, + required double zoom, }) : super(source: source, center: center, zoom: zoom); } @@ -45,10 +48,10 @@ class MapEventTap extends MapEvent { final LatLng tapPosition; MapEventTap({ - this.tapPosition, - MapEventSource source, - LatLng center, - double zoom, + required this.tapPosition, + required MapEventSource source, + required LatLng center, + required double zoom, }) : super(source: source, center: center, zoom: zoom); } @@ -56,23 +59,23 @@ class MapEventLongPress extends MapEvent { final LatLng tapPosition; MapEventLongPress({ - this.tapPosition, - MapEventSource source, - LatLng center, - double zoom, + required this.tapPosition, + required MapEventSource source, + required LatLng center, + required double zoom, }) : super(source: source, center: center, zoom: zoom); } class MapEventMove extends MapEventWithMove { - final String id; + final String? id; MapEventMove({ this.id, - LatLng targetCenter, - double targetZoom, - MapEventSource source, - LatLng center, - double zoom, + required LatLng targetCenter, + required double targetZoom, + required MapEventSource source, + required LatLng center, + required double zoom, }) : super( targetCenter: targetCenter, targetZoom: targetZoom, @@ -83,22 +86,28 @@ class MapEventMove extends MapEventWithMove { } class MapEventMoveStart extends MapEvent { - MapEventMoveStart({MapEventSource source, LatLng center, double zoom}) + MapEventMoveStart( + {required MapEventSource source, + required LatLng center, + required double zoom}) : super(source: source, center: center, zoom: zoom); } class MapEventMoveEnd extends MapEvent { - MapEventMoveEnd({MapEventSource source, LatLng center, double zoom}) + MapEventMoveEnd( + {required MapEventSource source, + required LatLng center, + required double zoom}) : super(source: source, center: center, zoom: zoom); } class MapEventFlingAnimation extends MapEventWithMove { MapEventFlingAnimation({ - LatLng targetCenter, - double targetZoom, - MapEventSource source, - LatLng center, - double zoom, + required LatLng targetCenter, + required double targetZoom, + required MapEventSource source, + required LatLng center, + required double zoom, }) : super( targetCenter: targetCenter, targetZoom: targetZoom, @@ -112,28 +121,35 @@ class MapEventFlingAnimation extends MapEventWithMove { /// to start fling animation class MapEventFlingAnimationNotStarted extends MapEvent { MapEventFlingAnimationNotStarted( - {MapEventSource source, LatLng center, double zoom}) + {required MapEventSource source, + required LatLng center, + required double zoom}) : super(source: source, center: center, zoom: zoom); } class MapEventFlingAnimationStart extends MapEvent { MapEventFlingAnimationStart( - {MapEventSource source, LatLng center, double zoom}) + {required MapEventSource source, + required LatLng center, + required double zoom}) : super(source: source, center: center, zoom: zoom); } class MapEventFlingAnimationEnd extends MapEvent { - MapEventFlingAnimationEnd({MapEventSource source, LatLng center, double zoom}) + MapEventFlingAnimationEnd( + {required MapEventSource source, + required LatLng center, + required double zoom}) : super(source: source, center: center, zoom: zoom); } class MapEventDoubleTapZoom extends MapEventWithMove { MapEventDoubleTapZoom({ - LatLng targetCenter, - double targetZoom, - MapEventSource source, - LatLng center, - double zoom, + required LatLng targetCenter, + required double targetZoom, + required MapEventSource source, + required LatLng center, + required double zoom, }) : super( targetCenter: targetCenter, targetZoom: targetZoom, @@ -145,36 +161,47 @@ class MapEventDoubleTapZoom extends MapEventWithMove { class MapEventDoubleTapZoomStart extends MapEvent { MapEventDoubleTapZoomStart( - {MapEventSource source, LatLng center, double zoom}) + {required MapEventSource source, + required LatLng center, + required double zoom}) : super(source: source, center: center, zoom: zoom); } class MapEventDoubleTapZoomEnd extends MapEvent { - MapEventDoubleTapZoomEnd({MapEventSource source, LatLng center, double zoom}) + MapEventDoubleTapZoomEnd( + {required MapEventSource source, + required LatLng center, + required double zoom}) : super(source: source, center: center, zoom: zoom); } class MapEventRotate extends MapEvent { - final String id; + final String? id; final double currentRotation; final double targetRotation; MapEventRotate({ - this.id, - this.currentRotation, - this.targetRotation, - MapEventSource source, - LatLng center, - double zoom, + required this.id, + required this.currentRotation, + required this.targetRotation, + required MapEventSource source, + required LatLng center, + required double zoom, }) : super(source: source, center: center, zoom: zoom); } class MapEventRotateStart extends MapEvent { - MapEventRotateStart({MapEventSource source, LatLng center, double zoom}) + MapEventRotateStart( + {required MapEventSource source, + required LatLng center, + required double zoom}) : super(source: source, center: center, zoom: zoom); } class MapEventRotateEnd extends MapEvent { - MapEventRotateEnd({MapEventSource source, LatLng center, double zoom}) + MapEventRotateEnd( + {required MapEventSource source, + required LatLng center, + required double zoom}) : super(source: source, center: center, zoom: zoom); } diff --git a/lib/src/layer/circle_layer.dart b/lib/src/layer/circle_layer.dart index 7bebde3dc..8f7c14804 100644 --- a/lib/src/layer/circle_layer.dart +++ b/lib/src/layer/circle_layer.dart @@ -8,9 +8,9 @@ import 'package:latlong2/latlong.dart' hide Path; class CircleLayerOptions extends LayerOptions { final List circles; CircleLayerOptions({ - Key key, + Key? key, this.circles = const [], - Stream rebuild, + Stream? rebuild, }) : super(key: key, rebuild: rebuild); } @@ -24,8 +24,8 @@ class CircleMarker { Offset offset = Offset.zero; num realRadius = 0; CircleMarker({ - this.point, - this.radius, + required this.point, + required this.radius, this.useRadiusInMeter = false, this.color = const Color(0xFF00FF00), this.borderStrokeWidth = 0.0, @@ -36,11 +36,11 @@ class CircleMarker { class CircleLayerWidget extends StatelessWidget { final CircleLayerOptions options; - CircleLayerWidget({Key key, @required this.options}) : super(key: key); + CircleLayerWidget({Key? key, required this.options}) : super(key: key); @override Widget build(BuildContext context) { - final mapState = MapState.of(context); + final mapState = MapState.maybeOf(context)!; return CircleLayer(options, mapState, mapState.onMoved); } } @@ -48,7 +48,7 @@ class CircleLayerWidget extends StatelessWidget { class CircleLayer extends StatelessWidget { final CircleLayerOptions circleOpts; final MapState map; - final Stream stream; + final Stream? stream; CircleLayer(this.circleOpts, this.map, this.stream) : super(key: circleOpts.key); @@ -112,8 +112,11 @@ class CirclePainter extends CustomPainter { ..style = PaintingStyle.fill ..color = circle.color; - _paintCircle(canvas, circle.offset, - circle.useRadiusInMeter ? circle.realRadius : circle.radius, paint); + _paintCircle( + canvas, + circle.offset, + circle.useRadiusInMeter ? circle.realRadius as double : circle.radius, + paint); if (circle.borderStrokeWidth > 0) { final paint = Paint() @@ -121,8 +124,11 @@ class CirclePainter extends CustomPainter { ..color = circle.borderColor ..strokeWidth = circle.borderStrokeWidth; - _paintCircle(canvas, circle.offset, - circle.useRadiusInMeter ? circle.realRadius : circle.radius, paint); + _paintCircle( + canvas, + circle.offset, + circle.useRadiusInMeter ? circle.realRadius as double : circle.radius, + paint); } } diff --git a/lib/src/layer/group_layer.dart b/lib/src/layer/group_layer.dart index 91cbe36bc..30a3ea3bd 100644 --- a/lib/src/layer/group_layer.dart +++ b/lib/src/layer/group_layer.dart @@ -7,20 +7,20 @@ class GroupLayerOptions extends LayerOptions { List group = []; GroupLayerOptions({ - Key key, - this.group, - Stream rebuild, + Key? key, + this.group = const [], + Stream? rebuild, }) : super(key: key, rebuild: rebuild); } class GroupLayerWidget extends StatelessWidget { final GroupLayerOptions options; - GroupLayerWidget({Key key, @required this.options}) : super(key: key); + GroupLayerWidget({Key? key, required this.options}) : super(key: key); @override Widget build(BuildContext context) { - final mapState = MapState.of(context); + final mapState = MapState.maybeOf(context)!; return GroupLayer(options, mapState, mapState.onMoved); } } diff --git a/lib/src/layer/layer.dart b/lib/src/layer/layer.dart index 7105fd0eb..cf4cb0801 100644 --- a/lib/src/layer/layer.dart +++ b/lib/src/layer/layer.dart @@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart'; /// All LayerOptions have access to a stream that notifies when the map needs /// rebuilding. class LayerOptions { - final Key key; - final Stream rebuild; + final Key? key; + final Stream? rebuild; LayerOptions({this.key, this.rebuild}); } diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index 3b97b75df..a6b391b3f 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -8,14 +8,14 @@ class MarkerLayerOptions extends LayerOptions { final List markers; /// If true markers will be counter rotated to the map rotation - final bool rotate; + final bool? rotate; /// The origin of the coordinate system (relative to the upper left corner of /// this render object) in which to apply the matrix. /// /// Setting an origin is equivalent to conjugating the transform matrix by a /// translation. This property is provided just for convenience. - final Offset rotateOrigin; + final Offset? rotateOrigin; /// The alignment of the origin, relative to the size of the box. /// @@ -29,15 +29,15 @@ class MarkerLayerOptions extends LayerOptions { /// same as an [Alignment] whose [Alignment.x] value is `1.0` if /// [Directionality.of] returns [TextDirection.ltr], and `-1.0` if /// [Directionality.of] returns [TextDirection.rtl]. - final AlignmentGeometry rotateAlignment; + final AlignmentGeometry? rotateAlignment; MarkerLayerOptions({ - Key key, + Key? key, this.markers = const [], this.rotate = false, this.rotateOrigin, this.rotateAlignment = Alignment.center, - Stream rebuild, + Stream? rebuild, }) : super(key: key, rebuild: rebuild); } @@ -79,8 +79,8 @@ class Anchor { } } - factory Anchor.forPos(AnchorPos pos, double width, double height) { - if (pos == null) return Anchor._(width, height, null); + factory Anchor.forPos(AnchorPos? pos, double width, double height) { + if (pos == null) return Anchor._(width, height, AnchorAlign.none); if (pos.value is AnchorAlign) return Anchor._(width, height, pos.value); if (pos.value is Anchor) return pos.value; throw Exception('Unsupported AnchorPos value type: ${pos.runtimeType}.'); @@ -95,6 +95,7 @@ class AnchorPos { } enum AnchorAlign { + none, left, right, top, @@ -105,20 +106,20 @@ enum AnchorAlign { class Marker { final LatLng point; final WidgetBuilder builder; - final Key key; + final Key? key; final double width; final double height; final Anchor anchor; /// If true marker will be counter rotated to the map rotation - final bool rotate; + final bool? rotate; /// The origin of the coordinate system (relative to the upper left corner of /// this render object) in which to apply the matrix. /// /// Setting an origin is equivalent to conjugating the transform matrix by a /// translation. This property is provided just for convenience. - final Offset rotateOrigin; + final Offset? rotateOrigin; /// The alignment of the origin, relative to the size of the box. /// @@ -132,29 +133,29 @@ class Marker { /// same as an [Alignment] whose [Alignment.x] value is `1.0` if /// [Directionality.of] returns [TextDirection.ltr], and `-1.0` if /// [Directionality.of] returns [TextDirection.rtl]. - final AlignmentGeometry rotateAlignment; + final AlignmentGeometry? rotateAlignment; Marker({ - this.point, - this.builder, + required this.point, + required this.builder, this.key, this.width = 30.0, this.height = 30.0, this.rotate, this.rotateOrigin, this.rotateAlignment, - AnchorPos anchorPos, + AnchorPos? anchorPos, }) : anchor = Anchor.forPos(anchorPos, width, height); } class MarkerLayerWidget extends StatelessWidget { final MarkerLayerOptions options; - MarkerLayerWidget({Key key, @required this.options}) : super(key: key); + MarkerLayerWidget({Key? key, required this.options}) : super(key: key); @override Widget build(BuildContext context) { - final mapState = MapState.of(context); + final mapState = MapState.maybeOf(context)!; return MarkerLayer(options, mapState, mapState.onMoved); } } @@ -162,7 +163,7 @@ class MarkerLayerWidget extends StatelessWidget { class MarkerLayer extends StatefulWidget { final MarkerLayerOptions markerLayerOptions; final MapState map; - final Stream stream; + final Stream? stream; MarkerLayer(this.markerLayerOptions, this.map, this.stream) : super(key: markerLayerOptions.key); @@ -201,9 +202,9 @@ class _MarkerLayerState extends State { @override Widget build(BuildContext context) { - return StreamBuilder( + return StreamBuilder( stream: widget.stream, // a Stream or null - builder: (BuildContext context, AsyncSnapshot snapshot) { + builder: (BuildContext context, AsyncSnapshot snapshot) { var markers = []; final sameZoom = widget.map.zoom == lastZoom; for (var i = 0; i < widget.markerLayerOptions.markers.length; i++) { @@ -227,7 +228,7 @@ class _MarkerLayerState extends State { final pos = pxPoint - widget.map.getPixelOrigin(); final markerWidget = - (marker.rotate ?? widget.markerLayerOptions.rotate) + (marker.rotate ?? widget.markerLayerOptions.rotate ?? false) // Counter rotated marker to the map rotation ? Transform.rotate( angle: -widget.map.rotationRad, diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index a753c6aab..137c63155 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -9,9 +9,9 @@ class OverlayImageLayerOptions extends LayerOptions { final List overlayImages; OverlayImageLayerOptions({ - Key key, + Key? key, this.overlayImages = const [], - Stream rebuild, + Stream? rebuild, }) : super(key: key, rebuild: rebuild); } @@ -22,8 +22,8 @@ class OverlayImage { final bool gaplessPlayback; OverlayImage({ - this.bounds, - this.imageProvider, + required this.bounds, + required this.imageProvider, this.opacity = 1.0, this.gaplessPlayback = false, }); @@ -32,11 +32,11 @@ class OverlayImage { class OverlayImageLayerWidget extends StatelessWidget { final OverlayImageLayerOptions options; - OverlayImageLayerWidget({Key key, @required this.options}) : super(key: key); + OverlayImageLayerWidget({Key? key, required this.options}) : super(key: key); @override Widget build(BuildContext context) { - final mapState = MapState.of(context); + final mapState = MapState.maybeOf(context)!; return OverlayImageLayer(options, mapState, mapState.onMoved); } } @@ -44,7 +44,7 @@ class OverlayImageLayerWidget extends StatelessWidget { class OverlayImageLayer extends StatelessWidget { final OverlayImageLayerOptions overlayImageOpts; final MapState map; - final Stream stream; + final Stream? stream; OverlayImageLayer(this.overlayImageOpts, this.map, this.stream) : super(key: overlayImageOpts.key); diff --git a/lib/src/layer/polygon_layer.dart b/lib/src/layer/polygon_layer.dart index f46956b81..05ab0be6f 100644 --- a/lib/src/layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer.dart @@ -12,10 +12,10 @@ class PolygonLayerOptions extends LayerOptions { /// screen space culling of polygons based on bounding box PolygonLayerOptions({ - Key key, + Key? key, this.polygons = const [], this.polygonCulling = false, - Stream rebuild, + Stream? rebuild, }) : super(key: key, rebuild: rebuild) { if (polygonCulling) { for (var polygon in polygons) { @@ -28,17 +28,17 @@ class PolygonLayerOptions extends LayerOptions { class Polygon { final List points; final List offsets = []; - final List> holePointsList; - final List> holeOffsetsList; + final List>? holePointsList; + final List>? holeOffsetsList; final Color color; final double borderStrokeWidth; final Color borderColor; final bool disableHolesBorder; final bool isDotted; - LatLngBounds boundingBox; + late final LatLngBounds boundingBox; Polygon({ - this.points, + required this.points, this.holePointsList, this.color = const Color(0xFF00FF00), this.borderStrokeWidth = 0.0, @@ -52,11 +52,11 @@ class Polygon { class PolygonLayerWidget extends StatelessWidget { final PolygonLayerOptions options; - PolygonLayerWidget({Key key, @required this.options}) : super(key: key); + PolygonLayerWidget({Key? key, required this.options}) : super(key: key); @override Widget build(BuildContext context) { - final mapState = MapState.of(context); + final mapState = MapState.maybeOf(context)!; return PolygonLayer(options, mapState, mapState.onMoved); } } @@ -64,7 +64,7 @@ class PolygonLayerWidget extends StatelessWidget { class PolygonLayer extends StatelessWidget { final PolygonLayerOptions polygonOpts; final MapState map; - final Stream stream; + final Stream? stream; PolygonLayer(this.polygonOpts, this.map, this.stream) : super(key: polygonOpts.key); @@ -89,7 +89,7 @@ class PolygonLayer extends StatelessWidget { polygon.offsets.clear(); if (null != polygon.holeOffsetsList) { - for (var offsets in polygon.holeOffsetsList) { + for (var offsets in polygon.holeOffsetsList!) { offsets.clear(); } } @@ -103,9 +103,11 @@ class PolygonLayer extends StatelessWidget { _fillOffsets(polygon.offsets, polygon.points); if (null != polygon.holePointsList) { - for (var i = 0, len = polygon.holePointsList.length; i < len; ++i) { + for (var i = 0, len = polygon.holePointsList!.length; + i < len; + ++i) { _fillOffsets( - polygon.holeOffsetsList[i], polygon.holePointsList[i]); + polygon.holeOffsetsList![i], polygon.holePointsList![i]); } } @@ -170,7 +172,7 @@ class PolygonPainter extends CustomPainter { if (!polygonOpt.disableHolesBorder && null != polygonOpt.holeOffsetsList) { - for (var offsets in polygonOpt.holeOffsetsList) { + for (var offsets in polygonOpt.holeOffsetsList!) { _paintDottedLine( canvas, offsets, borderRadius, spacing, borderPaint); } @@ -180,7 +182,7 @@ class PolygonPainter extends CustomPainter { if (!polygonOpt.disableHolesBorder && null != polygonOpt.holeOffsetsList) { - for (var offsets in polygonOpt.holeOffsetsList) { + for (var offsets in polygonOpt.holeOffsetsList!) { _paintLine(canvas, offsets, borderRadius, borderPaint); } } @@ -225,7 +227,7 @@ class PolygonPainter extends CustomPainter { canvas.saveLayer(rect, paint); paint.style = PaintingStyle.fill; - for (var offsets in polygonOpt.holeOffsetsList) { + for (var offsets in polygonOpt.holeOffsetsList!) { var path = Path(); path.addPolygon(offsets, true); canvas.drawPath(path, paint); diff --git a/lib/src/layer/polyline_layer.dart b/lib/src/layer/polyline_layer.dart index 30c68bc03..5bba99771 100644 --- a/lib/src/layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer.dart @@ -11,10 +11,10 @@ class PolylineLayerOptions extends LayerOptions { final bool polylineCulling; PolylineLayerOptions({ - Key key, + Key? key, this.polylines = const [], this.polylineCulling = false, - Stream rebuild, + Stream? rebuild, }) : super(key: key, rebuild: rebuild) { if (polylineCulling) { for (var polyline in polylines) { @@ -30,14 +30,14 @@ class Polyline { final double strokeWidth; final Color color; final double borderStrokeWidth; - final Color borderColor; - final List gradientColors; - final List colorsStop; + final Color? borderColor; + final List? gradientColors; + final List? colorsStop; final bool isDotted; - LatLngBounds boundingBox; + late final LatLngBounds boundingBox; Polyline({ - this.points, + required this.points, this.strokeWidth = 1.0, this.color = const Color(0xFF00FF00), this.borderStrokeWidth = 0.0, @@ -50,11 +50,12 @@ class Polyline { class PolylineLayerWidget extends StatelessWidget { final PolylineLayerOptions options; - PolylineLayerWidget({Key key, @required this.options}) : super(key: key); + + PolylineLayerWidget({Key? key, required this.options}) : super(key: key); @override Widget build(BuildContext context) { - final mapState = MapState.of(context); + final mapState = MapState.maybeOf(context)!; return PolylineLayer(options, mapState, mapState.onMoved); } } @@ -62,7 +63,7 @@ class PolylineLayerWidget extends StatelessWidget { class PolylineLayer extends StatelessWidget { final PolylineLayerOptions polylineOpts; final MapState map; - final Stream stream; + final Stream? stream; PolylineLayer(this.polylineOpts, this.map, this.stream) : super(key: polylineOpts.key); @@ -145,15 +146,15 @@ class PolylinePainter extends CustomPainter { if (polylineOpt.gradientColors == null) { paint.color = polylineOpt.color; } else { - polylineOpt.gradientColors.isNotEmpty + polylineOpt.gradientColors!.isNotEmpty ? paint.shader = _paintGradient() : paint.color = polylineOpt.color; } - Paint filterPaint; + Paint? filterPaint; if (polylineOpt.borderColor != null) { filterPaint = Paint() - ..color = polylineOpt.borderColor.withAlpha(255) + ..color = polylineOpt.borderColor!.withAlpha(255) ..strokeWidth = polylineOpt.strokeWidth ..strokeCap = StrokeCap.round ..strokeJoin = StrokeJoin.round @@ -162,7 +163,7 @@ class PolylinePainter extends CustomPainter { final borderPaint = polylineOpt.borderStrokeWidth > 0.0 ? (Paint() - ..color = polylineOpt.borderColor + ..color = polylineOpt.borderColor ?? Color(0x00000000) ..strokeWidth = polylineOpt.strokeWidth + polylineOpt.borderStrokeWidth ..strokeCap = StrokeCap.round @@ -232,18 +233,18 @@ class PolylinePainter extends CustomPainter { } ui.Gradient _paintGradient() => ui.Gradient.linear(polylineOpt.offsets.first, - polylineOpt.offsets.last, polylineOpt.gradientColors, _getColorsStop()); + polylineOpt.offsets.last, polylineOpt.gradientColors!, _getColorsStop()); - List _getColorsStop() => (polylineOpt.colorsStop != null && - polylineOpt.colorsStop.length == polylineOpt.gradientColors.length) + List? _getColorsStop() => (polylineOpt.colorsStop != null && + polylineOpt.colorsStop!.length == polylineOpt.gradientColors!.length) ? polylineOpt.colorsStop : _calculateColorsStop(); List _calculateColorsStop() { - final colorsStopInterval = 1.0 / polylineOpt.gradientColors.length; - return polylineOpt.gradientColors + final colorsStopInterval = 1.0 / polylineOpt.gradientColors!.length; + return polylineOpt.gradientColors! .map((gradientColor) => - polylineOpt.gradientColors.indexOf(gradientColor) * + polylineOpt.gradientColors!.indexOf(gradientColor) * colorsStopInterval) .toList(); } diff --git a/lib/src/layer/tile_layer.dart b/lib/src/layer/tile_layer.dart index 7b52f289a..1f040217c 100644 --- a/lib/src/layer/tile_layer.dart +++ b/lib/src/layer/tile_layer.dart @@ -49,14 +49,14 @@ class TileLayerOptions extends LayerOptions { /// Is translated to this: /// /// https://a.tile.openstreetmap.org/12/2177/1259.png - final String urlTemplate; + final String? urlTemplate; /// If `true`, inverses Y axis numbering for tiles (turn this on for /// [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). final bool tms; /// If not `null`, then tiles will pull's WMS protocol requests - final WMSTileLayerOptions wmsOptions; + final WMSTileLayerOptions? wmsOptions; /// Size for the tile. /// Default is 256 @@ -73,12 +73,12 @@ class TileLayerOptions extends LayerOptions { /// Minimum zoom number the tile source has available. If it is specified, the /// tiles on all zoom levels lower than minNativeZoom will be loaded from /// minNativeZoom level and auto-scaled. - final double minNativeZoom; + final double? minNativeZoom; /// Maximum zoom number the tile source has available. If it is specified, the /// tiles on all zoom levels higher than maxNativeZoom will be loaded from /// maxNativeZoom level and auto-scaled. - final double maxNativeZoom; + final double? maxNativeZoom; /// If set to true, the zoom number used in tile URLs will be reversed /// (`maxZoom - zoom` instead of `zoom`) @@ -110,10 +110,10 @@ class TileLayerOptions extends LayerOptions { /// Opacity of the rendered tile final double opacity; - /// Provider to load the tiles. The default is CachedNetworkTileProvider, - /// which loads tile images from network and caches them offline. - /// - /// If you don't want to cache the tiles, use NetworkTileProvider instead. + /// Provider to load the tiles. The default is `NonCachingNetworkTileProvider()` which + /// doesn't cache tiles and won't retry the HTTP request. Use `NetworkTileProvider()` for + /// a provider which will retry requests. For the best caching implementations, see the + /// flutter_map readme. /// /// In order to use images from the asset folder set this option to /// AssetTileProvider() Note that it requires the urlTemplate to target @@ -141,12 +141,12 @@ class TileLayerOptions extends LayerOptions { final int keepBuffer; /// Placeholder to show until tile images are fetched by the provider. - final ImageProvider placeholderImage; + final ImageProvider? placeholderImage; /// Tile image to show in place of the tile that failed to load. - final ImageProvider errorImage; + final ImageProvider? errorImage; - /// Static informations that should replace placeholders in the [urlTemplate]. + /// Static information that should replace placeholders in the [urlTemplate]. /// Applying API keys is a good example on how to use this parameter. /// /// Example: @@ -170,11 +170,11 @@ class TileLayerOptions extends LayerOptions { /// loading tiles every frame when panning / zooming, flutter is fast) This /// can save some fps and even bandwidth (ie. when fast panning / animating /// between long distances in short time) - final Duration updateInterval; + final Duration? updateInterval; /// Tiles fade in duration in milliseconds (default 100). This can be null to /// avoid fade in. - final Duration tileFadeInDuration; + final Duration? tileFadeInDuration; /// Opacity start value when Tile starts fade in (0.0 - 1.0) Takes effect if /// `tileFadeInDuration` is not null @@ -210,17 +210,17 @@ class TileLayerOptions extends LayerOptions { final bool retinaMode; /// This callback will be execute if some errors occur when fetching tiles. - final ErrorTileCallBack errorTileCallback; + final ErrorTileCallBack? errorTileCallback; final TemplateFunction templateFunction; /// Function which may Wrap Tile with custom Widget /// There are predefined examples in 'tile_builder.dart' - final TileBuilder tileBuilder; + final TileBuilder? tileBuilder; /// Function which may wrap Tiles Container with custom Widget /// There are predefined examples in 'tile_builder.dart' - final TilesContainerBuilder tilesContainerBuilder; + final TilesContainerBuilder? tilesContainerBuilder; // If a Tile was loaded with error and if strategy isn't `none` then TileProvider // will be asked to evict Image based on current strategy @@ -241,7 +241,8 @@ class TileLayerOptions extends LayerOptions { final bool fastReplace; TileLayerOptions({ - Key key, + Key? key, + // TODO: make required this.urlTemplate, double tileSize = 256.0, double minZoom = 0.0, @@ -250,7 +251,7 @@ class TileLayerOptions extends LayerOptions { this.maxNativeZoom, this.zoomReverse = false, double zoomOffset = 0.0, - Map additionalOptions, + Map? additionalOptions, this.subdomains = const [], this.keepBuffer = 2, this.backgroundColor = const Color(0xFFE0E0E0), @@ -277,7 +278,7 @@ class TileLayerOptions extends LayerOptions { this.overrideTilesWhenUrlChanges = false, this.retinaMode = false, this.errorTileCallback, - Stream rebuild, + Stream? rebuild, this.templateFunction = util.template, this.tileBuilder, this.tilesContainerBuilder, @@ -333,7 +334,7 @@ class WMSTileLayerOptions { /// Version of the WMS service to use final String version; - /// tile transperency flag + /// tile transparency flag final bool transparent; // TODO find a way to implicit pass of current map [Crs] @@ -342,12 +343,12 @@ class WMSTileLayerOptions { /// other request parameters final Map otherParameters; - String _encodedBaseUrl; + late final String _encodedBaseUrl; - double _versionNumber; + late final double _versionNumber; WMSTileLayerOptions({ - @required this.baseUrl, + required this.baseUrl, this.layers = const [], this.styles = const [], this.format = 'image/png', @@ -380,8 +381,8 @@ class WMSTileLayerOptions { final tileSizePoint = CustomPoint(tileSize, tileSize); final nvPoint = coords.scaleBy(tileSizePoint); final sePoint = nvPoint + tileSizePoint; - final nvCoords = crs.pointToLatLng(nvPoint, coords.z); - final seCoords = crs.pointToLatLng(sePoint, coords.z); + final nvCoords = crs.pointToLatLng(nvPoint, coords.z as double)!; + final seCoords = crs.pointToLatLng(sePoint, coords.z as double)!; final nv = crs.projection.project(nvCoords); final se = crs.projection.project(seCoords); final bounds = Bounds(nv, se); @@ -400,11 +401,11 @@ class WMSTileLayerOptions { class TileLayerWidget extends StatelessWidget { final TileLayerOptions options; - TileLayerWidget({Key key, @required this.options}) : super(key: key); + TileLayerWidget({Key? key, required this.options}) : super(key: key); @override Widget build(BuildContext context) { - final mapState = MapState.of(context); + final mapState = MapState.maybeOf(context)!; return TileLayer( mapState: mapState, @@ -420,9 +421,9 @@ class TileLayer extends StatefulWidget { final Stream stream; TileLayer({ - this.options, - this.mapState, - this.stream, + required this.options, + required this.mapState, + required this.stream, }) : super(key: options.key); @override @@ -435,20 +436,22 @@ class _TileLayerState extends State with TickerProviderStateMixin { MapState get map => widget.mapState; TileLayerOptions get options => widget.options; - Bounds _globalTileRange; - Tuple2 _wrapX; - Tuple2 _wrapY; - double _tileZoom; + late final Bounds _globalTileRange; + Tuple2? _wrapX; + Tuple2? _wrapY; + double? _tileZoom; //ignore: unused_field - Level _level; - StreamSubscription _moveSub; - StreamController _throttleUpdate; - CustomPoint _tileSize; + Level? _level; + StreamSubscription? _moveSub; + StreamController? _throttleUpdate; + late CustomPoint _tileSize; final Map _tiles = {}; final Map _levels = {}; + Timer? _pruneLater; + @override void initState() { super.initState(); @@ -512,8 +515,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { bool _isZoomOutsideMinMax() { for (var tile in _tiles.values) { - if (tile.level.zoom > (options.maxZoom ?? 1.0) || - tile.level.zoom < (options.minZoom ?? 20.0)) { + if (tile.level.zoom > (options.maxZoom) || + tile.level.zoom < (options.minZoom)) { return true; } } @@ -524,11 +527,11 @@ class _TileLayerState extends State with TickerProviderStateMixin { if (options.updateInterval == null) { _throttleUpdate = null; } else { - _throttleUpdate = StreamController(sync: true); - _throttleUpdate.stream + _throttleUpdate = StreamController(sync: true); + _throttleUpdate!.stream .transform( - util.throttleStreamTransformerWithTrailingCall( - options.updateInterval, + util.throttleStreamTransformerWithTrailingCall( + options.updateInterval!, ), ) .listen(_update); @@ -539,6 +542,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { void dispose() { _removeAllTiles(); _moveSub?.cancel(); + _pruneLater?.cancel(); options.tileProvider.dispose(); _throttleUpdate?.close(); @@ -563,7 +567,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { color: options.backgroundColor, child: options.tilesContainerBuilder == null ? tilesContainer - : options.tilesContainerBuilder( + : options.tilesContainerBuilder!( context, tilesContainer, tilesToRender, @@ -577,8 +581,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { var level = tile.level; var tileSize = getTileSize(); var pos = (tilePos).multiplyBy(level.scale) + level.translatePoint; - var width = tileSize.x * level.scale; - var height = tileSize.y * level.scale; + num width = tileSize.x * level.scale; + num height = tileSize.y * level.scale; final Widget content = AnimatedTile( tile: tile, @@ -609,7 +613,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { } for (var key in toRemove) { - var tile = _tiles[key]; + var tile = _tiles[key]!; tile.tileReady = null; tile.dispose(tile.loadError && @@ -632,7 +636,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { return false; } - Level _updateLevels() { + Level? _updateLevels() { var zoom = _tileZoom; var maxZoom = options.maxZoom; @@ -661,8 +665,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { if (level == null) { level = _levels[zoom] = Level(); level.zIndex = maxZoom; - level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom) ?? - CustomPoint(0.0, 0.0); + level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom); level.zoom = zoom; _setZoomTransform(level, map.center, map.zoom); @@ -672,10 +675,6 @@ class _TileLayerState extends State with TickerProviderStateMixin { } void _pruneTiles() { - if (map == null) { - return; - } - var zoom = _tileZoom; if (zoom == null) { _removeAllTiles(); @@ -790,21 +789,20 @@ class _TileLayerState extends State with TickerProviderStateMixin { } double _clampZoom(double zoom) { - if (null != options.minNativeZoom && zoom < options.minNativeZoom) { - return options.minNativeZoom; + if (null != options.minNativeZoom && zoom < options.minNativeZoom!) { + return options.minNativeZoom!; } - if (null != options.maxNativeZoom && options.maxNativeZoom < zoom) { - return options.maxNativeZoom; + if (null != options.maxNativeZoom && options.maxNativeZoom! < zoom) { + return options.maxNativeZoom!; } return zoom; } void _setView(LatLng center, double zoom) { - var tileZoom = _clampZoom(zoom.roundToDouble()); - if ((options.maxZoom != null && tileZoom > options.maxZoom) || - (options.minZoom != null && tileZoom < options.minZoom)) { + double? tileZoom = _clampZoom(zoom.roundToDouble()); + if ((tileZoom > options.maxZoom) || (tileZoom < options.minZoom)) { tileZoom = null; } @@ -824,7 +822,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { void _setZoomTransforms(LatLng center, double zoom) { for (var i in _levels.keys) { - _setZoomTransform(_levels[i], center, zoom); + _setZoomTransform(_levels[i]!, center, zoom); } } @@ -834,7 +832,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { if (level.origin == null) { return; } - var translate = level.origin.multiplyBy(scale) - pixelOrigin; + var translate = level.origin!.multiplyBy(scale) - pixelOrigin; level.translatePoint = translate; level.scale = scale; } @@ -853,23 +851,23 @@ class _TileLayerState extends State with TickerProviderStateMixin { // wrapping _wrapX = crs.wrapLng; if (_wrapX != null) { - var first = - (map.project(LatLng(0.0, crs.wrapLng.item1), tileZoom).x / tileSize.x) - .floorToDouble(); - var second = - (map.project(LatLng(0.0, crs.wrapLng.item2), tileZoom).x / tileSize.y) - .ceilToDouble(); + var first = (map.project(LatLng(0.0, crs.wrapLng!.item1), tileZoom).x / + tileSize.x) + .floorToDouble(); + var second = (map.project(LatLng(0.0, crs.wrapLng!.item2), tileZoom).x / + tileSize.y) + .ceilToDouble(); _wrapX = Tuple2(first, second); } _wrapY = crs.wrapLat; if (_wrapY != null) { - var first = - (map.project(LatLng(crs.wrapLat.item1, 0.0), tileZoom).y / tileSize.x) - .floorToDouble(); - var second = - (map.project(LatLng(crs.wrapLat.item2, 0.0), tileZoom).y / tileSize.y) - .ceilToDouble(); + var first = (map.project(LatLng(crs.wrapLat!.item1, 0.0), tileZoom).y / + tileSize.x) + .floorToDouble(); + var second = (map.project(LatLng(crs.wrapLat!.item2, 0.0), tileZoom).y / + tileSize.y) + .ceilToDouble(); _wrapY = Tuple2(first, second); } } @@ -880,8 +878,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { if (_tileZoom == null) { // if there is no _tileZoom available it means we are out within zoom level // we will restore fully via _setView call if we are back on trail - if ((options.maxZoom != null && tileZoom <= options.maxZoom) && - (options.minZoom != null && tileZoom >= options.minZoom)) { + if ((tileZoom <= options.maxZoom) && (tileZoom >= options.minZoom)) { _tileZoom = tileZoom; setState(() { _setView(map.center, tileZoom); @@ -891,16 +888,16 @@ class _TileLayerState extends State with TickerProviderStateMixin { } } else { setState(() { - if ((tileZoom - _tileZoom).abs() >= 1) { + if ((tileZoom - _tileZoom!).abs() >= 1) { // It was a zoom lvl change _setView(map.center, tileZoom); _setZoomTransforms(map.center, map.zoom); } else { - if (null == _throttleUpdate) { + if (_throttleUpdate == null) { _update(null); } else { - _throttleUpdate.add(null); + _throttleUpdate!.add(null); } _setZoomTransforms(map.center, map.zoom); @@ -919,8 +916,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { // Private method to load tiles in the grid's active zoom level according to // map bounds - void _update(LatLng center) { - if (map == null || _tileZoom == null) { + void _update(LatLng? center) { + if (_tileZoom == null) { return; } @@ -929,8 +926,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { var pixelBounds = _getTiledPixelBounds(center); var tileRange = _pxBoundsToTileRange(pixelBounds); - var tileCenter = tileRange.getCenter(); - var queue = >[]; + var tileCenter = tileRange.center; + final queue = >[]; var margin = options.keepBuffer; var noPruneRange = Bounds( tileRange.bottomLeft - CustomPoint(margin, -margin), @@ -949,7 +946,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { // _update just loads more tiles. If the tile zoom level differs too much // from the map's, let _setView reset levels and prune old tiles. - if ((zoom - _tileZoom).abs() > 1) { + if ((zoom - _tileZoom!).abs() > 1) { _setView(center, zoom); return; } @@ -957,8 +954,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { // create a queue of coordinates to load tiles from for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { - var coords = Coords(i.toDouble(), j.toDouble()); - coords.z = _tileZoom; + final coords = Coords(i.toDouble(), j.toDouble()); + coords.z = _tileZoom!; if (!_isValidTile(coords)) { continue; @@ -980,7 +977,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { (a.distanceTo(tileCenter) - b.distanceTo(tileCenter)).toInt()); for (var i = 0; i < queue.length; i++) { - _addTile(queue[i]); + _addTile(queue[i] as Coords); } } @@ -1032,7 +1029,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { coordsKey: tileCoordsToKey, tilePos: _getTilePos(coords), current: true, - level: _levels[coords.z], + level: _levels[coords.z]!, imageProvider: options.tileProvider.getImage(_wrapCoords(coords), options), tileReady: _tileReady, @@ -1054,7 +1051,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { } for (var key in toRemove) { - var tile = _tiles[key]; + var tile = _tiles[key]!; tile.dispose(true); _tiles.remove(key); @@ -1073,7 +1070,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { } for (var key in toRemove) { - var tile = _tiles[key]; + var tile = _tiles[key]!; tile.dispose(true); _tiles.remove(key); @@ -1081,17 +1078,17 @@ class _TileLayerState extends State with TickerProviderStateMixin { } } - void _tileReady(Coords coords, dynamic error, Tile tile) { + void _tileReady(Coords coords, dynamic error, Tile? tile) { if (null != error) { print(error); - tile.loadError = true; + tile!.loadError = true; if (options.errorTileCallback != null) { - options.errorTileCallback(tile, error); + options.errorTileCallback!(tile, error); } } else { - tile.loadError = false; + tile!.loadError = false; } var key = _tileCoordsToKey(coords); @@ -1102,7 +1099,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { if (options.fastReplace && mounted) { setState(() { - tile.active = true; + tile!.active = true; if (_noTilesToLoad()) { // We're not waiting for anything, prune the tiles immediately. @@ -1122,7 +1119,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { tile.active = true; } else { tile.startFadeInAnimation( - options.tileFadeInDuration, + options.tileFadeInDuration!, this, from: fadeInStart, ); @@ -1135,9 +1132,10 @@ class _TileLayerState extends State with TickerProviderStateMixin { if (_noTilesToLoad()) { // Wait a bit more than tileFadeInDuration (the duration of the tile // fade-in) to trigger a pruning. - Future.delayed( + _pruneLater?.cancel(); + _pruneLater = Timer( options.tileFadeInDuration != null - ? options.tileFadeInDuration + const Duration(milliseconds: 50) + ? options.tileFadeInDuration! + const Duration(milliseconds: 50) : const Duration(milliseconds: 50), () { if (mounted) { @@ -1149,17 +1147,17 @@ class _TileLayerState extends State with TickerProviderStateMixin { } CustomPoint _getTilePos(Coords coords) { - var level = _levels[coords.z]; - return coords.scaleBy(getTileSize()) - level.origin; + var level = _levels[coords.z as double]!; + return coords.scaleBy(getTileSize()) - level.origin!; } Coords _wrapCoords(Coords coords) { var newCoords = Coords( _wrapX != null - ? util.wrapNum(coords.x.toDouble(), _wrapX) + ? util.wrapNum(coords.x.toDouble(), _wrapX!) : coords.x.toDouble(), _wrapY != null - ? util.wrapNum(coords.y.toDouble(), _wrapY) + ? util.wrapNum(coords.y.toDouble(), _wrapY!) : coords.y.toDouble(), ); newCoords.z = coords.z.toDouble(); @@ -1198,29 +1196,29 @@ class Tile implements Comparable { bool retain; bool active; bool loadError; - DateTime loaded; - DateTime loadStarted; + DateTime? loaded; + late DateTime loadStarted; - AnimationController animationController; + AnimationController? animationController; double get opacity => animationController == null ? (active ? 1.0 : 0.0) - : animationController.value; + : animationController!.value; // callback when tile is ready / error occurred - // it maybe be null forinstance when download aborted - TileReady tileReady; - ImageInfo imageInfo; - ImageStream _imageStream; - ImageStreamListener _listener; + // it maybe be null for instance when download aborted + TileReady? tileReady; + ImageInfo? imageInfo; + ImageStream? _imageStream; + late ImageStreamListener _listener; Tile({ - this.coordsKey, - this.coords, - this.tilePos, - this.imageProvider, + required this.coordsKey, + required this.coords, + required this.tilePos, + required this.imageProvider, this.tileReady, - this.level, + required this.level, this.current = false, this.active = false, this.retain = false, @@ -1234,11 +1232,11 @@ class Tile implements Comparable { final oldImageStream = _imageStream; _imageStream = imageProvider.resolve(ImageConfiguration()); - if (_imageStream.key != oldImageStream?.key) { + if (_imageStream!.key != oldImageStream?.key) { oldImageStream?.removeListener(_listener); _listener = ImageStreamListener(_tileOnLoad, onError: _tileOnError); - _imageStream.addListener(_listener); + _imageStream!.addListener(_listener); } } catch (e, s) { // make sure all exception is handled - #444 / #536 @@ -1248,8 +1246,9 @@ class Tile implements Comparable { // call this before GC! void dispose([bool evict = false]) { - if (evict && imageProvider != null) { + if (evict) { try { + // ignore: return_type_invalid_for_catch_error imageProvider.evict().catchError(print); } catch (e) { // this may be never called because catchError will handle errors, however @@ -1264,13 +1263,13 @@ class Tile implements Comparable { } void startFadeInAnimation(Duration duration, TickerProvider vsync, - {double from}) { + {double? from}) { animationController?.removeStatusListener(_onAnimateEnd); animationController = AnimationController(duration: duration, vsync: vsync) ..addStatusListener(_onAnimateEnd); - animationController.forward(from: from); + animationController!.forward(from: from); } void _onAnimateEnd(AnimationStatus status) { @@ -1282,13 +1281,13 @@ class Tile implements Comparable { void _tileOnLoad(ImageInfo imageInfo, bool synchronousCall) { if (null != tileReady) { this.imageInfo = imageInfo; - tileReady(coords, null, this); + tileReady!(coords, null, this); } } - void _tileOnError(dynamic exception, StackTrace stackTrace) { + void _tileOnError(dynamic exception, StackTrace? stackTrace) { if (null != tileReady) { - tileReady( + tileReady!( coords, exception ?? 'Unknown exception during loadTileImage', this); } } @@ -1316,16 +1315,15 @@ class Tile implements Comparable { class AnimatedTile extends StatefulWidget { final Tile tile; - final ImageProvider errorImage; - final TileBuilder tileBuilder; + final ImageProvider? errorImage; + final TileBuilder? tileBuilder; AnimatedTile({ - Key key, - @required this.tile, + Key? key, + required this.tile, this.errorImage, - @required this.tileBuilder, - }) : assert(null != tile), - super(key: key); + required this.tileBuilder, + }) : super(key: key); @override _AnimatedTileState createState() => _AnimatedTileState(); @@ -1338,7 +1336,7 @@ class _AnimatedTileState extends State { Widget build(BuildContext context) { final tileWidget = (widget.tile.loadError && widget.errorImage != null) ? Image( - image: widget.errorImage, + image: widget.errorImage!, fit: BoxFit.fill, ) : RawImage( @@ -1350,7 +1348,7 @@ class _AnimatedTileState extends State { opacity: widget.tile.opacity, child: widget.tileBuilder == null ? tileWidget - : widget.tileBuilder(context, tileWidget, widget.tile), + : widget.tileBuilder!(context, tileWidget, widget.tile), ); } @@ -1359,7 +1357,7 @@ class _AnimatedTileState extends State { super.initState(); if (null != widget.tile.animationController) { - widget.tile.animationController.addListener(_handleChange); + widget.tile.animationController!.addListener(_handleChange); listenerAttached = true; } } @@ -1378,7 +1376,7 @@ class _AnimatedTileState extends State { super.didUpdateWidget(oldWidget); if (!listenerAttached && null != widget.tile.animationController) { - widget.tile.animationController.addListener(_handleChange); + widget.tile.animationController!.addListener(_handleChange); listenerAttached = true; } } @@ -1391,15 +1389,15 @@ class _AnimatedTileState extends State { } class Level { - double zIndex; - CustomPoint origin; - double zoom; - CustomPoint translatePoint; - double scale; + late double zIndex; + CustomPoint? origin; + late double zoom; + late CustomPoint translatePoint; + late double scale; } class Coords extends CustomPoint { - T z; + late T z; Coords(T x, T y) : super(x, y); diff --git a/lib/src/layer/tile_provider/network_image_with_retry.dart b/lib/src/layer/tile_provider/network_image_with_retry.dart new file mode 100644 index 000000000..9b6094e92 --- /dev/null +++ b/lib/src/layer/tile_provider/network_image_with_retry.dart @@ -0,0 +1,46 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/painting.dart'; +import 'package:http/http.dart'; +import 'package:http/retry.dart'; + +class NetworkImageWithRetry extends ImageProvider { + /// The URL from which the image will be fetched. + final String url; + + /// The scale to place in the [ImageInfo] object of the image. + final double scale; + + /// The http RetryClient that is used for the requests + final RetryClient retryClient = RetryClient(Client()); + + NetworkImageWithRetry(this.url, {this.scale = 1.0}); + + @override + ImageStreamCompleter load(NetworkImageWithRetry key, decode) { + return OneFrameImageStreamCompleter(_loadWithRetry(key, decode), + informationCollector: () sync* { + yield ErrorDescription('Image provider: $this'); + yield ErrorDescription('Image key: $key'); + }); + } + + @override + Future obtainKey(ImageConfiguration configuration) { + return SynchronousFuture(this); + } + + Future _loadWithRetry( + NetworkImageWithRetry key, DecoderCallback decode) async { + assert(key == this); + + final uri = Uri.parse(url); + final response = await retryClient.get(uri); + final codec = await decode(response.bodyBytes); + final image = (await codec.getNextFrame()).image; + + return ImageInfo( + image: image, + scale: key.scale, + ); + } +} diff --git a/lib/src/layer/tile_provider/tile_provider.dart b/lib/src/layer/tile_provider/tile_provider.dart index 8aa0d4676..ab6adfc38 100644 --- a/lib/src/layer/tile_provider/tile_provider.dart +++ b/lib/src/layer/tile_provider/tile_provider.dart @@ -1,9 +1,10 @@ import 'dart:io'; import 'package:flutter/widgets.dart'; -import 'package:flutter_image/network.dart'; import 'package:flutter_map/flutter_map.dart'; +import 'network_image_with_retry.dart'; + abstract class TileProvider { const TileProvider(); @@ -13,7 +14,7 @@ abstract class TileProvider { String getTileUrl(Coords coords, TileLayerOptions options) { var urlTemplate = (options.wmsOptions != null) - ? options.wmsOptions + ? options.wmsOptions! .getUrl(coords, options.tileSize.toInt(), options.retinaMode) : options.urlTemplate; @@ -31,7 +32,7 @@ abstract class TileProvider { } var allOpts = Map.from(data) ..addAll(options.additionalOptions); - return options.templateFunction(urlTemplate, allOpts); + return options.templateFunction(urlTemplate!, allOpts); } double _getZoomForUrl(Coords coords, TileLayerOptions options) { @@ -91,7 +92,7 @@ class FileTileProvider extends TileProvider { class CustomTileProvider extends TileProvider { final String Function(Coords coors, TileLayerOptions options) customTileUrl; - const CustomTileProvider({@required this.customTileUrl}); + const CustomTileProvider({required this.customTileUrl}); @override String getTileUrl(Coords coords, TileLayerOptions options) { diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index a1dea4180..e66f4ddc3 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -19,10 +19,11 @@ class FlutterMapState extends MapGestureMixin { MapOptions get options => widget.options; @override - MapState mapState; + late final MapState mapState; - FlutterMapState(MapController mapController) - : mapController = mapController ?? MapController(); + FlutterMapState(MapController? mapController) + : mapController = mapController as MapControllerImpl? ?? + MapController() as MapControllerImpl; @override void didUpdateWidget(FlutterMap oldWidget) { @@ -41,7 +42,7 @@ class FlutterMapState extends MapGestureMixin { // Callback onMapCreated if not null if (options.onMapCreated != null) { - options.onMapCreated(mapController); + options.onMapCreated!(mapController); } } @@ -63,11 +64,11 @@ class FlutterMapState extends MapGestureMixin { } Stream _merge(LayerOptions options) { - if (options?.rebuild == null) return mapState.onMoved; + if (options.rebuild == null) return mapState.onMoved; var group = StreamGroup(); group.add(mapState.onMoved); - group.add(options.rebuild); + group.add(options.rebuild!); groups.add(group); return group.stream; } @@ -103,19 +104,16 @@ class FlutterMapState extends MapGestureMixin { child: Stack( children: [ OverflowBox( - minWidth: size.x, - maxWidth: size.x, - minHeight: size.y, - maxHeight: size.y, + minWidth: size.x as double?, + maxWidth: size.x as double?, + minHeight: size.y as double?, + maxHeight: size.y as double?, child: Transform.rotate( angle: mapState.rotationRad, child: Stack( children: [ - if (widget.children != null && - widget.children.isNotEmpty) - ...widget.children, - if (widget.layers != null && - widget.layers.isNotEmpty) + if (widget.children.isNotEmpty) ...widget.children, + if (widget.layers.isNotEmpty) ...widget.layers.map( (layer) => _createLayer(layer, options.plugins), ) @@ -125,11 +123,9 @@ class FlutterMapState extends MapGestureMixin { ), Stack( children: [ - if (widget.nonRotatedChildren != null && - widget.nonRotatedChildren.isNotEmpty) + if (widget.nonRotatedChildren.isNotEmpty) ...widget.nonRotatedChildren, - if (widget.nonRotatedLayers != null && - widget.nonRotatedLayers.isNotEmpty) + if (widget.nonRotatedLayers.isNotEmpty) ...widget.nonRotatedLayers.map( (layer) => _createLayer(layer, options.plugins), ) @@ -173,10 +169,9 @@ class FlutterMapState extends MapGestureMixin { if (options is OverlayImageLayerOptions) { return OverlayImageLayer(options, mapState, _merge(options)); } - assert(false, """ + throw (StateError(""" Can't find correct layer for $options. Perhaps when you create your FlutterMap you need something like this: - options: new MapOptions(plugins: [MyFlutterMapPlugin()])"""); - return null; + options: new MapOptions(plugins: [MyFlutterMapPlugin()])""")); } } diff --git a/lib/src/map/map.dart b/lib/src/map/map.dart index b8f534d27..e32c92a17 100644 --- a/lib/src/map/map.dart +++ b/lib/src/map/map.dart @@ -13,7 +13,7 @@ class MapControllerImpl implements MapController { final Completer _readyCompleter = Completer(); final StreamController _mapEventSink = StreamController.broadcast(); StreamSink get mapEventSink => _mapEventSink.sink; - MapState _state; + late final MapState _state; @override Future get onReady => _readyCompleter.future; @@ -31,13 +31,13 @@ class MapControllerImpl implements MapController { @override MoveAndRotateResult moveAndRotate(LatLng center, double zoom, double degree, - {String id}) { + {String? id}) { return _state.moveAndRotate(center, zoom, degree, - source: MapEventSource.mapController, id: id); + source: MapEventSource.mapController, id: id!); } @override - bool move(LatLng center, double zoom, {String id}) { + bool move(LatLng center, double zoom, {String? id}) { return _state.move(center, zoom, id: id, source: MapEventSource.mapController); } @@ -45,20 +45,17 @@ class MapControllerImpl implements MapController { @override void fitBounds( LatLngBounds bounds, { - FitBoundsOptions options = + FitBoundsOptions? options = const FitBoundsOptions(padding: EdgeInsets.all(12.0)), }) { - _state.fitBounds(bounds, options); + _state.fitBounds(bounds, options!); } - @override - bool get ready => _state != null; - @override LatLng get center => _state.center; @override - LatLngBounds get bounds => _state.bounds; + LatLngBounds? get bounds => _state.bounds; @override double get zoom => _state.zoom; @@ -67,7 +64,7 @@ class MapControllerImpl implements MapController { double get rotation => _state.rotation; @override - bool rotate(double degree, {String id}) { + bool rotate(double degree, {String? id}) { return _state.rotate(degree, id: id, source: MapEventSource.mapController); } @@ -95,10 +92,10 @@ class MapState { double get rotationRad => _rotationRad; - LatLng _lastCenter; - LatLngBounds _lastBounds; - Bounds _lastPixelBounds; - CustomPoint _pixelOrigin; + LatLng? _lastCenter; + LatLngBounds? _lastBounds; + Bounds? _lastPixelBounds; + late CustomPoint _pixelOrigin; bool _initialized = false; MapState(this.options, this.onRotationChanged, this._mapEventSink) @@ -110,15 +107,15 @@ class MapState { Stream get onMoved => _onMoveSink.stream; // Original size of the map where rotation isn't calculated - CustomPoint _originalSize; + CustomPoint? _originalSize; - CustomPoint get originalSize => _originalSize; + CustomPoint? get originalSize => _originalSize; void setOriginalSize(double width, double height) { final isCurrSizeNull = _originalSize == null; if (isCurrSizeNull || - _originalSize.x != width || - _originalSize.y != height) { + _originalSize!.x != width || + _originalSize!.y != height) { _originalSize = CustomPoint(width, height); _updateSizeByOriginalSizeAndRotation(); @@ -131,19 +128,21 @@ class MapState { } // Extended size of the map where rotation is calculated - CustomPoint _size; + CustomPoint? _size; CustomPoint get size => _size ?? CustomPoint(0.0, 0.0); void _updateSizeByOriginalSizeAndRotation() { - final originalWidth = _originalSize.x; - final originalHeight = _originalSize.y; + final originalWidth = _originalSize!.x; + final originalHeight = _originalSize!.y; if (_rotation != 0.0) { final cosAngle = math.cos(_rotationRad).abs(); final sinAngle = math.sin(_rotationRad).abs(); - final width = (originalWidth * cosAngle) + (originalHeight * sinAngle); - final height = (originalHeight * cosAngle) + (originalWidth * sinAngle); + final num width = + (originalWidth * cosAngle) + (originalHeight * sinAngle); + final num height = + (originalHeight * cosAngle) + (originalWidth * sinAngle); _size = CustomPoint(width, height); } else { @@ -155,10 +154,10 @@ class MapState { _initialized = true; } - _pixelOrigin = getNewPixelOrigin(_lastCenter); + _pixelOrigin = getNewPixelOrigin(_lastCenter!); } - LatLng get center => getCenter() ?? options.center; + LatLng get center => getCenter(); LatLngBounds get bounds => getBounds(); @@ -166,18 +165,18 @@ class MapState { void _init() { if (options.bounds != null) { - fitBounds(options.bounds, options.boundsOptions); + fitBounds(options.bounds!, options.boundsOptions); } else { - move(options.center, zoom); + move(options.center, zoom, source: MapEventSource.initialization); } } void _handleMoveEmit(LatLng targetCenter, double targetZoom, hasGesture, - MapEventSource source, String id) { + MapEventSource source, String? id) { if (source == MapEventSource.flingAnimationController) { emitMapEvent( MapEventFlingAnimation( - center: _lastCenter, + center: _lastCenter!, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -187,7 +186,7 @@ class MapState { } else if (source == MapEventSource.doubleTapZoomAnimationController) { emitMapEvent( MapEventDoubleTapZoom( - center: _lastCenter, + center: _lastCenter!, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -198,7 +197,7 @@ class MapState { source == MapEventSource.onMultiFinger) { emitMapEvent( MapEventMove( - center: _lastCenter, + center: _lastCenter!, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -209,7 +208,7 @@ class MapState { emitMapEvent( MapEventMove( id: id, - center: _lastCenter, + center: _lastCenter!, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -236,8 +235,8 @@ class MapState { double degree, { hasGesture = false, callOnMoveSink = true, - MapEventSource source, - String id, + required MapEventSource source, + String? id, }) { if (degree != _rotation) { var oldRotation = _rotation; @@ -251,7 +250,7 @@ class MapState { id: id, currentRotation: oldRotation, targetRotation: _rotation, - center: _lastCenter, + center: _lastCenter!, zoom: _zoom, source: source, ), @@ -268,7 +267,7 @@ class MapState { } MoveAndRotateResult moveAndRotate(LatLng center, double zoom, double degree, - {MapEventSource source, String id}) { + {required MapEventSource source, required String id}) { final moveSucc = move(center, zoom, id: id, source: source, callOnMoveSink: false); final rotateSucc = @@ -284,8 +283,8 @@ class MapState { bool move(LatLng center, double zoom, {hasGesture = false, callOnMoveSink = true, - MapEventSource source, - String id}) { + required MapEventSource source, + String? id}) { zoom = fitZoomToBounds(zoom); final mapMoved = center != _lastCenter || zoom != _zoom; @@ -315,20 +314,20 @@ class MapState { var mapPosition = MapPosition( center: center, bounds: bounds, zoom: zoom, hasGesture: hasGesture); - options.onPositionChanged(mapPosition, hasGesture); + options.onPositionChanged!(mapPosition, hasGesture); } return true; } - double fitZoomToBounds(double zoom) { + double fitZoomToBounds(double? zoom) { zoom ??= _zoom; // Abide to min/max zoom if (options.maxZoom != null) { - zoom = (zoom > options.maxZoom) ? options.maxZoom : zoom; + zoom = (zoom > options.maxZoom!) ? options.maxZoom! : zoom; } if (options.minZoom != null) { - zoom = (zoom < options.minZoom) ? options.minZoom : zoom; + zoom = (zoom < options.minZoom!) ? options.minZoom! : zoom; } return zoom; } @@ -338,19 +337,19 @@ class MapState { throw Exception('Bounds are not valid.'); } var target = getBoundsCenterZoom(bounds, options); - move(target.center, target.zoom); + move(target.center, target.zoom, source: MapEventSource.fitBounds); } LatLng getCenter() { if (_lastCenter != null) { - return _lastCenter; + return _lastCenter!; } return layerPointToLatLng(_centerLayerPoint); } LatLngBounds getBounds() { if (_lastBounds != null) { - return _lastBounds; + return _lastBounds!; } return _calculateBounds(); @@ -358,7 +357,7 @@ class MapState { Bounds getLastPixelBounds() { if (_lastPixelBounds != null) { - return _lastPixelBounds; + return _lastPixelBounds!; } return getPixelBounds(zoom); @@ -385,8 +384,8 @@ class MapState { zoom = math.min(options.maxZoom, zoom); var paddingOffset = (paddingBR - paddingTL) / 2; - var swPoint = project(bounds.southWest, zoom); - var nePoint = project(bounds.northEast, zoom); + var swPoint = project(bounds.southWest!, zoom); + var nePoint = project(bounds.northEast!, zoom); var center = unproject((swPoint + nePoint) / 2 + paddingOffset, zoom); return CenterZoom( center: center, @@ -396,7 +395,7 @@ class MapState { double getBoundsZoom(LatLngBounds bounds, CustomPoint padding, {bool inside = false}) { - var zoom = this.zoom ?? 0.0; + var zoom = this.zoom; var min = options.minZoom ?? 0.0; var max = options.maxZoom ?? double.infinity; var nw = bounds.northWest; @@ -414,14 +413,14 @@ class MapState { return math.max(min, math.min(max, zoom)); } - CustomPoint project(LatLng latlng, [double zoom]) { + CustomPoint project(LatLng latlng, [double? zoom]) { zoom ??= _zoom; return options.crs.latLngToPoint(latlng, zoom); } - LatLng unproject(CustomPoint point, [double zoom]) { + LatLng unproject(CustomPoint point, [double? zoom]) { zoom ??= _zoom; - return options.crs.pointToLatLng(point, zoom); + return options.crs.pointToLatLng(point, zoom)!; } LatLng layerPointToLatLng(CustomPoint point) { @@ -432,19 +431,19 @@ class MapState { return size / 2; } - double getZoomScale(double toZoom, double fromZoom) { + double getZoomScale(double toZoom, double? fromZoom) { var crs = options.crs; fromZoom = fromZoom ?? _zoom; return crs.scale(toZoom) / crs.scale(fromZoom); } - double getScaleZoom(double scale, double fromZoom) { + double getScaleZoom(double scale, double? fromZoom) { var crs = options.crs; fromZoom = fromZoom ?? _zoom; - return crs.zoom(scale * crs.scale(fromZoom)); + return crs.zoom(scale * crs.scale(fromZoom)) as double; } - Bounds getPixelWorldBounds(double zoom) { + Bounds? getPixelWorldBounds(double? zoom) { return options.crs.getProjectedBounds(zoom ?? _zoom); } @@ -452,7 +451,7 @@ class MapState { return _pixelOrigin; } - CustomPoint getNewPixelOrigin(LatLng center, [double zoom]) { + CustomPoint getNewPixelOrigin(LatLng center, [double? zoom]) { var viewHalf = size / 2.0; return (project(center, zoom) - viewHalf).round(); } @@ -465,9 +464,7 @@ class MapState { return Bounds(pixelCenter - halfSize, pixelCenter + halfSize); } - static MapState of(BuildContext context, {bool nullOk = false}) { - assert(context != null); - assert(nullOk != null); + static MapState? maybeOf(BuildContext context, {bool nullOk = false}) { final widget = context.dependOnInheritedWidgetOfExactType(); if (nullOk || widget != null) { diff --git a/lib/src/map/map_state_widget.dart b/lib/src/map/map_state_widget.dart index 9e97d05ea..661409229 100644 --- a/lib/src/map/map_state_widget.dart +++ b/lib/src/map/map_state_widget.dart @@ -6,9 +6,9 @@ class MapStateInheritedWidget extends InheritedWidget { final MapState mapState; MapStateInheritedWidget({ - Key key, - @required this.mapState, - @required Widget child, + Key? key, + required this.mapState, + required Widget child, }) : super(key: key, child: child); @override @@ -19,7 +19,7 @@ class MapStateInheritedWidget extends InheritedWidget { return true; } - static MapStateInheritedWidget of(BuildContext context) { + static MapStateInheritedWidget? maybeOf(BuildContext context) { return context .dependOnInheritedWidgetOfExactType(); } diff --git a/pubspec.yaml b/pubspec.yaml index 357f1fe05..76590f474 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,30 +1,30 @@ name: flutter_map -description: A Dart implementation of Leaflet for Flutter apps -version: 0.12.0 -repository: https://github.com/johnpryan/flutter_map +description: A Dart implementation of Leaflet to provide a Map widget for Flutter apps +version: 0.13.0 +repository: https://github.com/fleaflet/flutter_map environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=2.0.0" + sdk: '>=2.12.0 <3.0.0' + flutter: '>=2.0.0' dependencies: + async: ^2.1.0 + collection: ^1.15.0 flutter: sdk: flutter - tuple: ^1.0.2 + http: ^0.13.2 + intl: ^0.17.0 latlong2: ^0.8.0 positioned_tap_detector_2: ^1.0.0 - transparent_image: ^1.0.0 - async: ^2.1.0 - flutter_image: ^3.0.0 - vector_math: ^2.0.0 - proj4dart: ^1.0.4 - meta: ^1.1.0 - collection: ^1.14.0 + proj4dart: ^2.0.0 + transparent_image: ^2.0.0 + tuple: ^2.0.0 + vector_math: ^2.1.0 dev_dependencies: - pedantic: ^1.11.0 - location: ^2.5.0 flutter_test: sdk: flutter - test: ^1.9.0 - mockito: ^4.1.4 + location: ^4.1.1 + mockito: ^5.0.2 + pedantic: ^1.11.0 + test: ^1.16.5 diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 1d7920157..48a86f330 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -13,36 +13,42 @@ class MockHttpClientResponse extends Mock implements HttpClientResponse { @override int get statusCode => HttpStatus.ok; + @override + int get contentLength => File('test/res/map.png').lengthSync(); + @override HttpClientResponseCompressionState get compressionState => HttpClientResponseCompressionState.notCompressed; @override - StreamSubscription> listen(void Function(List event) onData, - {Function onError, void Function() onDone, bool cancelOnError}) { - return _stream.listen(onData, - onError: onError, onDone: onDone, cancelOnError: cancelOnError); + StreamSubscription> listen(void Function(List event)? onData, + {Function? onError, void Function()? onDone, bool? cancelOnError}) { + return _stream.listen( + onData, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); } static Stream> readFile() => File('test/res/map.png').openRead(); } -class MockHttpClientRequest extends Mock implements HttpClientRequest {} +class MockHttpClientRequest extends Mock implements HttpClientRequest { + @override + Future close() => Future.value(MockHttpClientResponse()); +} class MockClient extends Mock implements HttpClient { @override Future getUrl(Uri url) { - final request = MockHttpClientRequest(); - when(request.close()).thenAnswer((_) async { - return MockHttpClientResponse(); - }); - return Future.value(request); + return Future.value(MockHttpClientRequest()); } } class MockHttpOverrides extends HttpOverrides { @override - HttpClient createHttpClient(SecurityContext securityContext) => MockClient(); + HttpClient createHttpClient(SecurityContext? securityContext) => MockClient(); } void main() {