From 79b54e4008b7d77ac54a55e9407839a4cd9854bc Mon Sep 17 00:00:00 2001 From: Luka S Date: Sun, 4 Jun 2023 12:01:41 +0100 Subject: [PATCH] Multiple Improvements & v5 Release Preparation (#1532) * Multiple changes, see PR body * Updated CHANGELOG * Added `offset` option to `FlutterMapState.move` and related methods (resolves #1460, #777, #952) Major refactoring and re-organization to improve understandibility * Updated CHANGELOG * Ensure `id` of `MapController.move` is passed through to the emitted `MapEventMove` Originally from #1534 (3eca34e84ff465098df078d803f7d09fa385db84) - thanks @rorystephenson! Co-Authored-By: Rory Stephenson <3683599+rorystephenson@users.noreply.github.com> * Fixed regression in commit 2764747 where internal `MapController` state was not continuous * Added `rotateAroundPoint` - resolves #1460 Co-Authored-By: 6y * Updated CHANGELOG * Added `TileLayer.fallbackUrl` support to `FileTileProvider` Improved performance of `AssetTileProvider` when `TileLayer.fallbackUrl` not specified (resolves #1436) Improved documentation about performance pitfalls of `TileLayer.fallbackUrl` * Updated CHANGELOG * Updated pubspec.yaml Updated CHANGELOG * Removed `saveLayers` property from `PolylineLayer` * Updated CHANGELOG * Updated MacOS configuration Removed unnecessary Java installation from Windows GitHub Actions builder * Added more position options to `AnchorAlign` Deprecated `AnchorAlign.none` in favour of `AnchorAlign.center` or `null` Improved response/emission time of `onTap`/`MapEventTap` when `InteractiveFlag.doubleTapZoom` is disabled Improved `MarkerLayer`/`Layer` interoperability Improved/reorganized example application Updated CHANGELOG * Simplified `Anchor` Improved documentation of marker anchor methods Improved CHANGELOG * Updated version numbers * Fixed bug in example application * Added automated publishing action for tags in format 'v_._._' * Updated CHANGELOG * Added credit to CHANGELOG --------- Co-authored-by: Rory Stephenson <3683599+rorystephenson@users.noreply.github.com> Co-authored-by: 6y --- .github/workflows/build-io.yml | 5 - .github/workflows/pr.yml | 5 +- .github/workflows/publish.yml | 13 + .github/workflows/push.yml | 2 +- CHANGELOG.md | 55 +++ LICENSE | 3 +- README.md | 19 +- example/android/app/build.gradle | 4 +- example/lib/main.dart | 34 +- .../{fallback_url => }/fallback_url.dart | 0 .../lib/pages/fallback_url_network_page.dart | 2 +- .../lib/pages/fallback_url_offline_page.dart | 29 -- example/lib/pages/home.dart | 157 ++++++-- example/lib/pages/latlng_to_screen_point.dart | 2 +- example/lib/pages/live_location.dart | 182 --------- example/lib/pages/map_controller.dart | 76 ---- example/lib/pages/marker_anchor.dart | 130 ------ example/lib/pages/marker_rotate.dart | 158 -------- example/lib/pages/markers.dart | 178 +++++++++ example/lib/pages/max_bounds.dart | 51 --- example/lib/pages/network_tile_provider.dart | 83 ---- example/lib/pages/on_tap.dart | 102 ----- example/lib/pages/polyline.dart | 237 +++++------ example/lib/pages/tap_to_add.dart | 69 ---- example/lib/pages/widgets.dart | 122 ------ example/lib/test_app.dart | 47 --- example/lib/widgets/drawer.dart | 186 ++++----- .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile | 2 +- example/macos/Podfile.lock | 19 +- .../macos/Runner.xcodeproj/project.pbxproj | 9 +- example/macos/Runner/Release.entitlements | 2 + example/pubspec.yaml | 3 +- lib/flutter_map.dart | 371 +----------------- lib/plugin_api.dart | 10 +- lib/src/geo/{crs => }/crs.dart | 43 +- lib/src/gestures/gestures.dart | 2 +- lib/src/gestures/map_events.dart | 2 +- lib/src/layer/attribution_layer/rich.dart | 6 +- lib/src/layer/circle_layer.dart | 2 +- lib/src/layer/marker_layer.dart | 177 +++++---- lib/src/layer/overlay_image_layer.dart | 4 +- lib/src/layer/polygon_layer.dart | 2 +- lib/src/layer/polyline_layer.dart | 24 +- lib/src/layer/tile_layer/tile.dart | 2 +- .../tile_layer/tile_bounds/tile_bounds.dart | 4 +- .../tile_bounds/tile_bounds_at_zoom.dart | 2 +- .../layer/tile_layer/tile_image_manager.dart | 2 +- lib/src/layer/tile_layer/tile_layer.dart | 14 +- .../layer/tile_layer/tile_layer_options.dart | 4 +- .../tile_provider/asset_tile_provider.dart | 30 +- .../file_providers/tile_provider_io.dart | 25 +- .../file_providers/tile_provider_stub.dart | 10 +- lib/src/layer/tile_layer/tile_range.dart | 4 +- .../tile_layer/tile_range_calculator.dart | 4 +- .../tile_layer/tile_scale_calculator.dart | 2 +- .../tile_layer/tile_update_transformer.dart | 2 +- lib/src/map/controller.dart | 295 ++++++++++++++ lib/src/map/map.dart | 95 ----- lib/src/map/map_state_widget.dart | 25 -- lib/src/map/options.dart | 199 ++++++++++ .../{flutter_map_state.dart => state.dart} | 236 +++++++---- lib/src/map/widget.dart | 37 ++ lib/src/{core => misc}/center_zoom.dart | 0 lib/src/misc/fit_bounds_options.dart | 19 + lib/src/misc/move_and_rotate_result.dart | 6 + lib/src/{core => misc}/point.dart | 0 lib/src/misc/position.dart | 23 ++ lib/src/{core => misc/private}/bounds.dart | 2 +- .../private}/positioned_tap_detector_2.dart | 32 +- lib/src/{core => misc/private}/util.dart | 0 pubspec.yaml | 16 +- test/core/bounds_test.dart | 4 +- .../tile_layer/tile_bounds/crs_fakes.dart | 4 +- .../tile_bounds/tile_bounds_at_zoom_test.dart | 4 +- .../tile_bounds/tile_bounds_test.dart | 2 +- test/layer/tile_layer/tile_range_test.dart | 4 +- tool/history.sh | 3 - windowsApplicationInstallerSetup.iss | 2 +- 79 files changed, 1614 insertions(+), 2130 deletions(-) create mode 100644 .github/workflows/publish.yml rename example/lib/pages/{fallback_url => }/fallback_url.dart (100%) delete mode 100644 example/lib/pages/fallback_url_offline_page.dart delete mode 100644 example/lib/pages/live_location.dart delete mode 100644 example/lib/pages/marker_anchor.dart delete mode 100644 example/lib/pages/marker_rotate.dart create mode 100644 example/lib/pages/markers.dart delete mode 100644 example/lib/pages/max_bounds.dart delete mode 100644 example/lib/pages/network_tile_provider.dart delete mode 100644 example/lib/pages/on_tap.dart delete mode 100644 example/lib/pages/tap_to_add.dart delete mode 100644 example/lib/pages/widgets.dart delete mode 100644 example/lib/test_app.dart rename lib/src/geo/{crs => }/crs.dart (92%) create mode 100644 lib/src/map/controller.dart delete mode 100644 lib/src/map/map.dart delete mode 100644 lib/src/map/map_state_widget.dart create mode 100644 lib/src/map/options.dart rename lib/src/map/{flutter_map_state.dart => state.dart} (82%) create mode 100644 lib/src/map/widget.dart rename lib/src/{core => misc}/center_zoom.dart (100%) create mode 100644 lib/src/misc/fit_bounds_options.dart create mode 100644 lib/src/misc/move_and_rotate_result.dart rename lib/src/{core => misc}/point.dart (100%) create mode 100644 lib/src/misc/position.dart rename lib/src/{core => misc/private}/bounds.dart (98%) rename lib/src/{core => misc/private}/positioned_tap_detector_2.dart (87%) rename lib/src/{core => misc/private}/util.dart (100%) delete mode 100755 tool/history.sh diff --git a/.github/workflows/build-io.yml b/.github/workflows/build-io.yml index f958051f7..9026d14b7 100644 --- a/.github/workflows/build-io.yml +++ b/.github/workflows/build-io.yml @@ -39,11 +39,6 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v3 - - name: Setup Java 17 Environment - uses: actions/setup-java@v3 - with: - distribution: "temurin" - java-version: "17" - name: Setup Flutter Environment uses: subosito/flutter-action@v2 with: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index aae5d2c36..1848cf21d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,5 +1,8 @@ -name: PR CI/CD +name: Branch Push & PR CI/CD on: + push: + branches: + - '!master' pull_request: workflow_dispatch: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..c53c8e686 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,13 @@ +name: Publish To pub.dev + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+*' + +jobs: + publish: + name: "Publish" + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + with: + environment: 'pub.dev' \ No newline at end of file diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 52b3658dc..b6deb4e7f 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -1,4 +1,4 @@ -name: Push CI/CD +name: Master Push CI/CD on: push: branches: diff --git a/CHANGELOG.md b/CHANGELOG.md index 72a33b9ff..ce62416fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,60 @@ # Changelog +## [5.0.0] - 2023/06/XX + +**"Dart The Third"** + +Contains the following API changes: + +- Migrated to Flutter 3.10 and Dart 3.0 minimums - [#1512](https://github.com/fleaflet/flutter_map/pull/1512) & [#1517](https://github.com/fleaflet/flutter_map/pull/1517) +- Added offset capability to `FlutterMapState.move`/`MapController.move` methods - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) for [#952](https://github.com/fleaflet/flutter_map/issues/952) +- Added `MapController.rotateAroundPoint` method - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) for [#1460](https://github.com/fleaflet/flutter_map/issues/1460) +- Added `TileLayer.fallbackUrl` support to `FileTileProvider` - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) +- Added more position options to `AnchorAlign` - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) +- Improved `MarkerLayer`/`Layer` interoperability - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) +- Improved response/emission time of `onTap`/`MapEventTap` when `InteractiveFlag.doubleTapZoom` is disabled - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) +- Improved (stricter) typing of `CustomPoint` - [#1515](https://github.com/fleaflet/flutter_map/pull/1515) +- Deprecated `TileUpdateTransformers.alwaysLoadAndPrune` in favour of `ignoreTapEvents` - [#1517](https://github.com/fleaflet/flutter_map/pull/1517) +- Deprecated `AnchorAlign.none` in favour of `AnchorAlign.center` or `null` - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) +- Removed `NetworkNoRetryTileProvider` in favour of custom `NetworkTileProvider.httpClient` - [#1512](https://github.com/fleaflet/flutter_map/pull/1512) +- Removed `FileTileProvider` fallback to `NetworkTileProvider` on web - [#1512](https://github.com/fleaflet/flutter_map/pull/1512) +- Removed `saveLayers` property from `PolylineLayer` - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) & [#1519](https://github.com/fleaflet/flutter_map/pull/1519) +- Updated dependencies - [#1530](https://github.com/fleaflet/flutter_map/pull/1530) + - Updated 'latlong2' to access `const` `LatLng` objects + - Updated 'http' + - Removed 'tuple' in favour of built-in `Record`s ([#1517](https://github.com/fleaflet/flutter_map/pull/1517)) + +Contains the following bug fixes: + +- Polylines with translucent fills and borders now paint properly - [#1519](https://github.com/fleaflet/flutter_map/pull/1519) for [#1510](https://github.com/fleaflet/flutter_map/issues/1510) & [#1420](https://github.com/fleaflet/flutter_map/issues/1420) +- Ensure `id` of `MapController.move` is passed through to the emitted `MapEventMove` - [#1534](https://github.com/fleaflet/flutter_map/pull/1534) in [#1532](https://github.com/fleaflet/flutter_map/pull/1532) for [#1542](https://github.com/fleaflet/flutter_map/issues/1542) +- Fixed MacOS pinch zoom gesture issue - [#1543](https://github.com/fleaflet/flutter_map/pull/1543) for part of [#1354](https://github.com/fleaflet/flutter_map/issues/1354) +- Removed potential for jitter/frame delay when painting `Polyline`s & `Polygon`s - [#1514](https://github.com/fleaflet/flutter_map/pull/1514) +- Removed potential for un-`mounted` `setState` call in `RichAttributionWidget` - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) for [#1538](https://github.com/fleaflet/flutter_map/issues/1538) + +Contains the following performance improvements: + +- Reduced unnecessary rebuilding in environments where `MediaQuery` changes frequently - [#1523](https://github.com/fleaflet/flutter_map/pull/1523) +- Use Flutter's default `CachingAssetBundle` in `AssetTileProvider` when `TileLayer.fallbackUrl` is not specified - [#1532](https://github.com/fleaflet/flutter_map/pull/1532) for [#1436](https://github.com/fleaflet/flutter_map/issues/1436) +- Improved performance of `TileProvider`s and `FlutterMapNetworkImageProvider` - [#1512](https://github.com/fleaflet/flutter_map/pull/1512) + +In other news: + +- You may have noticed some minor rebranding around the repo recently! The maintainers have finally gained full member access from the previous owner (thanks John :)) to the 'fleaflet' organisation and now have total control. +- We've launched a Live Web Demo so you can experiment with flutter_map on the web without having to install any apps or build from source yourself! Visit [demo.fleaflet.dev](https://demo.fleaflet.dev). +- We've made some big changes to the structure/organization of flutter_map internals, which we hope should make it easier for new contributors to add code due to the reduction of the scope of responsibility of each source file. + +Many thanks to these contributors (in no particular order): + +- @josxha +- @ignatz +- @rorystephenson +- @tlserver +- @JosefWN +- ... and all the maintainers + +And an additional special thanks to @josxha & @ignatz for investing so much of their time into this project recently - we appreciate it! + ## [4.0.0] - 2023/05/05 **"Out With The Old, In With The New"** diff --git a/LICENSE b/LICENSE index 760dc4f77..d5c2bcf01 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,4 @@ -Copyright (c) 2018-2023, the 'flutter_map' authors and maintainers -Loosely based on the original works of 'leaflet.js' (c) by Vladimir Agafonkin & CloudMade +Copyright (c) 2018-2023, the 'flutter_map' authors and maintainers, loosely based on the original works of 'leaflet.js' by Vladimir Agafonkin & CloudMade All rights reserved. diff --git a/README.md b/README.md index 4a2c80540..8f939d8c3 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,25 @@ # flutter_map -A versatile mapping package for Flutter, based off of ['leaflet.js'](https://leafletjs.com/). Simple and easy to learn, yet completely customizable and configurable, it's the best choice for mapping in your Flutter app. +A versatile mapping package for Flutter. Simple and easy to learn, yet completely customizable and configurable, it's the best choice for mapping in your Flutter app. [![Pub.dev](https://img.shields.io/pub/v/flutter_map.svg?label=Latest+Version)](https://pub.dev/packages/flutter_map) [![Checks & Tests](https://badgen.net/github/checks/fleaflet/flutter_map?label=Checks+%26+Tests&color=orange)](https://github.com/fleaflet/flutter_map/actions?query=branch%3Amaster) [![points](https://img.shields.io/pub/points/flutter_map?logo=flutter)](https://pub.dev/packages/flutter_map/score) [![stars](https://badgen.net/github/stars/fleaflet/flutter_map?label=stars&color=green&icon=github)](https://github.com/fleaflet/flutter_map/stargazers) [![likes](https://img.shields.io/pub/likes/flutter_map?logo=flutter)](https://pub.dev/packages/flutter_map/score)      [![Open Issues](https://badgen.net/github/open-issues/fleaflet/flutter_map?label=Open+Issues&color=green)](https://GitHub.com/fleaflet/flutter_map/issues) [![Open PRs](https://badgen.net/github/open-prs/fleaflet/flutter_map?label=Open+PRs&color=green)](https://GitHub.com/fleaflet/flutter_map/pulls) --- -## [Discord Server](https://discord.gg/egEGeByf4q) +## [Documentation](https://docs.fleaflet.dev/) -Join the Discord server: ! +Visit our [documentation website](https://docs.fleaflet.dev/), for all information about how to use this library. +Additional documentation can be found in-code/whilst you type and over at the [pub.dev package](https://pub.dev/documentation/flutter_map/latest/flutter_map/flutter_map-library.html). -Talk about 'flutter_map', get and give help, and receive notifications about new 'flutter_map' updates! More additions planned in the future. +## [Live Web Demo](https://demo.fleaflet.dev/) -## [Documentation](https://docs.fleaflet.dev/) +> Please don't abuse the web demo! It runs on limited bandwidth and won't hold up to thousands of loads. +If you're going to be straining the application, please see the [Examples page in the docs](https://docs.fleaflet.dev/getting-started/examples), for information about building/using the application locally. + +Want to see how flutter_map looks and works quickly? Just visit our [demo site](https://demo.fleaflet.dev/) that gets updated automatically with every new commit to 'master'! -Visit the [documentation website here](https://docs.fleaflet.dev/), for all information about how to use this library, including setup and usage instructions. +## [Get Help](https://docs.fleaflet.dev/#get-help) -Some documentation is also provided within the code and can be view inside your favourite editor. These docs are also over at the [pub.dev package](https://pub.dev/documentation/flutter_map/latest/flutter_map/flutter_map-library.html), and can be viewed from within your favourite editor. +Not sure about how to do something, or just want to chat? Pop over to our [Discord server](https://discord.gg/BwpEsjqMAH) to get support quickly, and to get notified about new releases! +Think you've found a bug, or would like to see a new feature? We'd love to hear about it! Please see the [Get Help section of our docs](https://docs.fleaflet.dev/#get-help) for information about what to do. diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 145f610b0..90adb0a88 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -13,12 +13,12 @@ if (flutterRoot == null) { def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { - flutterVersionCode = '4' + flutterVersionCode = '5' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { - flutterVersionName = '4.0.0' + flutterVersionName = '5.0.0' } apply plugin: 'com.android.application' diff --git a/example/lib/main.dart b/example/lib/main.dart index 6c7ed31d8..fbc18d44c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,21 +6,15 @@ import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/epsg3413_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/fallback_url_network_page.dart'; -import 'package:flutter_map_example/pages/fallback_url_offline_page.dart'; import 'package:flutter_map_example/pages/home.dart'; import 'package:flutter_map_example/pages/interactive_test_page.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; -import 'package:flutter_map_example/pages/live_location.dart'; import 'package:flutter_map_example/pages/many_markers.dart'; import 'package:flutter_map_example/pages/map_controller.dart'; import 'package:flutter_map_example/pages/map_inside_listview.dart'; -import 'package:flutter_map_example/pages/marker_anchor.dart'; -import 'package:flutter_map_example/pages/marker_rotate.dart'; -import 'package:flutter_map_example/pages/max_bounds.dart'; +import 'package:flutter_map_example/pages/markers.dart'; import 'package:flutter_map_example/pages/moving_markers.dart'; -import 'package:flutter_map_example/pages/network_tile_provider.dart'; import 'package:flutter_map_example/pages/offline_map.dart'; -import 'package:flutter_map_example/pages/on_tap.dart'; import 'package:flutter_map_example/pages/overlay_image.dart'; import 'package:flutter_map_example/pages/plugin_scalebar.dart'; import 'package:flutter_map_example/pages/plugin_zoombuttons.dart'; @@ -31,39 +25,37 @@ import 'package:flutter_map_example/pages/reset_tile_layer.dart'; import 'package:flutter_map_example/pages/secondary_tap.dart'; import 'package:flutter_map_example/pages/sliding_map.dart'; import 'package:flutter_map_example/pages/stateful_markers.dart'; -import 'package:flutter_map_example/pages/tap_to_add.dart'; import 'package:flutter_map_example/pages/tile_builder_example.dart'; import 'package:flutter_map_example/pages/tile_loading_error_handle.dart'; -import 'package:flutter_map_example/pages/widgets.dart'; import 'package:flutter_map_example/pages/wms_tile_layer.dart'; +import 'package:url_strategy/url_strategy.dart'; -void main() => runApp(const MyApp()); +void main() { + setPathUrlStrategy(); + runApp(const MyApp()); +} class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'flutter_map Demo', - theme: ThemeData.light(useMaterial3: true), + theme: ThemeData( + useMaterial3: true, + colorSchemeSeed: const Color(0xFF8dea88), + ), home: const HomePage(), routes: { - NetworkTileProviderPage.route: (context) => - const NetworkTileProviderPage(), - WidgetsPage.route: (context) => const WidgetsPage(), - TapToAddPage.route: (context) => const TapToAddPage(), PolylinePage.route: (context) => const PolylinePage(), MapControllerPage.route: (context) => const MapControllerPage(), AnimatedMapControllerPage.route: (context) => const AnimatedMapControllerPage(), - MarkerAnchorPage.route: (context) => const MarkerAnchorPage(), + MarkerPage.route: (context) => const MarkerPage(), PluginScaleBar.route: (context) => const PluginScaleBar(), PluginZoomButtons.route: (context) => const PluginZoomButtons(), OfflineMapPage.route: (context) => const OfflineMapPage(), - OnTapPage.route: (context) => const OnTapPage(), - MarkerRotatePage.route: (context) => const MarkerRotatePage(), MovingMarkersPage.route: (context) => const MovingMarkersPage(), CirclePage.route: (context) => const CirclePage(), OverlayImagePage.route: (context) => const OverlayImagePage(), @@ -71,7 +63,6 @@ class MyApp extends StatelessWidget { SlidingMapPage.route: (_) => const SlidingMapPage(), WMSLayerPage.route: (context) => const WMSLayerPage(), CustomCrsPage.route: (context) => const CustomCrsPage(), - LiveLocationPage.route: (context) => const LiveLocationPage(), TileLoadingErrorHandle.route: (context) => const TileLoadingErrorHandle(), TileBuilderPage.route: (context) => const TileBuilderPage(), @@ -82,14 +73,11 @@ class MyApp extends StatelessWidget { ResetTileLayerPage.route: (context) => const ResetTileLayerPage(), EPSG4326Page.route: (context) => const EPSG4326Page(), EPSG3413Page.route: (context) => const EPSG3413Page(), - MaxBoundsPage.route: (context) => const MaxBoundsPage(), PointToLatLngPage.route: (context) => const PointToLatLngPage(), LatLngScreenPointTestPage.route: (context) => const LatLngScreenPointTestPage(), FallbackUrlNetworkPage.route: (context) => const FallbackUrlNetworkPage(), - FallbackUrlOfflinePage.route: (context) => - const FallbackUrlOfflinePage(), SecondaryTapPage.route: (context) => const SecondaryTapPage(), }, ); diff --git a/example/lib/pages/fallback_url/fallback_url.dart b/example/lib/pages/fallback_url.dart similarity index 100% rename from example/lib/pages/fallback_url/fallback_url.dart rename to example/lib/pages/fallback_url.dart diff --git a/example/lib/pages/fallback_url_network_page.dart b/example/lib/pages/fallback_url_network_page.dart index 2d857627b..464827ac9 100644 --- a/example/lib/pages/fallback_url_network_page.dart +++ b/example/lib/pages/fallback_url_network_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/pages/fallback_url/fallback_url.dart'; +import 'package:flutter_map_example/pages/fallback_url.dart'; import 'package:latlong2/latlong.dart'; class FallbackUrlNetworkPage extends StatelessWidget { diff --git a/example/lib/pages/fallback_url_offline_page.dart b/example/lib/pages/fallback_url_offline_page.dart deleted file mode 100644 index aa4818b93..000000000 --- a/example/lib/pages/fallback_url_offline_page.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/pages/fallback_url/fallback_url.dart'; -import 'package:latlong2/latlong.dart'; - -class FallbackUrlOfflinePage extends StatelessWidget { - static const String route = '/fallback_url_offline'; - - const FallbackUrlOfflinePage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return FallbackUrlPage( - route: route, - tileLayer: TileLayer( - tileProvider: AssetTileProvider(), - maxZoom: 14, - urlTemplate: 'assets/fake/tiles/{z}/{x}/{y}.png', - fallbackUrl: 'assets/map/anholt_osmbright/{z}/{x}/{y}.png', - ), - title: 'Fallback URL AssetTileProvider', - description: - 'Map with a fake asset path, should be using the fallback to show Anholt Island, Denmark.', - maxZoom: 14, - minZoom: 12, - center: const LatLng(56.704173, 11.543808), - ); - } -} diff --git a/example/lib/pages/home.dart b/example/lib/pages/home.dart index 159575901..794d8f61d 100644 --- a/example/lib/pages/home.dart +++ b/example/lib/pages/home.dart @@ -1,49 +1,106 @@ +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_example/widgets/drawer.dart'; import 'package:latlong2/latlong.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; -class HomePage extends StatelessWidget { +class HomePage extends StatefulWidget { static const String route = '/'; const HomePage({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { - final markers = [ - Marker( - width: 80, - height: 80, - point: const LatLng(51.5, -0.09), - builder: (ctx) => const FlutterLogo( - textColor: Colors.blue, - key: ObjectKey(Colors.blue), - ), - ), - Marker( - width: 80, - height: 80, - point: const LatLng(53.3498, -6.2603), - builder: (ctx) => const FlutterLogo( - textColor: Colors.green, - key: ObjectKey(Colors.green), - ), - ), - Marker( - width: 80, - height: 80, - point: const LatLng(48.8566, 2.3522), - builder: (ctx) => const FlutterLogo( - textColor: Colors.purple, - key: ObjectKey(Colors.purple), - ), - ), - ]; + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + @override + void initState() { + super.initState(); + + const seenIntroBoxKey = 'seenIntroBox(a)'; + if (kIsWeb && Uri.base.host.trim() == 'demo.fleaflet.dev') { + SchedulerBinding.instance.addPostFrameCallback( + (_) async { + final prefs = await SharedPreferences.getInstance(); + if (prefs.getBool(seenIntroBoxKey) ?? false) return; + + if (!mounted) return; + final width = MediaQuery.of(context).size.width; + await showDialog( + context: context, + builder: (context) => AlertDialog( + icon: UnconstrainedBox( + child: SizedBox.square( + dimension: 64, + child: + Image.asset('assets/ProjectIcon.png', fit: BoxFit.fill), + ), + ), + title: const Text('flutter_map Live Web Demo'), + content: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: width < 750 + ? double.infinity + : (width / (width < 1100 ? 1.5 : 2.5)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + "This is built automatically off of the latest commits to 'master', so may not reflect the latest release available on pub.dev.\nThis is hosted on Firebase Hosting, meaning there's limited bandwidth to share between all users, so please keep loads to a minimum.", + textAlign: TextAlign.center, + ), + Padding( + padding: + const EdgeInsets.only(right: 8, top: 16, bottom: 4), + child: Align( + alignment: Alignment.centerRight, + child: Text( + "This won't be shown again", + style: TextStyle( + color: Theme.of(context) + .colorScheme + .inverseSurface + .withOpacity(0.5), + ), + textAlign: TextAlign.right, + ), + ), + ), + ], + ), + ), + actions: [ + TextButton.icon( + onPressed: () => Navigator.of(context).pop(), + label: const Text('OK'), + icon: const Icon(Icons.done), + ), + ], + contentPadding: const EdgeInsets.only( + left: 24, + top: 16, + bottom: 0, + right: 24, + ), + ), + ); + await prefs.setBool(seenIntroBoxKey, true); + }, + ); + } + } + + @override + Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Home')), - drawer: buildDrawer(context, route), + drawer: buildDrawer(context, HomePage.route), body: Padding( padding: const EdgeInsets.all(8), child: Column( @@ -57,6 +114,10 @@ class HomePage extends StatelessWidget { options: MapOptions( center: const LatLng(51.5, -0.09), zoom: 5, + maxBounds: LatLngBounds( + const LatLng(-90, -180), + const LatLng(90, 180), + ), ), nonRotatedChildren: [ RichAttributionWidget( @@ -82,7 +143,37 @@ class HomePage extends StatelessWidget { 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), - MarkerLayer(markers: markers), + MarkerLayer( + markers: [ + Marker( + width: 80, + height: 80, + point: const LatLng(51.5, -0.09), + builder: (ctx) => const FlutterLogo( + textColor: Colors.blue, + key: ObjectKey(Colors.blue), + ), + ), + Marker( + width: 80, + height: 80, + point: const LatLng(53.3498, -6.2603), + builder: (ctx) => const FlutterLogo( + textColor: Colors.green, + key: ObjectKey(Colors.green), + ), + ), + Marker( + width: 80, + height: 80, + point: const LatLng(48.8566, 2.3522), + builder: (ctx) => const FlutterLogo( + textColor: Colors.purple, + key: ObjectKey(Colors.purple), + ), + ), + ], + ), ], ), ), diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index 9ed50b7f2..7e6497576 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -47,7 +47,7 @@ class _LatLngScreenPointTestPageState extends State { onMapEvent: onMapEvent, onTap: (tapPos, latLng) { final pt1 = _mapController.latLngToScreenPoint(latLng); - _textPos = CustomPoint(pt1!.x, pt1.y); + _textPos = CustomPoint(pt1.x, pt1.y); setState(() {}); }, center: const LatLng(51.5, -0.09), diff --git a/example/lib/pages/live_location.dart b/example/lib/pages/live_location.dart deleted file mode 100644 index 92d0ca0f1..000000000 --- a/example/lib/pages/live_location.dart +++ /dev/null @@ -1,182 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; -import 'package:location/location.dart'; - -class LiveLocationPage extends StatefulWidget { - static const String route = '/live_location'; - - const LiveLocationPage({Key? key}) : super(key: key); - - @override - _LiveLocationPageState createState() => _LiveLocationPageState(); -} - -class _LiveLocationPageState extends State { - LocationData? _currentLocation; - late final MapController _mapController; - - bool _liveUpdate = false; - bool _permission = false; - - String? _serviceError = ''; - - int interActiveFlags = InteractiveFlag.all; - - final Location _locationService = Location(); - - @override - void initState() { - super.initState(); - _mapController = MapController(); - initLocationService(); - } - - void initLocationService() async { - await _locationService.changeSettings( - accuracy: LocationAccuracy.high, - interval: 1000, - ); - - LocationData? location; - bool serviceEnabled; - bool serviceRequestResult; - - try { - serviceEnabled = await _locationService.serviceEnabled(); - - if (serviceEnabled) { - final permission = await _locationService.requestPermission(); - _permission = permission == PermissionStatus.granted; - - if (_permission) { - location = await _locationService.getLocation(); - _currentLocation = location; - _locationService.onLocationChanged - .listen((LocationData result) async { - if (mounted) { - setState(() { - _currentLocation = result; - - // If Live Update is enabled, move map center - if (_liveUpdate) { - _mapController.move( - LatLng(_currentLocation!.latitude!, - _currentLocation!.longitude!), - _mapController.zoom); - } - }); - } - }); - } - } else { - serviceRequestResult = await _locationService.requestService(); - if (serviceRequestResult) { - initLocationService(); - return; - } - } - } on PlatformException catch (e) { - debugPrint(e.toString()); - if (e.code == 'PERMISSION_DENIED') { - _serviceError = e.message; - } else if (e.code == 'SERVICE_STATUS_ERROR') { - _serviceError = e.message; - } - location = null; - } - } - - @override - Widget build(BuildContext context) { - LatLng currentLatLng; - - // Until currentLocation is initially updated, Widget can locate to 0, 0 - // by default or store previous location value to show. - if (_currentLocation != null) { - currentLatLng = - LatLng(_currentLocation!.latitude!, _currentLocation!.longitude!); - } else { - currentLatLng = const LatLng(0, 0); - } - - final markers = [ - Marker( - width: 80, - height: 80, - point: currentLatLng, - builder: (ctx) => const FlutterLogo( - textColor: Colors.blue, - key: ObjectKey(Colors.blue), - ), - ), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('Home')), - drawer: buildDrawer(context, LiveLocationPage.route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 8, bottom: 8), - child: _serviceError!.isEmpty - ? Text('This is a map that is showing ' - '(${currentLatLng.latitude}, ${currentLatLng.longitude}).') - : Text( - 'Error occured while acquiring location. Error Message : ' - '$_serviceError'), - ), - Flexible( - child: FlutterMap( - mapController: _mapController, - options: MapOptions( - center: - LatLng(currentLatLng.latitude, currentLatLng.longitude), - zoom: 5, - interactiveFlags: interActiveFlags, - ), - children: [ - TileLayer( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayer(markers: markers), - ], - ), - ), - ], - ), - ), - floatingActionButton: Builder(builder: (BuildContext context) { - return FloatingActionButton( - onPressed: () { - setState(() { - _liveUpdate = !_liveUpdate; - - if (_liveUpdate) { - interActiveFlags = InteractiveFlag.rotate | - InteractiveFlag.pinchZoom | - InteractiveFlag.doubleTapZoom; - - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text( - 'In live update mode only zoom and rotation are enable'), - )); - } else { - interActiveFlags = InteractiveFlag.all; - } - }); - }, - child: _liveUpdate - ? const Icon(Icons.location_on) - : const Icon(Icons.location_off), - ); - }), - ); - } -} diff --git a/example/lib/pages/map_controller.dart b/example/lib/pages/map_controller.dart index 54f336b4d..1aa7086ff 100644 --- a/example/lib/pages/map_controller.dart +++ b/example/lib/pages/map_controller.dart @@ -1,10 +1,7 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_example/widgets/drawer.dart'; import 'package:latlong2/latlong.dart'; -import 'package:location/location.dart'; class MapControllerPage extends StatefulWidget { static const String route = 'map_controller'; @@ -92,7 +89,6 @@ class MapControllerPageState extends State { }, child: const Text('Dublin'), ), - CurrentLocation(mapController: _mapController), ], ), ), @@ -177,75 +173,3 @@ class MapControllerPageState extends State { ); } } - -class CurrentLocation extends StatefulWidget { - const CurrentLocation({ - Key? key, - required this.mapController, - }) : super(key: key); - - final MapController mapController; - - @override - _CurrentLocationState createState() => _CurrentLocationState(); -} - -class _CurrentLocationState extends State { - int _eventKey = 0; - - IconData icon = Icons.gps_not_fixed; - late final StreamSubscription mapEventSubscription; - - @override - void initState() { - super.initState(); - mapEventSubscription = - widget.mapController.mapEventStream.listen(onMapEvent); - } - - @override - void dispose() { - mapEventSubscription.cancel(); - super.dispose(); - } - - void setIcon(IconData newIcon) { - if (newIcon != icon && mounted) { - setState(() { - icon = newIcon; - }); - } - } - - void onMapEvent(MapEvent mapEvent) { - if (mapEvent is MapEventMove && mapEvent.id != _eventKey.toString()) { - setIcon(Icons.gps_not_fixed); - } - } - - void _moveToCurrent() async { - _eventKey++; - final location = Location(); - - try { - final currentLocation = await location.getLocation(); - final moved = widget.mapController.move( - LatLng(currentLocation.latitude!, currentLocation.longitude!), - 18, - id: _eventKey.toString(), - ); - - setIcon(moved ? Icons.gps_fixed : Icons.gps_not_fixed); - } catch (e) { - setIcon(Icons.gps_off); - } - } - - @override - Widget build(BuildContext context) { - return IconButton( - icon: Icon(icon), - onPressed: _moveToCurrent, - ); - } -} diff --git a/example/lib/pages/marker_anchor.dart b/example/lib/pages/marker_anchor.dart deleted file mode 100644 index 5d2c4ce8b..000000000 --- a/example/lib/pages/marker_anchor.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; - -class MarkerAnchorPage extends StatefulWidget { - static const String route = '/marker_anchors'; - - const MarkerAnchorPage({Key? key}) : super(key: key); - - @override - MarkerAnchorPageState createState() { - return MarkerAnchorPageState(); - } -} - -class MarkerAnchorPageState extends State { - late AnchorPos anchorPos; - - @override - void initState() { - super.initState(); - anchorPos = AnchorPos.align(AnchorAlign.center); - } - - void _setAnchorAlignPos(AnchorAlign alignOpt) { - setState(() { - anchorPos = AnchorPos.align(alignOpt); - }); - } - - void _setAnchorExactlyPos(Anchor anchor) { - setState(() { - anchorPos = AnchorPos.exactly(anchor); - }); - } - - @override - Widget build(BuildContext context) { - final markers = [ - Marker( - width: 80, - height: 80, - point: const LatLng(51.5, -0.09), - builder: (ctx) => const FlutterLogo(), - anchorPos: anchorPos, - ), - Marker( - width: 80, - height: 80, - point: const LatLng(53.3498, -6.2603), - builder: (ctx) => const FlutterLogo( - textColor: Colors.green, - ), - anchorPos: anchorPos, - ), - Marker( - width: 80, - height: 80, - point: const LatLng(48.8566, 2.3522), - builder: (ctx) => const FlutterLogo(textColor: Colors.purple), - anchorPos: anchorPos, - ), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('Marker Anchor Points')), - drawer: buildDrawer(context, MarkerAnchorPage.route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 8, bottom: 8), - child: Text( - 'Markers can be anchored to the top, bottom, left or right.'), - ), - Padding( - padding: const EdgeInsets.only(top: 8, bottom: 8), - child: Wrap( - children: [ - MaterialButton( - onPressed: () => _setAnchorAlignPos(AnchorAlign.left), - child: const Text('Left'), - ), - MaterialButton( - onPressed: () => _setAnchorAlignPos(AnchorAlign.right), - child: const Text('Right'), - ), - MaterialButton( - onPressed: () => _setAnchorAlignPos(AnchorAlign.top), - child: const Text('Top'), - ), - MaterialButton( - onPressed: () => _setAnchorAlignPos(AnchorAlign.bottom), - child: const Text('Bottom'), - ), - MaterialButton( - onPressed: () => _setAnchorAlignPos(AnchorAlign.center), - child: const Text('Center'), - ), - MaterialButton( - onPressed: () => _setAnchorExactlyPos(Anchor(80, 80)), - child: const Text('Custom'), - ), - ], - ), - ), - Flexible( - child: FlutterMap( - options: MapOptions( - center: const LatLng(51.5, -0.09), - zoom: 5, - ), - children: [ - TileLayer( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayer(markers: markers), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/example/lib/pages/marker_rotate.dart b/example/lib/pages/marker_rotate.dart deleted file mode 100644 index 7bd458e3a..000000000 --- a/example/lib/pages/marker_rotate.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; - -class MarkerRotatePage extends StatefulWidget { - static const String route = '/marker_rotate'; - - const MarkerRotatePage({Key? key}) : super(key: key); - - @override - MarkerRotatePageState createState() { - return MarkerRotatePageState(); - } -} - -class MarkerRotatePageState extends State { - bool? rotateMarkerLondon; - bool? rotateMarkerDublin; - bool? rotateMarkerParis; - bool rotateMarkerLayerOptions = false; - - @override - void initState() { - super.initState(); - } - - void _setRotateMarkerLayerOptions() { - setState(() { - rotateMarkerLayerOptions = !rotateMarkerLayerOptions; - }); - } - - void _setRotateMarkerLondon() { - setState(() { - if (rotateMarkerLondon == null) { - rotateMarkerLondon = true; - } else if (rotateMarkerLondon != null) { - rotateMarkerLondon = false; - } else { - rotateMarkerLondon = null; - } - }); - } - - void _setRotateMarkerDublin() { - setState(() { - if (rotateMarkerDublin == null) { - rotateMarkerDublin = true; - } else if (rotateMarkerDublin != null) { - rotateMarkerDublin = false; - } else { - rotateMarkerDublin = null; - } - }); - } - - void _setRotateMarkerParis() { - setState(() { - if (rotateMarkerParis == null) { - rotateMarkerParis = true; - } else if (rotateMarkerParis != null) { - rotateMarkerParis = false; - } else { - rotateMarkerParis = null; - } - }); - } - - @override - Widget build(BuildContext context) { - final markers = [ - Marker( - width: 80, - height: 80, - point: const LatLng(51.5, -0.09), - rotate: rotateMarkerLondon, - builder: (ctx) => const FlutterLogo(), - ), - Marker( - width: 80, - height: 80, - point: const LatLng(53.3498, -6.2603), - rotate: rotateMarkerDublin, - builder: (ctx) => const FlutterLogo( - textColor: Colors.green, - ), - ), - Marker( - width: 80, - height: 80, - point: const LatLng(48.8566, 2.3522), - rotate: rotateMarkerParis, - builder: (ctx) => const FlutterLogo(textColor: Colors.purple), - ), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('Marker rotate by Map')), - drawer: buildDrawer(context, MarkerRotatePage.route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 8, bottom: 8), - child: - Text('Markers can be counter rotated to the map rotation.'), - ), - Padding( - padding: const EdgeInsets.only(top: 8, bottom: 8), - child: Wrap( - children: [ - MaterialButton( - onPressed: _setRotateMarkerLayerOptions, - child: - Text('Set by LayerOptions: $rotateMarkerLayerOptions'), - ), - MaterialButton( - onPressed: _setRotateMarkerLondon, - child: Text('Override Marker London: $rotateMarkerLondon'), - ), - MaterialButton( - onPressed: _setRotateMarkerDublin, - child: Text('Override Marker Dublin: $rotateMarkerDublin'), - ), - MaterialButton( - onPressed: _setRotateMarkerParis, - child: Text('Override Marker Paris: $rotateMarkerParis'), - ), - ], - ), - ), - Flexible( - child: FlutterMap( - options: MapOptions( - center: const LatLng(51.5, -0.09), - zoom: 5, - ), - children: [ - TileLayer( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayer( - rotate: rotateMarkerLayerOptions, - markers: markers, - ) - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/example/lib/pages/markers.dart b/example/lib/pages/markers.dart new file mode 100644 index 000000000..df79fedc4 --- /dev/null +++ b/example/lib/pages/markers.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_example/widgets/drawer.dart'; +import 'package:latlong2/latlong.dart'; + +class MarkerPage extends StatefulWidget { + static const String route = '/markers'; + + const MarkerPage({Key? key}) : super(key: key); + + @override + MarkerPageState createState() { + return MarkerPageState(); + } +} + +class MarkerPageState extends State { + final alignments = { + 315: AnchorAlign.topLeft, + 0: AnchorAlign.top, + 45: AnchorAlign.topRight, + 270: AnchorAlign.left, + null: AnchorAlign.center, + 90: AnchorAlign.right, + 225: AnchorAlign.bottomLeft, + 180: AnchorAlign.bottom, + 135: AnchorAlign.bottomRight, + }; + + AnchorAlign anchorAlign = AnchorAlign.top; + bool counterRotate = false; + final customMarkers = []; + + Marker buildPin(LatLng point) => Marker( + point: point, + builder: (ctx) => const Icon(Icons.location_pin, size: 60), + width: 60, + height: 60, + ); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Markers')), + drawer: buildDrawer(context, MarkerPage.route), + body: Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox.square( + dimension: 130, + child: GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + mainAxisSpacing: 5, + crossAxisSpacing: 5, + ), + itemCount: 9, + itemBuilder: (_, index) { + final deg = alignments.keys.elementAt(index); + final align = alignments.values.elementAt(index); + + return IconButton.outlined( + onPressed: () => setState(() => anchorAlign = align), + icon: Transform.rotate( + angle: deg == null ? 0 : deg * pi / 180, + child: Icon( + deg == null ? Icons.circle : Icons.arrow_upward, + color: anchorAlign == align ? Colors.green : null, + size: deg == null ? 16 : null, + ), + ), + ); + }, + ), + ), + const SizedBox(width: 16), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Tap the map to add markers!'), + const SizedBox(height: 10), + Row( + children: [ + const Text('Counter-rotation'), + const SizedBox(width: 10), + Switch.adaptive( + value: counterRotate, + onChanged: (v) => setState(() => counterRotate = v), + ), + ], + ), + ], + ), + ], + ), + ), + Flexible( + child: FlutterMap( + options: MapOptions( + center: const LatLng(51.5, -0.09), + zoom: 5, + onTap: (_, p) => + setState(() => customMarkers.add(buildPin(p))), + interactiveFlags: ~InteractiveFlag.doubleTapZoom, + ), + children: [ + TileLayer( + urlTemplate: + 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), + MarkerLayer( + rotate: counterRotate, + anchorPos: AnchorPos.align(anchorAlign), + markers: [ + buildPin(const LatLng( + 51.51868093513547, -0.12835376940892318)), + buildPin( + const LatLng(53.33360293799854, -6.284001062079881)), + Marker( + point: const LatLng( + 47.18664724067855, -1.5436768515939427), + width: 64, + height: 64, + anchorPos: AnchorPos.align(AnchorAlign.left), + builder: (context) => const ColoredBox( + color: Colors.lightBlue, + child: Align( + alignment: Alignment.centerRight, + child: Text('-->'), + ), + ), + ), + Marker( + point: const LatLng( + 47.18664724067855, -1.5436768515939427), + width: 64, + height: 64, + anchorPos: AnchorPos.align(AnchorAlign.right), + builder: (context) => const ColoredBox( + color: Colors.pink, + child: Align( + alignment: Alignment.centerLeft, + child: Text('<--'), + ), + ), + ), + Marker( + point: const LatLng( + 47.18664724067855, -1.5436768515939427), + rotate: false, + anchorPos: AnchorPos.align(AnchorAlign.center), + builder: (context) => + const ColoredBox(color: Colors.black), + ), + ], + ), + MarkerLayer( + markers: customMarkers, + rotate: counterRotate, + anchorPos: AnchorPos.align(anchorAlign), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/pages/max_bounds.dart b/example/lib/pages/max_bounds.dart deleted file mode 100644 index 0d8153710..000000000 --- a/example/lib/pages/max_bounds.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; - -class MaxBoundsPage extends StatelessWidget { - static const String route = '/max_bounds'; - - const MaxBoundsPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Max Bounds edges check')), - drawer: buildDrawer(context, route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 8, bottom: 8), - child: Text( - 'This is a map that has edges constrained to a latlng bounds.'), - ), - Flexible( - child: FlutterMap( - options: MapOptions( - center: const LatLng(56.704173, 11.543808), - zoom: 3, - maxBounds: LatLngBounds( - const LatLng(-90, -180), - const LatLng(90, 180), - ), - screenSize: MediaQuery.of(context).size, - ), - children: [ - TileLayer( - maxZoom: 15, - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/example/lib/pages/network_tile_provider.dart b/example/lib/pages/network_tile_provider.dart deleted file mode 100644 index 1b5a7d7b9..000000000 --- a/example/lib/pages/network_tile_provider.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; - -class NetworkTileProviderPage extends StatelessWidget { - static const String route = 'NetworkTileProvider'; - - const NetworkTileProviderPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final markers = [ - Marker( - width: 80, - height: 80, - point: const LatLng(51.5, -0.09), - builder: (ctx) => const FlutterLogo( - textColor: Colors.blue, - key: ObjectKey(Colors.blue), - ), - ), - Marker( - width: 80, - height: 80, - point: const LatLng(53.3498, -6.2603), - builder: (ctx) => const FlutterLogo( - textColor: Colors.green, - key: ObjectKey(Colors.green), - ), - ), - Marker( - width: 80, - height: 80, - point: const LatLng(48.8566, 2.3522), - builder: (ctx) => const FlutterLogo( - textColor: Colors.purple, - key: ObjectKey(Colors.purple), - ), - ), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('NetworkTileProvider')), - drawer: buildDrawer(context, route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 8, bottom: 8), - child: Wrap( - children: [ - Text( - 'This provider will automatically retry failed requests, unlike the other pages.'), - Text( - 'For further information, check the documentation website.'), - ], - ), - ), - Flexible( - child: FlutterMap( - options: MapOptions( - center: const LatLng(51.5, -0.09), - zoom: 5, - ), - children: [ - TileLayer( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - tileProvider: NetworkTileProvider(), - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayer(markers: markers) - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/example/lib/pages/on_tap.dart b/example/lib/pages/on_tap.dart deleted file mode 100644 index 9584ec02d..000000000 --- a/example/lib/pages/on_tap.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; - -class OnTapPage extends StatefulWidget { - static const String route = 'on_tap'; - - const OnTapPage({Key? key}) : super(key: key); - - @override - OnTapPageState createState() { - return OnTapPageState(); - } -} - -class OnTapPageState extends State { - static LatLng london = const LatLng(51.5, -0.09); - static LatLng paris = const LatLng(48.8566, 2.3522); - static LatLng dublin = const LatLng(53.3498, -6.2603); - - @override - Widget build(BuildContext context) { - final markers = [ - Marker( - width: 80, - height: 80, - point: london, - builder: (ctx) => GestureDetector( - onTap: () { - ScaffoldMessenger.of(ctx).showSnackBar(const SnackBar( - content: Text('Tapped on blue FlutterLogo Marker'), - )); - }, - child: const FlutterLogo(), - ), - ), - Marker( - width: 80, - height: 80, - point: dublin, - builder: (ctx) => GestureDetector( - onTap: () { - ScaffoldMessenger.of(ctx).showSnackBar(const SnackBar( - content: Text('Tapped on green FlutterLogo Marker'), - )); - }, - child: const FlutterLogo( - textColor: Colors.green, - ), - ), - ), - Marker( - width: 80, - height: 80, - point: paris, - builder: (ctx) => GestureDetector( - onTap: () { - ScaffoldMessenger.of(ctx).showSnackBar(const SnackBar( - content: Text('Tapped on purple FlutterLogo Marker'), - )); - }, - child: const FlutterLogo(textColor: Colors.purple), - ), - ), - ]; - - return Scaffold( - appBar: AppBar(title: const Text('OnTap')), - drawer: buildDrawer(context, OnTapPage.route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 8, bottom: 8), - child: Text('Try tapping on the markers'), - ), - Flexible( - child: FlutterMap( - options: MapOptions( - center: const LatLng(51.5, -0.09), - zoom: 5, - maxZoom: 5, - minZoom: 3, - ), - children: [ - TileLayer( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayer(markers: markers), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/example/lib/pages/polyline.dart b/example/lib/pages/polyline.dart index 2def86729..0fb9ee465 100644 --- a/example/lib/pages/polyline.dart +++ b/example/lib/pages/polyline.dart @@ -13,150 +13,107 @@ class PolylinePage extends StatefulWidget { } class _PolylinePageState extends State { - late Future> polylines; - - Future> getPolylines() async { - final polyLines = [ - Polyline( - points: [ - const LatLng(50.5, -0.09), - const LatLng(51.3498, -6.2603), - const LatLng(53.8566, 2.3522), - ], - strokeWidth: 20, - color: Colors.blue.withOpacity(0.6), - borderStrokeWidth: 20, - borderColor: Colors.red.withOpacity(0.4), - ), - Polyline( - points: [ - const LatLng(50.2, -0.08), - const LatLng(51.2498, -7.2603), - const LatLng(54.8566, 1.3522), - ], - strokeWidth: 20, - color: Colors.black.withOpacity(0.2), - borderStrokeWidth: 20, - borderColor: Colors.white30, - ), - Polyline( - points: [ - const LatLng(49.1, -0.06), - const LatLng(51.15, -7.4), - const LatLng(55.5, 0.8), - ], - strokeWidth: 10, - color: Colors.yellow, - borderStrokeWidth: 10, - borderColor: Colors.blue.withOpacity(0.5), - ), - Polyline( - points: const [ - LatLng(48.1, -0.03), - LatLng(50.5, -7.8), - LatLng(56.5, 0.4), - ], - strokeWidth: 10, - color: Colors.amber, - borderStrokeWidth: 10, - borderColor: Colors.blue.withOpacity(0.5), - ), - ]; - await Future.delayed(const Duration(seconds: 3)); - return polyLines; - } - - @override - void initState() { - polylines = getPolylines(); - super.initState(); - } - @override Widget build(BuildContext context) { - final points = [ - const LatLng(51.5, -0.09), - const LatLng(53.3498, -6.2603), - const LatLng(48.8566, 2.3522), - ]; - - final pointsGradient = [ - const LatLng(55.5, -0.09), - const LatLng(54.3498, -6.2603), - const LatLng(52.8566, 2.3522), - ]; - return Scaffold( - appBar: AppBar(title: const Text('Polylines')), - drawer: buildDrawer(context, PolylinePage.route), - body: Padding( - padding: const EdgeInsets.all(8), - child: FutureBuilder>( - future: polylines, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - debugPrint('snapshot: ${snapshot.hasData}'); - if (snapshot.hasData) { - return Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 8, bottom: 8), - child: Text('Polylines'), - ), - Flexible( - child: FlutterMap( - options: MapOptions( - center: const LatLng(51.5, -0.09), - zoom: 5, - onTap: (tapPosition, point) { - setState(() { - debugPrint('onTap'); - polylines = getPolylines(); - }); - }, - ), - children: [ - TileLayer( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: - 'dev.fleaflet.flutter_map.example', - ), - PolylineLayer( - polylines: [ - Polyline( - points: points, - strokeWidth: 4, - color: Colors.purple), - ], - ), - PolylineLayer( - polylines: [ - Polyline( - points: pointsGradient, - strokeWidth: 4, - gradientColors: [ - const Color(0xffE40203), - const Color(0xffFEED00), - const Color(0xff007E2D), - ], - ), - ], - ), - PolylineLayer( - polylines: snapshot.data!, - polylineCulling: true, - ), + appBar: AppBar(title: const Text('Polylines')), + drawer: buildDrawer(context, PolylinePage.route), + body: Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.only(top: 8, bottom: 8), + child: Text('Polylines'), + ), + Flexible( + child: FlutterMap( + options: MapOptions( + center: const LatLng(51.5, -0.09), + zoom: 5, + ), + children: [ + TileLayer( + urlTemplate: + 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), + PolylineLayer( + polylines: [ + Polyline( + points: [ + const LatLng(51.5, -0.09), + const LatLng(53.3498, -6.2603), + const LatLng(48.8566, 2.3522), ], + strokeWidth: 4, + color: Colors.purple, ), - ), - ], - ); - } - return const Text( - 'Getting map data...\n\nTap on map when complete to refresh map data.'); - }, - ), - )); + Polyline( + points: [ + const LatLng(55.5, -0.09), + const LatLng(54.3498, -6.2603), + const LatLng(52.8566, 2.3522), + ], + strokeWidth: 4, + gradientColors: [ + const Color(0xffE40203), + const Color(0xffFEED00), + const Color(0xff007E2D), + ], + ), + Polyline( + points: [ + const LatLng(50.5, -0.09), + const LatLng(51.3498, 6.2603), + const LatLng(53.8566, 2.3522), + ], + strokeWidth: 20, + color: Colors.blue.withOpacity(0.6), + borderStrokeWidth: 20, + borderColor: Colors.red.withOpacity(0.4), + ), + Polyline( + points: [ + const LatLng(50.2, -0.08), + const LatLng(51.2498, -10.2603), + const LatLng(54.8566, -9.3522), + ], + strokeWidth: 20, + color: Colors.black.withOpacity(0.2), + borderStrokeWidth: 20, + borderColor: Colors.white30, + ), + Polyline( + points: [ + const LatLng(49.1, -0.06), + const LatLng(52.15, -1.4), + const LatLng(55.5, 0.8), + ], + strokeWidth: 10, + color: Colors.yellow, + borderStrokeWidth: 10, + borderColor: Colors.blue.withOpacity(0.5), + ), + Polyline( + points: [ + const LatLng(48.1, -0.03), + const LatLng(50.5, -7.8), + const LatLng(56.5, 0.4), + ], + strokeWidth: 10, + color: Colors.amber, + borderStrokeWidth: 10, + borderColor: Colors.blue.withOpacity(0.5), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); } } diff --git a/example/lib/pages/tap_to_add.dart b/example/lib/pages/tap_to_add.dart deleted file mode 100644 index 37899ecd2..000000000 --- a/example/lib/pages/tap_to_add.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; - -class TapToAddPage extends StatefulWidget { - static const String route = '/tap'; - - const TapToAddPage({Key? key}) : super(key: key); - - @override - State createState() { - return TapToAddPageState(); - } -} - -class TapToAddPageState extends State { - List tappedPoints = []; - - @override - Widget build(BuildContext context) { - final markers = tappedPoints.map((latlng) { - return Marker( - width: 80, - height: 80, - point: latlng, - builder: (ctx) => const FlutterLogo(), - ); - }).toList(); - - return Scaffold( - appBar: AppBar(title: const Text('Tap to add pins')), - drawer: buildDrawer(context, TapToAddPage.route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 8, bottom: 8), - child: Text('Tap to add pins'), - ), - Flexible( - child: FlutterMap( - options: MapOptions( - center: const LatLng(45.5231, -122.6765), - zoom: 13, - onTap: _handleTap), - children: [ - TileLayer( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayer(markers: markers), - ], - ), - ), - ], - ), - ), - ); - } - - void _handleTap(TapPosition tapPosition, LatLng latlng) { - setState(() { - tappedPoints.add(latlng); - }); - } -} diff --git a/example/lib/pages/widgets.dart b/example/lib/pages/widgets.dart deleted file mode 100644 index c03a7ed27..000000000 --- a/example/lib/pages/widgets.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_map/plugin_api.dart'; -import 'package:flutter_map_example/pages/zoombuttons_plugin_option.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; - -class WidgetsPage extends StatelessWidget { - static const String route = 'widgets'; - - const WidgetsPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Widgets')), - drawer: buildDrawer(context, WidgetsPage.route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - Flexible( - child: FlutterMap( - options: MapOptions( - center: const LatLng(51.5, -0.09), - zoom: 5, - ), - nonRotatedChildren: const [ - FlutterMapZoomButtons( - minZoom: 4, - maxZoom: 19, - mini: true, - padding: 10, - alignment: Alignment.bottomLeft, - ), - Text( - 'Plugin is just Text widget', - style: TextStyle( - fontSize: 22, - color: Colors.red, - fontWeight: FontWeight.bold, - backgroundColor: Colors.yellow), - ) - ], - children: [ - TileLayer( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - const MovingWithoutRefreshAllMapMarkers(), - ], - ), - ), - ], - ), - ), - ); - } -} - -class MovingWithoutRefreshAllMapMarkers extends StatefulWidget { - const MovingWithoutRefreshAllMapMarkers({Key? key}) : super(key: key); - - @override - State createState() => - _MovingWithoutRefreshAllMapMarkersState(); -} - -class _MovingWithoutRefreshAllMapMarkersState - extends State { - Marker? _marker; - Timer? _timer; - int _markerIndex = 0; - - @override - void initState() { - super.initState(); - _marker = _markers[_markerIndex]; - _timer = Timer.periodic(const Duration(seconds: 1), (_) { - setState(() { - _marker = _markers[_markerIndex]; - _markerIndex = (_markerIndex + 1) % _markers.length; - }); - }); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return MarkerLayer( - markers: [_marker!], - ); - } -} - -List _markers = [ - Marker( - width: 80, - height: 80, - point: const LatLng(51.5, -0.09), - builder: (ctx) => const FlutterLogo(), - ), - Marker( - width: 80, - height: 80, - point: const LatLng(53.3498, -6.2603), - builder: (ctx) => const FlutterLogo(), - ), - Marker( - width: 80, - height: 80, - point: const LatLng(48.8566, 2.3522), - builder: (ctx) => const FlutterLogo(), - ), -]; diff --git a/example/lib/test_app.dart b/example/lib/test_app.dart deleted file mode 100644 index e3c436100..000000000 --- a/example/lib/test_app.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:latlong2/latlong.dart'; - -void main() { - runApp(const TestApp()); -} - -class TestApp extends StatefulWidget { - const TestApp({Key? key}) : super(key: key); - - @override - _TestAppState createState() => _TestAppState(); -} - -class _TestAppState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - body: Center( - child: SizedBox( - width: 200, - height: 200, - child: FlutterMap( - options: MapOptions( - center: const LatLng(45.5231, -122.6765), - zoom: 13, - ), - children: [ - TileLayer( - urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/example/lib/widgets/drawer.dart b/example/lib/widgets/drawer.dart index 35eb43efa..63527f99d 100644 --- a/example/lib/widgets/drawer.dart +++ b/example/lib/widgets/drawer.dart @@ -6,21 +6,15 @@ import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/epsg3413_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/fallback_url_network_page.dart'; -import 'package:flutter_map_example/pages/fallback_url_offline_page.dart'; import 'package:flutter_map_example/pages/home.dart'; import 'package:flutter_map_example/pages/interactive_test_page.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; -import 'package:flutter_map_example/pages/live_location.dart'; import 'package:flutter_map_example/pages/many_markers.dart'; import 'package:flutter_map_example/pages/map_controller.dart'; import 'package:flutter_map_example/pages/map_inside_listview.dart'; -import 'package:flutter_map_example/pages/marker_anchor.dart'; -import 'package:flutter_map_example/pages/marker_rotate.dart'; -import 'package:flutter_map_example/pages/max_bounds.dart'; +import 'package:flutter_map_example/pages/markers.dart'; import 'package:flutter_map_example/pages/moving_markers.dart'; -import 'package:flutter_map_example/pages/network_tile_provider.dart'; import 'package:flutter_map_example/pages/offline_map.dart'; -import 'package:flutter_map_example/pages/on_tap.dart'; import 'package:flutter_map_example/pages/overlay_image.dart'; import 'package:flutter_map_example/pages/plugin_scalebar.dart'; import 'package:flutter_map_example/pages/plugin_zoombuttons.dart'; @@ -31,18 +25,22 @@ import 'package:flutter_map_example/pages/reset_tile_layer.dart'; import 'package:flutter_map_example/pages/secondary_tap.dart'; import 'package:flutter_map_example/pages/sliding_map.dart'; import 'package:flutter_map_example/pages/stateful_markers.dart'; -import 'package:flutter_map_example/pages/tap_to_add.dart'; import 'package:flutter_map_example/pages/tile_builder_example.dart'; import 'package:flutter_map_example/pages/tile_loading_error_handle.dart'; -import 'package:flutter_map_example/pages/widgets.dart'; import 'package:flutter_map_example/pages/wms_tile_layer.dart'; Widget _buildMenuItem( - BuildContext context, Widget title, String routeName, String currentRoute) { + BuildContext context, + Widget title, + String routeName, + String currentRoute, { + Widget? icon, +}) { final isSelected = routeName == currentRoute; return ListTile( title: title, + leading: icon, selected: isSelected, onTap: () { if (isSelected) { @@ -81,210 +79,182 @@ Drawer buildDrawer(BuildContext context, String currentRoute) { ), _buildMenuItem( context, - const Text('OpenStreetMap'), + const Text('Home'), HomePage.route, currentRoute, + icon: const Icon(Icons.home), ), + const Divider(), _buildMenuItem( context, - const Text('NetworkTileProvider'), - NetworkTileProviderPage.route, + const Text('Marker Layer'), + MarkerPage.route, currentRoute, ), _buildMenuItem( context, - const Text('WMS Layer'), - WMSLayerPage.route, - currentRoute, - ), - _buildMenuItem( - context, - const Text('Custom CRS'), - CustomCrsPage.route, + const Text('Polygon Layer'), + PolygonPage.route, currentRoute, ), _buildMenuItem( context, - const Text('Tap to Add Pins'), - TapToAddPage.route, + const Text('Polyline Layer'), + PolylinePage.route, currentRoute, ), _buildMenuItem( context, - const Text('Polylines'), - PolylinePage.route, + const Text('Circle Layer'), + CirclePage.route, currentRoute, ), _buildMenuItem( context, - const Text('Polygons'), - PolygonPage.route, + const Text('Overlay Image Layer'), + OverlayImagePage.route, currentRoute, ), + const Divider(), _buildMenuItem( context, - const Text('MapController'), + const Text('Map Controller'), MapControllerPage.route, currentRoute, ), _buildMenuItem( context, - const Text('Animated MapController'), + const Text('Animated Map Controller'), AnimatedMapControllerPage.route, currentRoute, ), _buildMenuItem( context, - const Text('Marker Anchors'), - MarkerAnchorPage.route, + const Text('Interactive Flags'), + InteractiveTestPage.route, currentRoute, ), + const Divider(), _buildMenuItem( context, - const Text('Marker Rotate'), - MarkerRotatePage.route, + const Text('WMS Sourced Map'), + WMSLayerPage.route, currentRoute, ), _buildMenuItem( context, - const Text('ScaleBar Plugins'), - PluginScaleBar.route, + const Text('Asset Sourced Map'), + OfflineMapPage.route, currentRoute, ), _buildMenuItem( context, - const Text('ZoomButtons Plugins'), - PluginZoomButtons.route, + const Text('Fallback URL'), + FallbackUrlNetworkPage.route, currentRoute, ), + const Divider(), _buildMenuItem( context, - const Text('Offline Map'), - OfflineMapPage.route, + const Text('Stateful Markers'), + StatefulMarkersPage.route, currentRoute, ), _buildMenuItem( context, - const Text('OnTap'), - OnTapPage.route, + const Text('Many Markers'), + ManyMarkersPage.route, currentRoute, ), _buildMenuItem( context, - const Text('Moving Markers'), + const Text('Moving Marker'), MovingMarkersPage.route, currentRoute, ), + const Divider(), _buildMenuItem( context, - const Text('Circle'), - CirclePage.route, + const Text('ScaleBar Plugins'), + PluginScaleBar.route, currentRoute, ), _buildMenuItem( context, - const Text('Overlay Image'), - OverlayImagePage.route, + const Text('ZoomButtons Plugins'), + PluginZoomButtons.route, currentRoute, ), + const Divider(), _buildMenuItem( context, - const Text('Sliding Map'), - SlidingMapPage.route, + const Text('Custom CRS'), + CustomCrsPage.route, currentRoute, ), - _buildMenuItem( - context, - const Text('Widgets'), - WidgetsPage.route, - currentRoute, + ListTile( + title: const Text('EPSG4326 CRS'), + selected: currentRoute == EPSG4326Page.route, + onTap: () { + Navigator.pushReplacementNamed(context, EPSG4326Page.route); + }, ), - _buildMenuItem( - context, - const Text('Live Location Update'), - LiveLocationPage.route, - currentRoute, + ListTile( + title: const Text('EPSG3413 CRS'), + selected: currentRoute == EPSG3413Page.route, + onTap: () { + Navigator.pushReplacementNamed(context, EPSG3413Page.route); + }, ), + const Divider(), _buildMenuItem( context, - const Text('Tile loading error handle'), - TileLoadingErrorHandle.route, + const Text('Sliding Map'), + SlidingMapPage.route, currentRoute, ), _buildMenuItem( context, - const Text('Tile builder'), - TileBuilderPage.route, + const Text('Map Inside Scrollable'), + MapInsideListViewPage.route, currentRoute, ), _buildMenuItem( context, - const Text('Interactive flags test page'), - InteractiveTestPage.route, + const Text('Secondary Tap'), + SecondaryTapPage.route, currentRoute, ), + const Divider(), _buildMenuItem( context, - const Text('Max Bounds test page'), - MaxBoundsPage.route, + const Text('Custom Tile Error Handling'), + TileLoadingErrorHandle.route, currentRoute, ), - ListTile( - title: const Text('A lot of markers'), - selected: currentRoute == ManyMarkersPage.route, - onTap: () { - Navigator.pushReplacementNamed(context, ManyMarkersPage.route); - }, - ), - ListTile( - title: const Text('Reset Tile Layer'), - selected: currentRoute == ResetTileLayerPage.route, - onTap: () { - Navigator.pushReplacementNamed(context, ResetTileLayerPage.route); - }, - ), - ListTile( - title: const Text('EPSG4326 CRS'), - selected: currentRoute == EPSG4326Page.route, - onTap: () { - Navigator.pushReplacementNamed(context, EPSG4326Page.route); - }, - ), - ListTile( - title: const Text('EPSG3413 CRS'), - selected: currentRoute == EPSG3413Page.route, - onTap: () { - Navigator.pushReplacementNamed(context, EPSG3413Page.route); - }, - ), _buildMenuItem( context, - const Text('Stateful markers'), - StatefulMarkersPage.route, + const Text('Custom Tile Builder'), + TileBuilderPage.route, currentRoute, ), - _buildMenuItem(context, const Text('Map inside listview'), - MapInsideListViewPage.route, currentRoute), - _buildMenuItem(context, const Text('Point to LatLng'), - PointToLatLngPage.route, currentRoute), - _buildMenuItem(context, const Text('LatLng to ScreenPoint'), - LatLngScreenPointTestPage.route, currentRoute), _buildMenuItem( context, - const Text('Fallback URL NetworkTileProvider'), - FallbackUrlNetworkPage.route, + const Text('Reset Tile Layer'), + ResetTileLayerPage.route, currentRoute, ), + const Divider(), _buildMenuItem( context, - const Text('Fallback URL AssetTileProvider'), - FallbackUrlOfflinePage.route, + const Text('Screen Point -> LatLng'), + PointToLatLngPage.route, currentRoute, ), _buildMenuItem( context, - const Text('Secondary Tap'), - SecondaryTapPage.route, + const Text('LatLng -> Screen Point'), + LatLngScreenPointTestPage.route, currentRoute, ), ], diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index d151654e4..057e8f8a6 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,9 +6,11 @@ import FlutterMacOS import Foundation import location +import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/example/macos/Podfile b/example/macos/Podfile index dade8dfad..049abe295 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.11' +platform :osx, '10.14' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index a385f0135..6d885cf66 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -2,21 +2,34 @@ PODS: - FlutterMacOS (1.0.0) - location (0.0.1): - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - location (from `Flutter/ephemeral/.symlinks/plugins/location/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral location: :path: Flutter/ephemeral/.symlinks/plugins/location/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 location: 7cdb0665bd6577d382b0a343acdadbcb7f964775 + shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c + url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 -PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c +PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.0 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 413b4413e..6aaa0831a 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -278,6 +278,7 @@ }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -404,7 +405,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -483,7 +484,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -530,7 +531,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/example/macos/Runner/Release.entitlements b/example/macos/Runner/Release.entitlements index 852fa1a47..779a1789c 100644 --- a/example/macos/Runner/Release.entitlements +++ b/example/macos/Runner/Release.entitlements @@ -4,5 +4,7 @@ com.apple.security.app-sandbox + com.apple.security.network.client + diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ab0e43b79..2e50967ea 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -12,10 +12,11 @@ dependencies: sdk: flutter flutter_map: path: ../ - location: ^4.4.0 latlong2: ^0.9.0 proj4dart: ^2.1.0 url_launcher: ^6.1.10 + shared_preferences: ^2.1.1 + url_strategy: ^0.2.0 dev_dependencies: flutter_lints: ^2.0.1 diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 671f78011..038466ee6 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -1,25 +1,12 @@ library flutter_map; -import 'dart:async'; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/core/center_zoom.dart'; -import 'package:flutter_map/src/core/point.dart'; -import 'package:flutter_map/src/core/positioned_tap_detector_2.dart'; -import 'package:flutter_map/src/geo/crs/crs.dart'; -import 'package:flutter_map/src/geo/latlng_bounds.dart'; -import 'package:flutter_map/src/gestures/interactive_flag.dart'; -import 'package:flutter_map/src/gestures/map_events.dart'; -import 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; -import 'package:flutter_map/src/map/map.dart'; -import 'package:latlong2/latlong.dart'; - -export 'package:flutter_map/src/core/center_zoom.dart'; -export 'package:flutter_map/src/core/point.dart'; -export 'package:flutter_map/src/core/positioned_tap_detector_2.dart'; -export 'package:flutter_map/src/geo/crs/crs.dart'; +export 'package:flutter_map/src/misc/center_zoom.dart'; +export 'package:flutter_map/src/misc/fit_bounds_options.dart'; +export 'package:flutter_map/src/misc/move_and_rotate_result.dart'; +export 'package:flutter_map/src/misc/point.dart'; +export 'package:flutter_map/src/misc/position.dart'; +export 'package:flutter_map/src/misc/private/positioned_tap_detector_2.dart'; +export 'package:flutter_map/src/geo/crs.dart'; export 'package:flutter_map/src/geo/latlng_bounds.dart'; export 'package:flutter_map/src/gestures/interactive_flag.dart'; export 'package:flutter_map/src/gestures/map_events.dart'; @@ -45,344 +32,6 @@ export 'package:flutter_map/src/layer/tile_layer/tile_provider/file_providers/ti export 'package:flutter_map/src/layer/tile_layer/tile_provider/network_tile_provider.dart'; export 'package:flutter_map/src/layer/tile_layer/tile_update_event.dart'; export 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart'; - -/// Renders a map composed of a list of layers powered by [LayerOptions]. -/// -/// Use a [MapController] to interact programmatically with the map. -/// -/// Through [MapOptions] map's callbacks and properties can be defined. -class FlutterMap extends StatefulWidget { - /// A set of layers' widgets to used to create the layers on the map. - final List children; - - /// These layers won't be rotated. - /// - /// These layers will render above [layers] - final List nonRotatedChildren; - - /// [MapOptions] to create a [MapState] with. - /// - /// This property must not be null. - final MapOptions options; - - /// A [MapController], used to control the map. - final MapController? mapController; - - const FlutterMap({ - super.key, - required this.options, - this.children = const [], - this.nonRotatedChildren = const [], - this.mapController, - }); - - @override - FlutterMapState createState() => FlutterMapState(); -} - -/// Controller to programmatically interact with [FlutterMap]. -/// -/// It allows for map movement through [move], rotation through [rotate] -/// and to fit the map bounds with [fitBounds]. -/// -/// It also provides current map properties. -abstract class MapController { - /// Moves the map to a specific location and zoom level - /// - /// Optionally provide [id] attribute and if you listen to [mapEventCallback] - /// later a [MapEventMove] event will be emitted (if move was success) with - /// same [id] attribute. Event's source attribute will be - /// [MapEventSource.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}); - - /// Sets the map rotation to a certain degrees angle (in decimal). - /// - /// Optionally provide [id] attribute and if you listen to [mapEventCallback] - /// later a [MapEventRotate] event will be emitted (if rotate was success) - /// with same [id] attribute. Event's source attribute will be - /// [MapEventSource.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}); - - /// Calls [move] and [rotate] together however layers will rebuild just once - /// instead of twice - MoveAndRotateResult moveAndRotate(LatLng center, double zoom, double degree, - {String? id}); - - /// Fits the map bounds. Optional constraints can be defined - /// through the [options] parameter. - void fitBounds(LatLngBounds bounds, {FitBoundsOptions? options}); - - /// Calcs the new center and zoom for the map bounds. Optional constraints can be defined - /// through the [options] parameter. - CenterZoom centerZoomFitBounds(LatLngBounds bounds, - {FitBoundsOptions? options}); - - LatLng get center; - - LatLngBounds? get bounds; - - double get zoom; - - double get rotation; - - set state(FlutterMapState state); - - Stream get mapEventStream; - - void dispose(); - - StreamSink get mapEventSink; - - LatLng? pointToLatLng(CustomPoint point); - - CustomPoint? latLngToScreenPoint(LatLng latLng); - - factory MapController() => MapControllerImpl(); -} - -typedef TapCallback = void Function(TapPosition tapPosition, LatLng point); -typedef LongPressCallback = void Function( - TapPosition tapPosition, - LatLng point, -); -typedef PointerDownCallback = void Function( - PointerDownEvent event, - LatLng point, -); -typedef PointerUpCallback = void Function(PointerUpEvent event, LatLng point); -typedef PointerCancelCallback = void Function( - PointerCancelEvent event, - LatLng point, -); -typedef PointerHoverCallback = void Function( - PointerHoverEvent event, - LatLng point, -); -typedef PositionCallback = void Function(MapPosition position, bool hasGesture); -typedef MapEventCallback = void Function(MapEvent); - -/// Allows you to provide your map's starting properties for [zoom], [rotation] -/// and [center]. Alternatively you can provide [bounds] instead of [center]. -/// If both, [center] and [bounds] are provided, bounds will take preference -/// over [center]. -/// Zoom, pan boundary and interactivity constraints can be specified here too. -/// -/// Callbacks for [onTap], [onSecondaryTap], [onLongPress] and -/// [onPositionChanged] can be registered here. -/// -/// Through [crs] the Coordinate Reference System can be -/// defined, it defaults to [Epsg3857]. -/// -/// Checks if a coordinate is outside of the map's -/// defined boundaries. -/// -/// If you download offline tiles dynamically, you can set [adaptiveBoundaries] -/// to true (make sure to pass [screenSize] and an external [controller]), which -/// will enforce panning/zooming to ensure there is never a need to display -/// tiles outside the boundaries set by [swPanBoundary] and [nePanBoundary]. -class MapOptions { - final Crs crs; - final double zoom; - final double rotation; - - /// Prints multi finger gesture winner Helps to fine adjust - /// [rotationThreshold] and [pinchZoomThreshold] and [pinchMoveThreshold] - /// Note: only takes effect if [enableMultiFingerGestureRace] is true - final bool debugMultiFingerGestureWinner; - - /// If true then [rotationThreshold] and [pinchZoomThreshold] and - /// [pinchMoveThreshold] will race If multiple gestures win at the same time - /// then precedence: [pinchZoomWinGestures] > [rotationWinGestures] > - /// [pinchMoveWinGestures] - final bool enableMultiFingerGestureRace; - - /// Rotation threshold in degree default is 20.0 Map starts to rotate when - /// [rotationThreshold] has been achieved or another multi finger gesture wins - /// which allows [MultiFingerGesture.rotate] Note: if [interactiveFlags] - /// doesn't contain [InteractiveFlag.rotate] or [enableMultiFingerGestureRace] - /// is false then rotate cannot win - final double rotationThreshold; - - /// When [rotationThreshold] wins over [pinchZoomThreshold] and - /// [pinchMoveThreshold] then [rotationWinGestures] gestures will be used. By - /// default only [MultiFingerGesture.rotate] gesture will take effect see - /// [MultiFingerGesture] for custom settings - final int rotationWinGestures; - - /// Pinch Zoom threshold default is 0.5 Map starts to zoom when - /// [pinchZoomThreshold] has been achieved or another multi finger gesture - /// wins which allows [MultiFingerGesture.pinchZoom] Note: if - /// [interactiveFlags] doesn't contain [InteractiveFlag.pinchZoom] or - /// [enableMultiFingerGestureRace] is false then zoom cannot win - final double pinchZoomThreshold; - - /// When [pinchZoomThreshold] wins over [rotationThreshold] and - /// [pinchMoveThreshold] then [pinchZoomWinGestures] gestures will be used. By - /// default [MultiFingerGesture.pinchZoom] and [MultiFingerGesture.pinchMove] - /// gestures will take effect see [MultiFingerGesture] for custom settings - final int pinchZoomWinGestures; - - /// Pinch Move threshold default is 40.0 (note: this doesn't take any effect - /// on drag) Map starts to move when [pinchMoveThreshold] has been achieved or - /// another multi finger gesture wins which allows - /// [MultiFingerGesture.pinchMove] Note: if [interactiveFlags] doesn't contain - /// [InteractiveFlag.pinchMove] or [enableMultiFingerGestureRace] is false - /// then pinch move cannot win - final double pinchMoveThreshold; - - /// When [pinchMoveThreshold] wins over [rotationThreshold] and - /// [pinchZoomThreshold] then [pinchMoveWinGestures] gestures will be used. By - /// default [MultiFingerGesture.pinchMove] and [MultiFingerGesture.pinchZoom] - /// gestures will take effect see [MultiFingerGesture] for custom settings - final int pinchMoveWinGestures; - - /// If true then the map will scroll when the user uses the scroll wheel on - /// his mouse. This is supported on web and desktop, but might also work well - /// on Android. A [Listener] is used to capture the onPointerSignal events. - final bool enableScrollWheel; - final double scrollWheelVelocity; - - final double? minZoom; - final double? maxZoom; - - /// see [InteractiveFlag] for custom settings - final int interactiveFlags; - - final TapCallback? onTap; - final TapCallback? onSecondaryTap; - final LongPressCallback? onLongPress; - final PointerDownCallback? onPointerDown; - final PointerUpCallback? onPointerUp; - final PointerCancelCallback? onPointerCancel; - final PointerHoverCallback? onPointerHover; - final PositionCallback? onPositionChanged; - final MapEventCallback? onMapEvent; - final bool slideOnBoundaries; - final Size? screenSize; - final bool adaptiveBoundaries; - final LatLng center; - final LatLngBounds? bounds; - final FitBoundsOptions boundsOptions; - final LatLng? swPanBoundary; - final LatLng? nePanBoundary; - - /// OnMapReady is called after the map runs it's initState. - /// At that point the map has assigned its state to the controller - /// Only use this if your map isn't built immediately (like inside FutureBuilder) - /// and you need to access the controller as soon as the map is built. - /// Otherwise you can use WidgetsBinding.instance.addPostFrameCallback - /// In initState to controll the map before the next frame - final void Function()? onMapReady; - - /// Restrict outer edges of map to LatLng Bounds, to prevent gray areas when - /// panning or zooming. LatLngBounds(LatLng(-90, -180.0), LatLng(90.0, 180.0)) - /// would represent the full extent of the map, so no gray area outside of it. - final LatLngBounds? maxBounds; - - /// Flag to enable the built in keep alive functionality - /// - /// If the map is within a complex layout, such as a [ListView] or [PageView], - /// the map will reset to it's inital position after it appears back into view. - /// To ensure this doesn't happen, enable this flag to prevent the [FlutterMap] - /// widget from rebuilding. - final bool keepAlive; - - MapOptions({ - this.crs = const Epsg3857(), - LatLng? center, - this.bounds, - this.boundsOptions = const FitBoundsOptions(), - this.zoom = 13.0, - this.rotation = 0.0, - this.debugMultiFingerGestureWinner = false, - this.enableMultiFingerGestureRace = false, - this.rotationThreshold = 20.0, - this.rotationWinGestures = MultiFingerGesture.rotate, - this.pinchZoomThreshold = 0.5, - this.pinchZoomWinGestures = - MultiFingerGesture.pinchZoom | MultiFingerGesture.pinchMove, - this.pinchMoveThreshold = 40.0, - this.pinchMoveWinGestures = - MultiFingerGesture.pinchZoom | MultiFingerGesture.pinchMove, - this.enableScrollWheel = true, - this.scrollWheelVelocity = 0.005, - this.minZoom, - this.maxZoom, - this.interactiveFlags = InteractiveFlag.all, - this.onTap, - this.onSecondaryTap, - this.onLongPress, - this.onPointerDown, - this.onPointerUp, - this.onPointerCancel, - this.onPointerHover, - this.onPositionChanged, - this.onMapEvent, - this.onMapReady, - this.slideOnBoundaries = false, - this.adaptiveBoundaries = false, - this.screenSize, - this.swPanBoundary, - this.nePanBoundary, - this.maxBounds, - this.keepAlive = false, - }) : center = center ?? const LatLng(50.5, 30.51), - assert(rotationThreshold >= 0.0), - assert(pinchZoomThreshold >= 0.0), - assert(pinchMoveThreshold >= 0.0) { - assert(!adaptiveBoundaries || screenSize != null, - 'screenSize must be set in order to enable adaptive boundaries.'); - } -} - -class FitBoundsOptions { - final EdgeInsets padding; - final double maxZoom; - final bool inside; - - /// By default calculations will return fractional zoom levels. - /// If this parameter is set to [true] fractional zoom levels will be round - /// to the next suitable integer. - final bool forceIntegerZoomLevel; - - const FitBoundsOptions({ - this.padding = EdgeInsets.zero, - this.maxZoom = 17.0, - this.inside = false, - this.forceIntegerZoomLevel = false, - }); -} - -/// Position's type for [PositionCallback]. -class MapPosition { - final LatLng? center; - final LatLngBounds? bounds; - final double? zoom; - final bool hasGesture; - - MapPosition({this.center, this.bounds, this.zoom, this.hasGesture = false}); - - @override - int get hashCode => center.hashCode + bounds.hashCode + zoom.hashCode; - - @override - bool operator ==(Object other) => - other is MapPosition && - other.center == center && - other.bounds == bounds && - other.zoom == zoom; -} - -class MoveAndRotateResult { - final bool moveSuccess; - final bool rotateSuccess; - - MoveAndRotateResult(this.moveSuccess, this.rotateSuccess); -} +export 'package:flutter_map/src/map/controller.dart' hide MapControllerImpl; +export 'package:flutter_map/src/map/widget.dart'; +export 'package:flutter_map/src/map/options.dart'; diff --git a/lib/plugin_api.dart b/lib/plugin_api.dart index c006a5d58..cecfbbb40 100644 --- a/lib/plugin_api.dart +++ b/lib/plugin_api.dart @@ -1,7 +1,9 @@ library flutter_map.plugin_api; export 'package:flutter_map/flutter_map.dart'; -export 'package:flutter_map/src/map/flutter_map_state.dart'; -export 'package:flutter_map/src/core/bounds.dart'; -export 'package:flutter_map/src/core/center_zoom.dart'; -export 'package:flutter_map/src/map/map.dart'; +export 'package:flutter_map/src/map/state.dart'; +export 'package:flutter_map/src/misc/private/bounds.dart'; +export 'package:flutter_map/src/misc/private/positioned_tap_detector_2.dart'; +export 'package:flutter_map/src/misc/private/util.dart'; +// ignore: invalid_export_of_internal_element +export 'package:flutter_map/src/map/controller.dart' show MapControllerImpl; diff --git a/lib/src/geo/crs/crs.dart b/lib/src/geo/crs.dart similarity index 92% rename from lib/src/geo/crs/crs.dart rename to lib/src/geo/crs.dart index 4aec56c30..df4aee993 100644 --- a/lib/src/geo/crs/crs.dart +++ b/lib/src/geo/crs.dart @@ -1,7 +1,7 @@ import 'dart:math' as math; -import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; +import 'package:flutter_map/src/misc/point.dart'; import 'package:latlong2/latlong.dart'; import 'package:meta/meta.dart'; import 'package:proj4dart/proj4dart.dart' as proj4; @@ -12,14 +12,14 @@ import 'package:proj4dart/proj4dart.dart' as proj4; /// The main objective of a CRS is to handle the conversion between surface /// points of objects of different dimensions. In our case 3D and 2D objects. abstract class Crs { + const Crs(); + String get code; Projection get projection; Transformation get transformation; - const Crs(); - /// Converts a point on the sphere surface (with a certain zoom) in a /// map point. CustomPoint latLngToPoint(LatLng latlng, double zoom) { @@ -33,26 +33,14 @@ abstract class Crs { } /// Converts a map point to the sphere coordinate (at a certain zoom). - LatLng? pointToLatLng(CustomPoint point, double zoom) { - final scale = this.scale(zoom); - final untransformedPoint = - transformation.untransform(point, scale.toDouble()); - try { - return projection.unproject(untransformedPoint); - } catch (e) { - return null; - } - } + LatLng pointToLatLng(CustomPoint point, double zoom) => projection + .unproject(transformation.untransform(point, scale(zoom).toDouble())); /// Zoom to Scale function. - double scale(double zoom) { - return 256.0 * math.pow(2, zoom); - } + double scale(double zoom) => 256.0 * math.pow(2, zoom); /// Scale to Zoom function. - double zoom(double scale) { - return math.log(scale / 256) / math.ln2; - } + double zoom(double scale) => math.log(scale / 256) / math.ln2; /// Rescales the bounds to a given zoom value. Bounds? getProjectedBounds(double zoom) { @@ -249,18 +237,9 @@ class Proj4Crs extends Crs { /// Converts a map point to the sphere coordinate (at a certain zoom). @override - LatLng? pointToLatLng(CustomPoint point, double zoom) { - final scale = this.scale(zoom); - final transformation = _getTransformationByZoom(zoom); - - final untransformedPoint = - transformation.untransform(point, scale.toDouble()); - try { - return projection.unproject(untransformedPoint); - } catch (e) { - return null; - } - } + LatLng pointToLatLng(CustomPoint point, double zoom) => + projection.unproject(_getTransformationByZoom(zoom) + .untransform(point, scale(zoom).toDouble())); /// Rescales the bounds to a given zoom value. @override diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 2033dfd8a..2fcf4db17 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -5,7 +5,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; +import 'package:flutter_map/src/map/state.dart'; import 'package:latlong2/latlong.dart'; abstract class MapGestureMixin extends State diff --git a/lib/src/gestures/map_events.dart b/lib/src/gestures/map_events.dart index 6683a33ba..4499b2cc1 100644 --- a/lib/src/gestures/map_events.dart +++ b/lib/src/gestures/map_events.dart @@ -1,4 +1,4 @@ -import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/misc/point.dart'; import 'package:latlong2/latlong.dart'; /// Event sources which are used to identify different types of diff --git a/lib/src/layer/attribution_layer/rich.dart b/lib/src/layer/attribution_layer/rich.dart index 29ef73b9f..4856be294 100644 --- a/lib/src/layer/attribution_layer/rich.dart +++ b/lib/src/layer/attribution_layer/rich.dart @@ -165,7 +165,11 @@ class RichAttributionWidgetState extends State { if (popupExpanded) { Future.delayed( widget.popupInitialDisplayDuration, - () => setState(() => popupExpanded = false), + () { + if (mounted) { + setState(() => popupExpanded = false); + } + }, ); } diff --git a/lib/src/layer/circle_layer.dart b/lib/src/layer/circle_layer.dart index a8b736c18..0bb4f2e38 100644 --- a/lib/src/layer/circle_layer.dart +++ b/lib/src/layer/circle_layer.dart @@ -1,5 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; +import 'package:flutter_map/src/map/state.dart'; import 'package:latlong2/latlong.dart' hide Path; class CircleMarker { diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index af09e4a32..9f0314c9e 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -1,75 +1,107 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; +import 'package:flutter_map/src/map/state.dart'; import 'package:latlong2/latlong.dart'; +/// Defines the positioning of a [Marker.builder] widget relative to the center +/// of its bounding box defined by its [Marker.height] & [Marker.width] +/// +/// Can be defined exactly (using [AnchorPos.exactly] with an [Anchor]) or in +/// a relative alignment (using [AnchorPos.align] with an [AnchorAlign]). +class AnchorPos { + final Anchor? anchor; + final AnchorAlign? alignment; + + AnchorPos.exactly(Anchor this.anchor) : alignment = null; + AnchorPos.align(AnchorAlign this.alignment) : anchor = null; +} + +/// Exact alignment for a [Marker.builder] widget relative to the center +/// of its bounding box defined by its [Marker.height] & [Marker.width] +/// +/// May be generated from an [AnchorPos] (usually with [AnchorPos.alignment] +/// defined) and dimensions through [Anchor.fromPos]. class Anchor { final double left; final double top; Anchor(this.left, this.top); - Anchor._(double width, double height, AnchorAlign alignOpt) - : left = _leftOffset(width, alignOpt), - top = _topOffset(height, alignOpt); - - static double _leftOffset(double width, AnchorAlign alignOpt) => - switch (alignOpt) { - AnchorAlign.left => 0, - AnchorAlign.right => width, - _ => width / 2, - }; - - static double _topOffset(double height, AnchorAlign alignOpt) => - switch (alignOpt) { - AnchorAlign.top => 0, - AnchorAlign.bottom => height, - _ => height / 2, - }; - - factory Anchor.forPos(AnchorPos? pos, double width, double height) { - if (pos == null) return Anchor._(width, height, AnchorAlign.none); - if (pos.alignment case final align?) return Anchor._(width, height, align); + factory Anchor.fromPos(AnchorPos pos, double width, double height) { if (pos.anchor case final anchor?) return anchor; + if (pos.alignment case final alignment?) { + return Anchor( + switch (alignment._x) { -1 => 0, 1 => width, _ => width / 2 }, + switch (alignment._y) { 1 => 0, -1 => height, _ => height / 2 }, + ); + } throw Exception(); } } -class AnchorPos { - final Anchor? anchor; - final AnchorAlign? alignment; +/// Relative alignment for a [Marker.builder] widget relative to the center +/// of its bounding box defined by its [Marker.height] & [Marker.width] +enum AnchorAlign { + topLeft(-1, 1), + topRight(1, 1), + bottomLeft(-1, -1), + bottomRight(1, -1), - AnchorPos.exactly(this.anchor) : alignment = null; - AnchorPos.align(this.alignment) : anchor = null; -} + center(0, 0), -enum AnchorAlign { - none, - left, - right, - top, - bottom, - center, + /// Top center + top(0, 1), + + /// Bottom center + bottom(0, -1), + + /// Left center + left(-1, 0), + + /// Right center + right(1, 0), + + @Deprecated( + 'Prefer `center`. ' + 'This value is equivalent to the `center` alignment. ' + 'If you notice a difference in behaviour, please open a bug report on GitHub. ' + 'This feature is deprecated since v5.', + ) + none(0, 0); + + final int _x; + final int _y; + + const AnchorAlign(this._x, this._y); } -/// Marker object that is rendered by [MarkerLayerWidget] +/// Represents a coordinate point on the map with an attached widget [builder], +/// rendered by [MarkerLayer] +/// +/// Some properties defaults will absorb the values from the parent [MarkerLayer], +/// if the reflected properties are defined there. class Marker { + final Key? key; + /// Coordinates of the marker final LatLng point; /// Function that builds UI of the marker final WidgetBuilder builder; - final Key? key; /// Bounding box width of the marker final double width; /// Bounding box height of the marker final double height; - final Anchor anchor; - /// If true marker will be counter rotated to the map rotation + /// Positioning of the [builder] widget relative to the center of its bounding + /// box defined by its [height] & [width] + final AnchorPos? anchorPos; + + /// Whether to counter rotate markers to the map's rotation, to keep a fixed + /// orientation final bool? rotate; /// The origin of the coordinate system (relative to the upper left corner of @@ -94,22 +126,31 @@ class Marker { final AlignmentGeometry? rotateAlignment; Marker({ + this.key, required this.point, required this.builder, - this.key, this.width = 30.0, this.height = 30.0, + this.anchorPos, this.rotate, this.rotateOrigin, this.rotateAlignment, - AnchorPos? anchorPos, - }) : anchor = Anchor.forPos(anchorPos, width, height); + }); } class MarkerLayer extends StatelessWidget { final List markers; - /// If true markers will be counter rotated to the map rotation + /// Positioning of the [Marker.builder] widget relative to the center of its + /// bounding box defined by its [Marker.height] & [Marker.width] + /// + /// Overriden on a per [Marker] basis if [Marker.anchorPos] is specified. + final AnchorPos? anchorPos; + + /// Whether to counter rotate markers to the map's rotation, to keep a fixed + /// orientation + /// + /// Overriden on a per [Marker] basis if [Marker.rotate] is specified. final bool rotate; /// The origin of the coordinate system (relative to the upper left corner of @@ -117,6 +158,8 @@ class MarkerLayer extends StatelessWidget { /// /// Setting an origin is equivalent to conjugating the transform matrix by a /// translation. This property is provided just for convenience. + /// + /// Overriden on a per [Marker] basis if [Marker.rotateOrigin] is specified. final Offset? rotateOrigin; /// The alignment of the origin, relative to the size of the box. @@ -131,14 +174,18 @@ class MarkerLayer extends StatelessWidget { /// 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]. + /// + /// Overriden on a per [Marker] basis if [Marker.rotateAlignment] is specified. final AlignmentGeometry? rotateAlignment; - const MarkerLayer( - {super.key, - this.markers = const [], - this.rotate = false, - this.rotateOrigin, - this.rotateAlignment = Alignment.center}); + const MarkerLayer({ + super.key, + this.markers = const [], + this.anchorPos, + this.rotate = false, + this.rotateOrigin, + this.rotateAlignment = Alignment.center, + }); @override Widget build(BuildContext context) { @@ -146,7 +193,6 @@ class MarkerLayer extends StatelessWidget { final markerWidgets = []; for (final marker in markers) { - // Find the position of the point on the screen final pxPoint = map.project(marker.point); // See if any portion of the Marker rect resides in the map bounds @@ -154,25 +200,26 @@ class MarkerLayer extends StatelessWidget { // This calculation works for any Anchor position whithin the Marker // Note that Anchor coordinates of (0,0) are at bottom-right of the Marker // unlike the map coordinates. - final rightPortion = marker.width - marker.anchor.left; - final leftPortion = marker.anchor.left; - final bottomPortion = marker.height - marker.anchor.top; - final topPortion = marker.anchor.top; - - final sw = - CustomPoint(pxPoint.x + leftPortion, pxPoint.y - bottomPortion); - final ne = CustomPoint(pxPoint.x - rightPortion, pxPoint.y + topPortion); - - if (!map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) { + final anchor = Anchor.fromPos( + marker.anchorPos ?? anchorPos ?? AnchorPos.align(AnchorAlign.center), + marker.width, + marker.height, + ); + final rightPortion = marker.width - anchor.left; + final leftPortion = anchor.left; + final bottomPortion = marker.height - anchor.top; + final topPortion = anchor.top; + if (!map.pixelBounds.containsPartialBounds(Bounds( + CustomPoint(pxPoint.x + leftPortion, pxPoint.y - bottomPortion), + CustomPoint(pxPoint.x - rightPortion, pxPoint.y + topPortion)))) { continue; } final pos = pxPoint - map.pixelOrigin; final markerWidget = (marker.rotate ?? rotate) - // Counter rotated marker to the map rotation ? Transform.rotate( angle: -map.rotationRad, - origin: marker.rotateOrigin ?? rotateOrigin, + origin: marker.rotateOrigin ?? rotateOrigin ?? Offset.zero, alignment: marker.rotateAlignment ?? rotateAlignment, child: marker.builder(context), ) @@ -189,8 +236,6 @@ class MarkerLayer extends StatelessWidget { ), ); } - return Stack( - children: markerWidgets, - ); + return Stack(children: markerWidgets); } } diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index 5850d839b..148a77ed4 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; -import 'package:flutter_map/src/core/bounds.dart'; +import 'package:flutter_map/src/map/state.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; import 'package:latlong2/latlong.dart'; /// Base class for all overlay images. diff --git a/lib/src/layer/polygon_layer.dart b/lib/src/layer/polygon_layer.dart index cf20a8217..1fca6d92b 100644 --- a/lib/src/layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer.dart @@ -3,7 +3,7 @@ import 'dart:ui' as ui; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/layer/label.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; +import 'package:flutter_map/src/map/state.dart'; import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI enum PolygonLabelPlacement { diff --git a/lib/src/layer/polyline_layer.dart b/lib/src/layer/polyline_layer.dart index 007d526a0..6d8702b5c 100644 --- a/lib/src/layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer.dart @@ -3,7 +3,7 @@ import 'dart:ui' as ui; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; +import 'package:flutter_map/src/map/state.dart'; import 'package:latlong2/latlong.dart'; class Polyline { @@ -55,33 +55,29 @@ class Polyline { } class PolylineLayer extends StatelessWidget { - /// List of polylines to draw. final List polylines; - final bool polylineCulling; const PolylineLayer({ super.key, this.polylines = const [], this.polylineCulling = false, - @Deprecated('No longer needed and will be removed.') - bool saveLayers = false, }); @override Widget build(BuildContext context) { final map = FlutterMapState.of(context); - final size = Size(map.size.x, map.size.y); - - final List lines = polylineCulling - ? polylines.where((p) { - return p.boundingBox.isOverlapping(map.bounds); - }).toList() - : polylines; return CustomPaint( - painter: PolylinePainter(lines, map), - size: size, + painter: PolylinePainter( + polylineCulling + ? polylines + .where((p) => p.boundingBox.isOverlapping(map.bounds)) + .toList() + : polylines, + map, + ), + size: Size(map.size.x, map.size.y), isComplex: true, ); } diff --git a/lib/src/layer/tile_layer/tile.dart b/lib/src/layer/tile_layer/tile.dart index 50acd6335..a0a8b7c97 100644 --- a/lib/src/layer/tile_layer/tile.dart +++ b/lib/src/layer/tile_layer/tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/misc/point.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_builder.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_image.dart'; diff --git a/lib/src/layer/tile_layer/tile_bounds/tile_bounds.dart b/lib/src/layer/tile_layer/tile_bounds/tile_bounds.dart index 1799e52ac..6cf9f8806 100644 --- a/lib/src/layer/tile_layer/tile_bounds/tile_bounds.dart +++ b/lib/src/layer/tile_layer/tile_bounds/tile_bounds.dart @@ -1,5 +1,5 @@ -import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/geo/crs/crs.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; +import 'package:flutter_map/src/geo/crs.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_bounds/tile_bounds_at_zoom.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_range.dart'; diff --git a/lib/src/layer/tile_layer/tile_bounds/tile_bounds_at_zoom.dart b/lib/src/layer/tile_layer/tile_bounds/tile_bounds_at_zoom.dart index e8b70aceb..b736d4f30 100644 --- a/lib/src/layer/tile_layer/tile_bounds/tile_bounds_at_zoom.dart +++ b/lib/src/layer/tile_layer/tile_bounds/tile_bounds_at_zoom.dart @@ -1,4 +1,4 @@ -import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/misc/point.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_range.dart'; diff --git a/lib/src/layer/tile_layer/tile_image_manager.dart b/lib/src/layer/tile_layer/tile_image_manager.dart index 6944f8175..c210dad1d 100644 --- a/lib/src/layer/tile_layer/tile_image_manager.dart +++ b/lib/src/layer/tile_layer/tile_image_manager.dart @@ -1,5 +1,5 @@ import 'package:collection/collection.dart'; -import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/misc/point.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_bounds/tile_bounds.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_bounds/tile_bounds_at_zoom.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart'; diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index 21833042e..c34c17a48 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -4,10 +4,10 @@ import 'dart:math' as math; import 'package:collection/collection.dart' show MapEquality; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/core/point.dart'; -import 'package:flutter_map/src/core/util.dart' as util; -import 'package:flutter_map/src/geo/crs/crs.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; +import 'package:flutter_map/src/misc/point.dart'; +import 'package:flutter_map/src/misc/private/util.dart' as util; +import 'package:flutter_map/src/geo/crs.dart'; import 'package:flutter_map/src/geo/latlng_bounds.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/layer/tile_layer/tile.dart'; @@ -27,7 +27,7 @@ import 'package:flutter_map/src/layer/tile_layer/tile_range_calculator.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_scale_calculator.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_update_event.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; +import 'package:flutter_map/src/map/state.dart'; import 'package:http/retry.dart'; part 'tile_layer_options.dart'; @@ -55,6 +55,10 @@ class TileLayer extends StatefulWidget { /// Follows the same structure as [urlTemplate]. If specified, this URL is /// used only if an error occurs when loading the [urlTemplate]. + /// + /// Avoid specifying this when using [AssetTileProvider] or [FileTileProvider], + /// as these providers are less performant and efficient when this is + /// specified. See their documentation for more information. final String? fallbackUrl; /// If `true`, inverses Y axis numbering for tiles (turn this on for diff --git a/lib/src/layer/tile_layer/tile_layer_options.dart b/lib/src/layer/tile_layer/tile_layer_options.dart index 925772ba0..5668cb412 100644 --- a/lib/src/layer/tile_layer/tile_layer_options.dart +++ b/lib/src/layer/tile_layer/tile_layer_options.dart @@ -93,8 +93,8 @@ class WMSTileLayerOptions { final tileSizePoint = CustomPoint(tileSize, tileSize); final nwPoint = coords.scaleBy(tileSizePoint); final sePoint = nwPoint + tileSizePoint; - final nwCoords = crs.pointToLatLng(nwPoint, coords.z.toDouble())!; - final seCoords = crs.pointToLatLng(sePoint, coords.z.toDouble())!; + final nwCoords = crs.pointToLatLng(nwPoint, coords.z.toDouble()); + final seCoords = crs.pointToLatLng(sePoint, coords.z.toDouble()); final nw = crs.projection.project(nwCoords); final se = crs.projection.project(seCoords); final bounds = Bounds(nw, se); diff --git a/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart b/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart index 0b3e401b6..551faa038 100644 --- a/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart +++ b/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart @@ -6,23 +6,33 @@ import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart'; +/// Fetch tiles from the app's shipped assets, where the tile URL is a path +/// within the asset store +/// +/// Uses [AssetImage] internally. +/// +/// All tiles must be listed as assets as normal in the pubspec.yaml config file. +/// +/// If [TileLayer.fallbackUrl] is specified, a custom [CachingAssetBundle] is +/// used to retrieve the assets - this bundle is approximatley 23% slower than +/// the default bundle, and as such, specifying [TileLayer.fallbackUrl] should be +/// avoided when using this provider. class AssetTileProvider extends TileProvider { @override AssetImage getImage(TileCoordinates coordinates, TileLayer options) { + final fallbackUrl = getTileFallbackUrl(coordinates, options); return AssetImage( getTileUrl(coordinates, options), - bundle: _FlutterMapAssetBundle( - fallbackKey: getTileFallbackUrl(coordinates, options), - ), + bundle: fallbackUrl == null + ? null + : _FlutterMapAssetBundle(fallbackUrl: fallbackUrl), ); } } -/// Used to load a fallback asset when the main asset is not found. class _FlutterMapAssetBundle extends CachingAssetBundle { - final String? fallbackKey; - - _FlutterMapAssetBundle({required this.fallbackKey}); + _FlutterMapAssetBundle({required this.fallbackUrl}); + final String fallbackUrl; Future _loadAsset(String key) async { final Uint8List encoded = @@ -38,10 +48,8 @@ class _FlutterMapAssetBundle extends CachingAssetBundle { final asset = await _loadAsset(key); if (asset != null && asset.lengthInBytes > 0) return asset; - if (fallbackKey != null) { - final fallbackAsset = await _loadAsset(fallbackKey!); - if (fallbackAsset != null) return fallbackAsset; - } + final fallbackAsset = await _loadAsset(fallbackUrl); + if (fallbackAsset != null) return fallbackAsset; throw FlutterError('_FlutterMapAssetBundle - Unable to load asset: $key'); } diff --git a/lib/src/layer/tile_layer/tile_provider/file_providers/tile_provider_io.dart b/lib/src/layer/tile_layer/tile_provider/file_providers/tile_provider_io.dart index 2ee26a312..8ee076c92 100644 --- a/lib/src/layer/tile_layer/tile_provider/file_providers/tile_provider_io.dart +++ b/lib/src/layer/tile_layer/tile_provider/file_providers/tile_provider_io.dart @@ -5,16 +5,33 @@ import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart'; -/// [TileProvider] to fetch tiles from the local filesystem (not asset store) +/// Fetch tiles from the local filesystem (not asset store), where the tile URL +/// is a path within the filesystem. /// /// Uses [FileImage] internally. +/// +/// If [TileLayer.fallbackUrl] is specified, the [File] must first be +/// synchronously checked for existence - this blocks the main thread, and as +/// such, specifying [TileLayer.fallbackUrl] should be avoided when using this +/// provider. class FileTileProvider extends TileProvider { - /// [TileProvider] to fetch tiles from the local filesystem (not asset store) + /// Fetch tiles from the local filesystem (not asset store), where the tile URL + /// is a path within the filesystem. /// /// Uses [FileImage] internally. + /// + /// If [TileLayer.fallbackUrl] is specified, the [File] must first be + /// synchronously checked for existence - this blocks the main thread, and as + /// such, specifying [TileLayer.fallbackUrl] should be avoided when using this + /// provider. FileTileProvider(); @override - ImageProvider getImage(TileCoordinates coordinates, TileLayer options) => - FileImage(File(getTileUrl(coordinates, options))); + ImageProvider getImage(TileCoordinates coordinates, TileLayer options) { + final file = File(getTileUrl(coordinates, options)); + final fallbackUrl = getTileFallbackUrl(coordinates, options); + + if (fallbackUrl == null || file.existsSync()) return FileImage(file); + return FileImage(File(fallbackUrl)); + } } diff --git a/lib/src/layer/tile_layer/tile_provider/file_providers/tile_provider_stub.dart b/lib/src/layer/tile_layer/tile_provider/file_providers/tile_provider_stub.dart index bbfb1ba46..e0796ee94 100644 --- a/lib/src/layer/tile_layer/tile_provider/file_providers/tile_provider_stub.dart +++ b/lib/src/layer/tile_layer/tile_provider/file_providers/tile_provider_stub.dart @@ -3,16 +3,18 @@ import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart'; -/// [TileProvider] to fetch tiles from the local filesystem (not asset store) +/// Fetch tiles from the local filesystem (not asset store), where the tile URL +/// is a path within the filesystem. /// -/// Stub for IO & web specific implementations. +/// Stub for IO specific implementations. /// /// This web platform does not support reading from the local filesystem, and /// therefore throws an [UnsupportedError] when [getImage] is invoked. class FileTileProvider extends TileProvider { - /// [TileProvider] to fetch tiles from the local filesystem (not asset store) + /// Fetch tiles from the local filesystem (not asset store), where the tile URL + /// is a path within the filesystem. /// - /// Stub for IO & web specific implementations. + /// Stub for IO specific implementations. /// /// This web platform does not support reading from the local filesystem, and /// therefore throws an [UnsupportedError] when [getImage] is invoked. diff --git a/lib/src/layer/tile_layer/tile_range.dart b/lib/src/layer/tile_layer/tile_range.dart index bc44ffb51..da142358a 100644 --- a/lib/src/layer/tile_layer/tile_range.dart +++ b/lib/src/layer/tile_layer/tile_range.dart @@ -1,7 +1,7 @@ import 'dart:math' as math; -import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; +import 'package:flutter_map/src/misc/point.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart'; abstract class TileRange { diff --git a/lib/src/layer/tile_layer/tile_range_calculator.dart b/lib/src/layer/tile_layer/tile_range_calculator.dart index 67c222fcb..b823ef112 100644 --- a/lib/src/layer/tile_layer/tile_range_calculator.dart +++ b/lib/src/layer/tile_layer/tile_range_calculator.dart @@ -1,6 +1,6 @@ -import 'package:flutter_map/src/core/bounds.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_range.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; +import 'package:flutter_map/src/map/state.dart'; import 'package:latlong2/latlong.dart'; class TileRangeCalculator { diff --git a/lib/src/layer/tile_layer/tile_scale_calculator.dart b/lib/src/layer/tile_layer/tile_scale_calculator.dart index ef0b118cc..a07a02216 100644 --- a/lib/src/layer/tile_layer/tile_scale_calculator.dart +++ b/lib/src/layer/tile_layer/tile_scale_calculator.dart @@ -1,4 +1,4 @@ -import 'package:flutter_map/src/geo/crs/crs.dart'; +import 'package:flutter_map/src/geo/crs.dart'; /// Calculate a scale value to transform the Tile's coordinate to its position. class TileScaleCalculator { diff --git a/lib/src/layer/tile_layer/tile_update_transformer.dart b/lib/src/layer/tile_layer/tile_update_transformer.dart index 57d7ac8a2..c75c80373 100644 --- a/lib/src/layer/tile_layer/tile_update_transformer.dart +++ b/lib/src/layer/tile_layer/tile_update_transformer.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:flutter_map/src/core/util.dart'; +import 'package:flutter_map/src/misc/private/util.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_update_event.dart'; diff --git a/lib/src/map/controller.dart b/lib/src/map/controller.dart new file mode 100644 index 000000000..9a481b4ed --- /dev/null +++ b/lib/src/map/controller.dart @@ -0,0 +1,295 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/map/state.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:meta/meta.dart'; + +/// Controller to programmatically interact with [FlutterMap], such as +/// controlling it and accessing some of its properties. +/// +/// See https://docs.fleaflet.dev/usage/controller#initialisation for information +/// on how to set-up and connect a controller to a map widget instance. +abstract class MapController { + /// Controller to programmatically interact with [FlutterMap], such as + /// controlling it and accessing some of its properties. + /// + /// See https://docs.fleaflet.dev/usage/controller#initialisation for + /// information how to set-up and connect a controller to a map widget + /// instance. + /// + /// Factory constructor redirects to underlying implementation's constructor. + factory MapController() = MapControllerImpl._; + + /// Moves and zooms the map to a [center] and [zoom] level + /// + /// [offset] allows a screen-based offset (in normal logical pixels) to be + /// applied to the [center] from the map's view center. For example, + /// `Offset(100, 100)` will move the intended new [center] 100px down & 100px + /// right, and the actual center will become 100px up & 100px left. + /// + /// [id] is an internally meaningless attribute, but it is passed through to + /// the emitted [MapEventMove]. + /// + /// The emitted [MapEventMove.source] property will be + /// [MapEventSource.mapController]. + /// + /// Returns `true` and emits a [MapEventMove] event (which can be listed to + /// through [MapEventCallback]s, such as [MapOptions.onMapEvent]), unless + /// the move failed because (after adjustment when necessary): + /// * [center] and [zoom] are equal to the current values + /// * [center] is out of bounds & [MapOptions.slideOnBoundaries] isn't enabled + bool move( + LatLng center, + double zoom, { + Offset offset = Offset.zero, + String? id, + }); + + /// Rotates the map to a decimal [degree] around the current center, where 0° + /// is North + /// + /// See [rotateAroundPoint] to rotate the map around a custom screen point. + /// + /// [id] is an internally meaningless attribute, but it is passed through to + /// the emitted [MapEventRotate]. + /// + /// The emitted [MapEventRotate.source] property will be + /// [MapEventSource.mapController]. + /// + /// Returns `true` and emits a [MapEventRotate] event (which can be listed to + /// through [MapEventCallback]s, such as [MapOptions.onMapEvent]), unless + /// the move failed because [degree] (after adjustment when necessary) was + /// equal to the current value. + bool rotate(double degree, {String? id}); + + /// Rotates the map to a decimal [degree] around a custom screen point, where + /// 0° is North + /// + /// See [rotate] to rotate the map around the current map center. + /// + /// One, and only one, of [point] or [offset] must be defined: + /// * [point]: allows rotation around a screen-based point (in normal logical + /// pixels), where `Offset(0,0)` is the top-left of the map widget, and the + /// bottom right is `Offset(mapWidth, mapHeight)`. + /// * [offset]: allows rotation around a screen-based offset (in normal logical + /// pixels) from the map's [center]. For example, `Offset(100, 100)` will mean + /// the point is the 100px down & 100px right from the [center]. + /// + /// May cause glitchy movement if rotated against the map's bounds. + /// + /// [id] is an internally meaningless attribute, but it is passed through to + /// the emitted [MapEventRotate] and [MapEventMove]. + /// + /// The emitted [MapEventRotate.source]/[MapEventMove.source] properties will + /// be [MapEventSource.mapController]. + /// + /// The operation was successful if [MoveAndRotateResult.moveSuccess] and + /// [MoveAndRotateResult.rotateSuccess] are `true`. + MoveAndRotateResult rotateAroundPoint( + double degree, { + CustomPoint? point, + Offset? offset, + String? id, + }); + + /// Calls [move] and [rotate] together, but is more efficient for the combined + /// operation + /// + /// Does not support offsets or rotations around custom points. + /// + /// See documentation on those methods for more details. + /// + /// The operation was successful if [MoveAndRotateResult.moveSuccess] and + /// [MoveAndRotateResult.rotateSuccess] are `true`. + MoveAndRotateResult moveAndRotate( + LatLng center, + double zoom, + double degree, { + String? id, + }); + + /// Move and zoom the map to perfectly fit [bounds], with additional + /// configurable [options] + /// + /// For information about return value meaning and emitted events, see [move]'s + /// documentation. + bool fitBounds(LatLngBounds bounds, {FitBoundsOptions? options}); + + /// Calculates the appropriate center and zoom level for the map to perfectly + /// fit [bounds], with additional configurable [options] + /// + /// Does not move/zoom the map: see [fitBounds]. + CenterZoom centerZoomFitBounds( + LatLngBounds bounds, { + FitBoundsOptions? options, + }); + + /// Convert a screen point (x/y) to its corresponding map coordinate (lat/lng), + /// based on the map's current properties + LatLng pointToLatLng(CustomPoint screenPoint); + + /// Convert a map coordinate (lat/lng) to its corresponding screen point (x/y), + /// based on the map's current screen positioning + CustomPoint latLngToScreenPoint(LatLng mapCoordinate); + + CustomPoint rotatePoint( + CustomPoint mapCenter, + CustomPoint point, { + bool counterRotation = true, + }); + + /// Current center coordinates + LatLng get center; + + /// Current outer points/boundaries coordinates + LatLngBounds? get bounds; + + /// Current zoom level + double get zoom; + + /// Current rotation in degrees, where 0° is North + double get rotation; + + /// [Stream] of all emitted [MapEvent]s + Stream get mapEventStream; + + /// Underlying [StreamSink] of [mapEventStream] + /// + /// Usually prefer to use [mapEventStream]. + StreamSink get mapEventSink; + + /// Immediately change the internal map state + /// + /// Not recommended for external usage. + set state(FlutterMapState state); + + /// Dispose of this controller by closing the [mapEventStream]'s + /// [StreamController] + /// + /// Not recommended for external usage. + void dispose(); +} + +@internal +class MapControllerImpl implements MapController { + MapControllerImpl._(); + + @override + bool move( + LatLng center, + double zoom, { + Offset offset = Offset.zero, + String? id, + }) => + _state.move( + center, + zoom, + offset: offset, + id: id, + source: MapEventSource.mapController, + ); + + @override + bool rotate(double degree, {String? id}) => + _state.rotate(degree, id: id, source: MapEventSource.mapController); + + @override + MoveAndRotateResult rotateAroundPoint( + double degree, { + CustomPoint? point, + Offset? offset, + String? id, + }) => + _state.rotateAroundPoint( + degree, + point: point, + offset: offset, + id: id, + source: MapEventSource.mapController, + ); + + @override + MoveAndRotateResult moveAndRotate( + LatLng center, + double zoom, + double degree, { + String? id, + }) => + _state.moveAndRotate( + center, + zoom, + degree, + source: MapEventSource.mapController, + id: id, + ); + + @override + bool fitBounds( + LatLngBounds bounds, { + FitBoundsOptions? options = + const FitBoundsOptions(padding: EdgeInsets.all(12)), + }) => + _state.fitBounds(bounds, options!); + + @override + CenterZoom centerZoomFitBounds( + LatLngBounds bounds, { + FitBoundsOptions? options = + const FitBoundsOptions(padding: EdgeInsets.all(12)), + }) => + _state.centerZoomFitBounds(bounds, options!); + + @override + LatLng pointToLatLng(CustomPoint localPoint) => + _state.pointToLatLng(localPoint); + + @override + CustomPoint latLngToScreenPoint(LatLng latLng) => + _state.latLngToScreenPoint(latLng); + + @override + CustomPoint rotatePoint( + CustomPoint mapCenter, + CustomPoint point, { + bool counterRotation = true, + }) => + _state.rotatePoint( + mapCenter.toDoublePoint(), + point.toDoublePoint(), + counterRotation: counterRotation, + ); + + @override + LatLng get center => _state.center; + + @override + LatLngBounds? get bounds => _state.bounds; + + @override + double get zoom => _state.zoom; + + @override + double get rotation => _state.rotation; + + final _mapEventStreamController = StreamController.broadcast(); + + @override + Stream get mapEventStream => _mapEventStreamController.stream; + + @override + StreamSink get mapEventSink => _mapEventStreamController.sink; + + late FlutterMapState _state; + + @override + set state(FlutterMapState state) { + _state = state; + } + + @override + void dispose() { + _mapEventStreamController.close(); + } +} diff --git a/lib/src/map/map.dart b/lib/src/map/map.dart deleted file mode 100644 index 018a8b673..000000000 --- a/lib/src/map/map.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; -import 'package:latlong2/latlong.dart'; - -class MapControllerImpl implements MapController { - final StreamController _mapEventSink = StreamController.broadcast(); - - @override - StreamSink get mapEventSink => _mapEventSink.sink; - - @override - Stream get mapEventStream => _mapEventSink.stream; - - late FlutterMapState _state; - - @override - set state(FlutterMapState state) { - _state = state; - } - - @override - void dispose() { - _mapEventSink.close(); - } - - @override - MoveAndRotateResult moveAndRotate(LatLng center, double zoom, double degree, - {String? id}) { - return _state.moveAndRotate(center, zoom, degree, - source: MapEventSource.mapController, id: id); - } - - @override - bool move(LatLng center, double zoom, {String? id}) { - return _state.move(center, zoom, - id: id, source: MapEventSource.mapController); - } - - @override - void fitBounds( - LatLngBounds bounds, { - FitBoundsOptions? options = - const FitBoundsOptions(padding: EdgeInsets.all(12)), - }) { - _state.fitBounds(bounds, options!); - } - - @override - CenterZoom centerZoomFitBounds( - LatLngBounds bounds, { - FitBoundsOptions? options = - const FitBoundsOptions(padding: EdgeInsets.all(12)), - }) { - return _state.centerZoomFitBounds(bounds, options!); - } - - @override - LatLng get center => _state.center; - - @override - LatLngBounds? get bounds => _state.bounds; - - @override - double get zoom => _state.zoom; - - @override - double get rotation => _state.rotation; - - @override - bool rotate(double degree, {String? id}) { - return _state.rotate(degree, id: id, source: MapEventSource.mapController); - } - - @override - CustomPoint latLngToScreenPoint(LatLng latLng) { - return _state.latLngToScreenPoint(latLng); - } - - @override - LatLng? pointToLatLng(CustomPoint localPoint) { - return _state.pointToLatLng(localPoint); - } - - CustomPoint rotatePoint(CustomPoint mapCenter, CustomPoint point, - {bool counterRotation = true}) { - return _state.rotatePoint( - mapCenter.toDoublePoint(), - point.toDoublePoint(), - counterRotation: counterRotation, - ); - } -} diff --git a/lib/src/map/map_state_widget.dart b/lib/src/map/map_state_widget.dart deleted file mode 100644 index 5422205ba..000000000 --- a/lib/src/map/map_state_widget.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/map/flutter_map_state.dart'; - -class MapStateInheritedWidget extends InheritedWidget { - final FlutterMapState mapState; - - const MapStateInheritedWidget({ - super.key, - required this.mapState, - required super.child, - }); - - @override - bool updateShouldNotify(MapStateInheritedWidget oldWidget) { - // mapState will be the same because FlutterMapState create MapState object just once - // and pass the same instance to the old / new MapStateInheritedWidget - // Moreover MapStateInheritedWidget child isn't cached so all of it's content will be updated no matter if we return here with false - return true; - } - - static MapStateInheritedWidget? maybeOf(BuildContext context) { - return context - .dependOnInheritedWidgetOfExactType(); - } -} diff --git a/lib/src/map/options.dart b/lib/src/map/options.dart new file mode 100644 index 000000000..e5a6ae14f --- /dev/null +++ b/lib/src/map/options.dart @@ -0,0 +1,199 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; + +/// Allows you to provide your map's starting properties for [zoom], [rotation] +/// and [center]. Alternatively you can provide [bounds] instead of [center]. +/// If both, [center] and [bounds] are provided, bounds will take preference +/// over [center]. +/// Zoom, pan boundary and interactivity constraints can be specified here too. +/// +/// Callbacks for [onTap], [onSecondaryTap], [onLongPress] and +/// [onPositionChanged] can be registered here. +/// +/// Through [crs] the Coordinate Reference System can be +/// defined, it defaults to [Epsg3857]. +/// +/// Checks if a coordinate is outside of the map's +/// defined boundaries. +/// +/// If you download offline tiles dynamically, you can set [adaptiveBoundaries] +/// to true (make sure to pass [screenSize] and an external [controller]), which +/// will enforce panning/zooming to ensure there is never a need to display +/// tiles outside the boundaries set by [swPanBoundary] and [nePanBoundary]. +class MapOptions { + final Crs crs; + final double zoom; + final double rotation; + + /// Prints multi finger gesture winner Helps to fine adjust + /// [rotationThreshold] and [pinchZoomThreshold] and [pinchMoveThreshold] + /// Note: only takes effect if [enableMultiFingerGestureRace] is true + final bool debugMultiFingerGestureWinner; + + /// If true then [rotationThreshold] and [pinchZoomThreshold] and + /// [pinchMoveThreshold] will race If multiple gestures win at the same time + /// then precedence: [pinchZoomWinGestures] > [rotationWinGestures] > + /// [pinchMoveWinGestures] + final bool enableMultiFingerGestureRace; + + /// Rotation threshold in degree default is 20.0 Map starts to rotate when + /// [rotationThreshold] has been achieved or another multi finger gesture wins + /// which allows [MultiFingerGesture.rotate] Note: if [interactiveFlags] + /// doesn't contain [InteractiveFlag.rotate] or [enableMultiFingerGestureRace] + /// is false then rotate cannot win + final double rotationThreshold; + + /// When [rotationThreshold] wins over [pinchZoomThreshold] and + /// [pinchMoveThreshold] then [rotationWinGestures] gestures will be used. By + /// default only [MultiFingerGesture.rotate] gesture will take effect see + /// [MultiFingerGesture] for custom settings + final int rotationWinGestures; + + /// Pinch Zoom threshold default is 0.5 Map starts to zoom when + /// [pinchZoomThreshold] has been achieved or another multi finger gesture + /// wins which allows [MultiFingerGesture.pinchZoom] Note: if + /// [interactiveFlags] doesn't contain [InteractiveFlag.pinchZoom] or + /// [enableMultiFingerGestureRace] is false then zoom cannot win + final double pinchZoomThreshold; + + /// When [pinchZoomThreshold] wins over [rotationThreshold] and + /// [pinchMoveThreshold] then [pinchZoomWinGestures] gestures will be used. By + /// default [MultiFingerGesture.pinchZoom] and [MultiFingerGesture.pinchMove] + /// gestures will take effect see [MultiFingerGesture] for custom settings + final int pinchZoomWinGestures; + + /// Pinch Move threshold default is 40.0 (note: this doesn't take any effect + /// on drag) Map starts to move when [pinchMoveThreshold] has been achieved or + /// another multi finger gesture wins which allows + /// [MultiFingerGesture.pinchMove] Note: if [interactiveFlags] doesn't contain + /// [InteractiveFlag.pinchMove] or [enableMultiFingerGestureRace] is false + /// then pinch move cannot win + final double pinchMoveThreshold; + + /// When [pinchMoveThreshold] wins over [rotationThreshold] and + /// [pinchZoomThreshold] then [pinchMoveWinGestures] gestures will be used. By + /// default [MultiFingerGesture.pinchMove] and [MultiFingerGesture.pinchZoom] + /// gestures will take effect see [MultiFingerGesture] for custom settings + final int pinchMoveWinGestures; + + /// If true then the map will scroll when the user uses the scroll wheel on + /// his mouse. This is supported on web and desktop, but might also work well + /// on Android. A [Listener] is used to capture the onPointerSignal events. + final bool enableScrollWheel; + final double scrollWheelVelocity; + + final double? minZoom; + final double? maxZoom; + + /// see [InteractiveFlag] for custom settings + final int interactiveFlags; + + final TapCallback? onTap; + final TapCallback? onSecondaryTap; + final LongPressCallback? onLongPress; + final PointerDownCallback? onPointerDown; + final PointerUpCallback? onPointerUp; + final PointerCancelCallback? onPointerCancel; + final PointerHoverCallback? onPointerHover; + final PositionCallback? onPositionChanged; + final MapEventCallback? onMapEvent; + final bool slideOnBoundaries; + final Size? screenSize; + final bool adaptiveBoundaries; + final LatLng center; + final LatLngBounds? bounds; + final FitBoundsOptions boundsOptions; + final LatLng? swPanBoundary; + final LatLng? nePanBoundary; + + /// OnMapReady is called after the map runs it's initState. + /// At that point the map has assigned its state to the controller + /// Only use this if your map isn't built immediately (like inside FutureBuilder) + /// and you need to access the controller as soon as the map is built. + /// Otherwise you can use WidgetsBinding.instance.addPostFrameCallback + /// In initState to controll the map before the next frame + final void Function()? onMapReady; + + /// Restrict outer edges of map to LatLng Bounds, to prevent gray areas when + /// panning or zooming. LatLngBounds(LatLng(-90, -180.0), LatLng(90.0, 180.0)) + /// would represent the full extent of the map, so no gray area outside of it. + final LatLngBounds? maxBounds; + + /// Flag to enable the built in keep alive functionality + /// + /// If the map is within a complex layout, such as a [ListView] or [PageView], + /// the map will reset to it's inital position after it appears back into view. + /// To ensure this doesn't happen, enable this flag to prevent the [FlutterMap] + /// widget from rebuilding. + final bool keepAlive; + + MapOptions({ + this.crs = const Epsg3857(), + LatLng? center, + this.bounds, + this.boundsOptions = const FitBoundsOptions(), + this.zoom = 13.0, + this.rotation = 0.0, + this.debugMultiFingerGestureWinner = false, + this.enableMultiFingerGestureRace = false, + this.rotationThreshold = 20.0, + this.rotationWinGestures = MultiFingerGesture.rotate, + this.pinchZoomThreshold = 0.5, + this.pinchZoomWinGestures = + MultiFingerGesture.pinchZoom | MultiFingerGesture.pinchMove, + this.pinchMoveThreshold = 40.0, + this.pinchMoveWinGestures = + MultiFingerGesture.pinchZoom | MultiFingerGesture.pinchMove, + this.enableScrollWheel = true, + this.scrollWheelVelocity = 0.005, + this.minZoom, + this.maxZoom, + this.interactiveFlags = InteractiveFlag.all, + this.onTap, + this.onSecondaryTap, + this.onLongPress, + this.onPointerDown, + this.onPointerUp, + this.onPointerCancel, + this.onPointerHover, + this.onPositionChanged, + this.onMapEvent, + this.onMapReady, + this.slideOnBoundaries = false, + this.adaptiveBoundaries = false, + this.screenSize, + this.swPanBoundary, + this.nePanBoundary, + this.maxBounds, + this.keepAlive = false, + }) : center = center ?? const LatLng(50.5, 30.51), + assert(rotationThreshold >= 0.0), + assert(pinchZoomThreshold >= 0.0), + assert(pinchMoveThreshold >= 0.0) { + assert(!adaptiveBoundaries || screenSize != null, + 'screenSize must be set in order to enable adaptive boundaries.'); + } +} + +typedef MapEventCallback = void Function(MapEvent); + +typedef TapCallback = void Function(TapPosition tapPosition, LatLng point); +typedef LongPressCallback = void Function( + TapPosition tapPosition, + LatLng point, +); +typedef PointerDownCallback = void Function( + PointerDownEvent event, + LatLng point, +); +typedef PointerUpCallback = void Function(PointerUpEvent event, LatLng point); +typedef PointerCancelCallback = void Function( + PointerCancelEvent event, + LatLng point, +); +typedef PointerHoverCallback = void Function( + PointerHoverEvent event, + LatLng point, +); diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/state.dart similarity index 82% rename from lib/src/map/flutter_map_state.dart rename to lib/src/map/state.dart index 35c83e21e..73568952c 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/state.dart @@ -4,10 +4,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/core/bounds.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; -import 'package:flutter_map/src/map/map.dart'; -import 'package:flutter_map/src/map/map_state_widget.dart'; import 'package:latlong2/latlong.dart'; class FlutterMapState extends MapGestureMixin @@ -15,41 +13,35 @@ class FlutterMapState extends MapGestureMixin static const invalidSize = CustomPoint(-1, -1); final _positionedTapController = PositionedTapController(); - final GestureArenaTeam _team = GestureArenaTeam(); - - final MapController _localController = MapControllerImpl(); + final _gestureArenaTeam = GestureArenaTeam(); bool _hasFitInitialBounds = false; - @override - MapOptions get options => widget.options; - @override FlutterMapState get mapState => this; + final _localController = MapController(); @override MapController get mapController => widget.mapController ?? _localController; + @override + MapOptions get options => widget.options; + @override void initState() { super.initState(); mapController.state = this; - - // Initialize all variables here, if they need to be updated after the map changes - // like center, or bounds they also need to be updated in build. _rotation = options.rotation; _center = options.center; _zoom = options.zoom; _pixelBounds = getPixelBounds(); _bounds = _calculateBounds(); - WidgetsBinding.instance.addPostFrameCallback((_) { - options.onMapReady?.call(); - }); + WidgetsBinding.instance + .addPostFrameCallback((_) => options.onMapReady?.call()); } - //This may not be required. @override void didUpdateWidget(FlutterMap oldWidget) { super.didUpdateWidget(oldWidget); @@ -96,7 +88,7 @@ class FlutterMapState extends MapGestureMixin // Absorbing vertical drags }; instance.gestureSettings = gestureSettings; - instance.team ??= _team; + instance.team ??= _gestureArenaTeam; }, ); gestures[HorizontalDragGestureRecognizer] = @@ -107,7 +99,7 @@ class FlutterMapState extends MapGestureMixin // Absorbing horizontal drags }; instance.gestureSettings = gestureSettings; - instance.team ??= _team; + instance.team ??= _gestureArenaTeam; }, ); } @@ -120,8 +112,8 @@ class FlutterMapState extends MapGestureMixin ..onStart = handleScaleStart ..onUpdate = handleScaleUpdate ..onEnd = handleScaleEnd; - instance.team ??= _team; - _team.captain = instance; + instance.team ??= _gestureArenaTeam; + _gestureArenaTeam.captain = instance; }, ); @@ -146,7 +138,7 @@ class FlutterMapState extends MapGestureMixin _bounds = _calculateBounds(); _pixelOrigin = getNewPixelOrigin(_center); - return MapStateInheritedWidget( + return _MapStateInheritedWidget( mapState: this, child: Listener( onPointerDown: onPointerDown, @@ -160,9 +152,31 @@ class FlutterMapState extends MapGestureMixin onSecondaryTap: handleSecondaryTap, onLongPress: handleLongPress, onDoubleTap: handleDoubleTap, + doubleTapDelay: InteractiveFlag.hasFlag( + options.interactiveFlags, + InteractiveFlag.doubleTapZoom, + ) + ? null + : Duration.zero, child: RawGestureDetector( gestures: gestures, - child: _buildMap(), + child: ClipRect( + child: Stack( + children: [ + OverflowBox( + minWidth: size.x, + maxWidth: size.x, + minHeight: size.y, + maxHeight: size.y, + child: Transform.rotate( + angle: rotationRad, + child: Stack(children: widget.children), + ), + ), + Stack(children: widget.nonRotatedChildren), + ], + ), + ), ), ), ), @@ -180,42 +194,9 @@ class FlutterMapState extends MapGestureMixin BuildContext context, BoxConstraints constraints) => constraints.maxWidth != 0 || MediaQuery.sizeOf(context) != Size.zero; - Widget _buildMap() { - return ClipRect( - child: Stack( - children: [ - OverflowBox( - minWidth: size.x, - maxWidth: size.x, - minHeight: size.y, - maxHeight: size.y, - child: Transform.rotate( - angle: rotationRad, - child: Stack( - children: widget.children, - ), - ), - ), - Stack( - children: widget.nonRotatedChildren, - ), - ], - ), - ); - } - @override bool get wantKeepAlive => options.keepAlive; - ///MAP STATE - ///MAP STATE - ///MAP STATE - ///MAP STATE - ///MAP STATE - ///MAP STATE - ///MAP STATE - ///MAP STATE - late double _zoom; late double _rotation; @@ -330,49 +311,119 @@ class FlutterMapState extends MapGestureMixin return false; } - MoveAndRotateResult moveAndRotate( - LatLng newCenter, double newZoom, double newRotation, - {required MapEventSource source, String? id}) { - final moveSucc = move(newCenter, newZoom, id: id, source: source); - final rotateSucc = rotate(newRotation, id: id, source: source); + MoveAndRotateResult rotateAroundPoint( + double degree, { + CustomPoint? point, + Offset? offset, + bool hasGesture = false, + required MapEventSource source, + String? id, + }) { + if (point != null && offset != null) { + throw ArgumentError('Only one of `point` or `offset` may be non-null'); + } + if (point == null && offset == null) { + throw ArgumentError('One of `point` or `offset` must be non-null'); + } + + if (degree == rotation) return MoveAndRotateResult(false, false); + + if (offset == Offset.zero) { + return MoveAndRotateResult( + true, + rotate( + degree, + hasGesture: hasGesture, + source: source, + id: id, + ), + ); + } - return MoveAndRotateResult(moveSucc, rotateSucc); + final rotationDiff = degree - rotation; + final rotationCenter = project(center, zoom) + + (point != null + ? (point - (nonrotatedSize / 2.0)) + : CustomPoint(offset!.dx, offset.dy)) + .rotate(rotationRad); + + return MoveAndRotateResult( + move( + unproject( + rotationCenter + + (project(center) - rotationCenter) + .rotate(degToRadian(rotationDiff)), + ), + zoom, + hasGesture: hasGesture, + source: source, + id: id, + ), + rotate( + rotation + rotationDiff, + hasGesture: hasGesture, + source: source, + id: id, + ), + ); } + MoveAndRotateResult moveAndRotate( + LatLng newCenter, + double newZoom, + double newRotation, { + Offset offset = Offset.zero, + required MapEventSource source, + String? id, + }) => + MoveAndRotateResult( + move(newCenter, newZoom, offset: offset, id: id, source: source), + rotate(newRotation, id: id, source: source), + ); + bool move( LatLng newCenter, double newZoom, { + Offset offset = Offset.zero, bool hasGesture = false, required MapEventSource source, String? id, }) { newZoom = fitZoomToBounds(newZoom); - if (newCenter == _center && newZoom == _zoom) return false; + // Algorithm thanks to https://github.com/tlserver/flutter_map_location_marker + if (offset != Offset.zero) { + final newPoint = options.crs.latLngToPoint(newCenter, newZoom); + newCenter = options.crs.pointToLatLng( + rotatePoint( + newPoint, + newPoint - CustomPoint(offset.dx, offset.dy), + ), + newZoom, + ); + } if (isOutOfBounds(newCenter)) { - if (!options.slideOnBoundaries) { - return false; - } + if (!options.slideOnBoundaries) return false; newCenter = containPoint(newCenter, _center); } - // Try and fit the corners of the map inside the visible area. - // If it's still outside (so response is null), don't perform a move. if (options.maxBounds != null) { final adjustedCenter = adjustCenterIfOutsideMaxBounds( - newCenter, newZoom, options.maxBounds!); - if (adjustedCenter == null) { - return false; - } else { - newCenter = adjustedCenter; - } + newCenter, + newZoom, + options.maxBounds!, + ); + + if (adjustedCenter == null) return false; + newCenter = adjustedCenter; } - final LatLng oldCenter = _center; - final double oldZoom = _zoom; + if (newCenter == _center && newZoom == _zoom) return false; + + final oldCenter = _center; + final oldZoom = _zoom; - //Apply state then emit events and callbacks setState(() { _zoom = newZoom; _center = newCenter; @@ -389,6 +440,7 @@ class FlutterMapState extends MapGestureMixin oldZoom: oldZoom, hasGesture: hasGesture, source: source, + id: id, ); if (movementEvent != null) emitMapEvent(movementEvent); @@ -416,9 +468,18 @@ class FlutterMapState extends MapGestureMixin return zoom; } - void fitBounds(LatLngBounds bounds, FitBoundsOptions options) { + bool fitBounds( + LatLngBounds bounds, + FitBoundsOptions options, { + Offset offset = Offset.zero, + }) { final target = getBoundsCenterZoom(bounds, options); - move(target.center, target.zoom, source: MapEventSource.fitBounds); + return move( + target.center, + target.zoom, + offset: offset, + source: MapEventSource.fitBounds, + ); } CenterZoom centerZoomFitBounds( @@ -491,7 +552,7 @@ class FlutterMapState extends MapGestureMixin LatLng unproject(CustomPoint point, [double? zoom]) { zoom ??= _zoom; - return options.crs.pointToLatLng(point, zoom)!; + return options.crs.pointToLatLng(point, zoom); } LatLng layerPointToLatLng(CustomPoint point) { @@ -613,7 +674,7 @@ class FlutterMapState extends MapGestureMixin return point - nonRotatedPixelOrigin; } - LatLng? pointToLatLng(CustomPoint localPoint) { + LatLng pointToLatLng(CustomPoint localPoint) { final localPointCenterDistance = CustomPoint( (_nonrotatedSize.x / 2) - localPoint.x, (_nonrotatedSize.y / 2) - localPoint.y, @@ -720,7 +781,7 @@ class FlutterMapState extends MapGestureMixin options.screenSize!.height * 170.102258 / math.pow(2, zoom + 8); static FlutterMapState? maybeOf(BuildContext context) => context - .dependOnInheritedWidgetOfExactType() + .dependOnInheritedWidgetOfExactType<_MapStateInheritedWidget>() ?.mapState; static FlutterMapState of(BuildContext context) => @@ -751,3 +812,16 @@ class _SafeArea { : point.longitude.clamp(bounds.west, bounds.east), ); } + +class _MapStateInheritedWidget extends InheritedWidget { + const _MapStateInheritedWidget({ + required this.mapState, + required super.child, + }); + + final FlutterMapState mapState; + + /// This return value does not appear to affect anything, no matter it's value + @override + bool updateShouldNotify(_MapStateInheritedWidget oldWidget) => true; +} diff --git a/lib/src/map/widget.dart b/lib/src/map/widget.dart new file mode 100644 index 000000000..9ae646296 --- /dev/null +++ b/lib/src/map/widget.dart @@ -0,0 +1,37 @@ +import 'package:flutter/widgets.dart'; + +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/map/state.dart'; + +/// Renders an interactive geographical map as a widget +/// +/// See the online documentation for more information about set-up, +/// configuration, and usage. +class FlutterMap extends StatefulWidget { + /// Renders an interactive geographical map as a widget + /// + /// See the online documentation for more information about set-up, + /// configuration, and usage. + const FlutterMap({ + super.key, + required this.options, + this.children = const [], + this.nonRotatedChildren = const [], + this.mapController, + }); + + /// Layers/widgets to be painted onto the map, in a [Stack]-like fashion + final List children; + + /// Same as [children], except these are unnaffected by map rotation + final List nonRotatedChildren; + + /// Configure this map + final MapOptions options; + + /// Programatically interact with this map + final MapController? mapController; + + @override + State createState() => FlutterMapState(); +} diff --git a/lib/src/core/center_zoom.dart b/lib/src/misc/center_zoom.dart similarity index 100% rename from lib/src/core/center_zoom.dart rename to lib/src/misc/center_zoom.dart diff --git a/lib/src/misc/fit_bounds_options.dart b/lib/src/misc/fit_bounds_options.dart new file mode 100644 index 000000000..d0b87b0d4 --- /dev/null +++ b/lib/src/misc/fit_bounds_options.dart @@ -0,0 +1,19 @@ +import 'package:flutter/widgets.dart'; + +class FitBoundsOptions { + final EdgeInsets padding; + final double maxZoom; + final bool inside; + + /// By default calculations will return fractional zoom levels. + /// If this parameter is set to [true] fractional zoom levels will be round + /// to the next suitable integer. + final bool forceIntegerZoomLevel; + + const FitBoundsOptions({ + this.padding = EdgeInsets.zero, + this.maxZoom = 17.0, + this.inside = false, + this.forceIntegerZoomLevel = false, + }); +} diff --git a/lib/src/misc/move_and_rotate_result.dart b/lib/src/misc/move_and_rotate_result.dart new file mode 100644 index 000000000..5addaba37 --- /dev/null +++ b/lib/src/misc/move_and_rotate_result.dart @@ -0,0 +1,6 @@ +class MoveAndRotateResult { + final bool moveSuccess; + final bool rotateSuccess; + + MoveAndRotateResult(this.moveSuccess, this.rotateSuccess); +} diff --git a/lib/src/core/point.dart b/lib/src/misc/point.dart similarity index 100% rename from lib/src/core/point.dart rename to lib/src/misc/point.dart diff --git a/lib/src/misc/position.dart b/lib/src/misc/position.dart new file mode 100644 index 000000000..6f9bff191 --- /dev/null +++ b/lib/src/misc/position.dart @@ -0,0 +1,23 @@ +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; + +class MapPosition { + final LatLng? center; + final LatLngBounds? bounds; + final double? zoom; + final bool hasGesture; + + MapPosition({this.center, this.bounds, this.zoom, this.hasGesture = false}); + + @override + int get hashCode => center.hashCode + bounds.hashCode + zoom.hashCode; + + @override + bool operator ==(Object other) => + other is MapPosition && + other.center == center && + other.bounds == bounds && + other.zoom == zoom; +} + +typedef PositionCallback = void Function(MapPosition position, bool hasGesture); diff --git a/lib/src/core/bounds.dart b/lib/src/misc/private/bounds.dart similarity index 98% rename from lib/src/core/bounds.dart rename to lib/src/misc/private/bounds.dart index b27379f1f..191fc86a9 100644 --- a/lib/src/core/bounds.dart +++ b/lib/src/misc/private/bounds.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/misc/point.dart'; /// Rectangular bound delimited by orthogonal lines passing through two /// points. diff --git a/lib/src/core/positioned_tap_detector_2.dart b/lib/src/misc/private/positioned_tap_detector_2.dart similarity index 87% rename from lib/src/core/positioned_tap_detector_2.dart rename to lib/src/misc/private/positioned_tap_detector_2.dart index 5a1b3cc5e..30799bb96 100644 --- a/lib/src/core/positioned_tap_detector_2.dart +++ b/lib/src/misc/private/positioned_tap_detector_2.dart @@ -1,3 +1,9 @@ +////////////////////////////////////////////////////////////////// +/// Based on the work by Ali Raghebi /// +/// Now maintained here due to abandonment /// +/// https://github.com/arsamme/flutter-positioned-tap-detector /// +////////////////////////////////////////////////////////////////// + import 'dart:async'; import 'dart:math'; @@ -7,16 +13,16 @@ typedef TapPositionCallback = void Function(TapPosition position); class PositionedTapDetector2 extends StatefulWidget { const PositionedTapDetector2({ - Key? key, + super.key, this.child, this.onTap, this.onDoubleTap, this.onSecondaryTap, this.onLongPress, - this.doubleTapDelay = _defaultDelay, + Duration? doubleTapDelay, this.behavior, this.controller, - }) : super(key: key); + }) : doubleTapDelay = doubleTapDelay ?? _defaultDelay; static const _defaultDelay = Duration(milliseconds: 250); static const _doubleTapMaxOffset = 48.0; @@ -37,9 +43,9 @@ class PositionedTapDetector2 extends StatefulWidget { class _TapPositionDetectorState extends State { final _controller = StreamController(); - Stream get _stream => _controller.stream; - + late final _stream = _controller.stream.asBroadcastStream(); Sink get _sink => _controller.sink; + late StreamSubscription _streamSub; PositionedTapController? _tapController; TapDownDetails? _pendingTap; @@ -48,10 +54,7 @@ class _TapPositionDetectorState extends State { @override void initState() { _updateController(); - _stream - .timeout(widget.doubleTapDelay) - .handleError(_onTimeout, test: (e) => e is TimeoutException) - .listen(_onTapConfirmed); + _startStream(); super.initState(); } @@ -61,6 +64,16 @@ class _TapPositionDetectorState extends State { if (widget.controller != oldWidget.controller) { _updateController(); } + if (widget.doubleTapDelay != oldWidget.doubleTapDelay) { + _streamSub.cancel().then(_startStream); + } + } + + void _startStream([dynamic d]) { + _streamSub = _stream + .timeout(widget.doubleTapDelay) + .handleError(_onTimeout, test: (e) => e is TimeoutException) + .listen(_onTapConfirmed); } void _updateController() { @@ -169,6 +182,7 @@ class _TapPositionDetectorState extends State { @override void dispose() { _controller.close(); + _streamSub.cancel(); _tapController?._state = null; super.dispose(); } diff --git a/lib/src/core/util.dart b/lib/src/misc/private/util.dart similarity index 100% rename from lib/src/core/util.dart rename to lib/src/misc/private/util.dart diff --git a/pubspec.yaml b/pubspec.yaml index 1d537db11..912a8b1d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,23 @@ name: flutter_map -description: A versatile mapping package for Flutter, based off leaflet.js, - that's simple and easy to learn, yet completely customizable and configurable. +description: A versatile mapping package for Flutter, that's simple and easy to learn, yet completely customizable and configurable. version: 5.0.0 + repository: https://github.com/fleaflet/flutter_map issue_tracker: https://github.com/fleaflet/flutter_map/issues documentation: https://docs.fleaflet.dev +topics: + - flutter-map + - map + +platforms: + android: + ios: + linux: + macos: + web: + windows: + environment: sdk: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" diff --git a/test/core/bounds_test.dart b/test/core/bounds_test.dart index 9fff7b714..fe58eab52 100644 --- a/test/core/bounds_test.dart +++ b/test/core/bounds_test.dart @@ -1,5 +1,5 @@ -import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; +import 'package:flutter_map/src/misc/point.dart'; import 'package:flutter_test/flutter_test.dart'; import '../helpers/core.dart'; diff --git a/test/layer/tile_layer/tile_bounds/crs_fakes.dart b/test/layer/tile_layer/tile_bounds/crs_fakes.dart index ee39c034f..594144930 100644 --- a/test/layer/tile_layer/tile_bounds/crs_fakes.dart +++ b/test/layer/tile_layer/tile_bounds/crs_fakes.dart @@ -1,5 +1,5 @@ -import 'package:flutter_map/src/core/point.dart'; -import 'package:flutter_map/src/geo/crs/crs.dart'; +import 'package:flutter_map/src/misc/point.dart'; +import 'package:flutter_map/src/geo/crs.dart'; import 'package:latlong2/latlong.dart'; class FakeInfiniteCrs extends Crs { diff --git a/test/layer/tile_layer/tile_bounds/tile_bounds_at_zoom_test.dart b/test/layer/tile_layer/tile_bounds/tile_bounds_at_zoom_test.dart index a7e1bb8ab..460591669 100644 --- a/test/layer/tile_layer/tile_bounds/tile_bounds_at_zoom_test.dart +++ b/test/layer/tile_layer/tile_bounds/tile_bounds_at_zoom_test.dart @@ -1,5 +1,5 @@ -import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/misc/private/bounds.dart'; +import 'package:flutter_map/src/misc/point.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_bounds/tile_bounds_at_zoom.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_range.dart'; diff --git a/test/layer/tile_layer/tile_bounds/tile_bounds_test.dart b/test/layer/tile_layer/tile_bounds/tile_bounds_test.dart index 91db2ef29..e668f3235 100644 --- a/test/layer/tile_layer/tile_bounds/tile_bounds_test.dart +++ b/test/layer/tile_layer/tile_bounds/tile_bounds_test.dart @@ -91,7 +91,7 @@ void main() { tileSize: 256, latLngBounds: LatLngBounds( const LatLng(0, 0), - crs.pointToLatLng(crs.getProjectedBounds(0)!.max, 0)!, + crs.pointToLatLng(crs.getProjectedBounds(0)!.max, 0), ), ); diff --git a/test/layer/tile_layer/tile_range_test.dart b/test/layer/tile_layer/tile_range_test.dart index 5c8cbea50..6c28a1761 100644 --- a/test/layer/tile_layer/tile_range_test.dart +++ b/test/layer/tile_layer/tile_range_test.dart @@ -1,6 +1,4 @@ -import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/core/point.dart'; -import 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart'; +import 'package:flutter_map/plugin_api.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_range.dart'; import 'package:test/test.dart'; diff --git a/tool/history.sh b/tool/history.sh deleted file mode 100755 index 47246a80a..000000000 --- a/tool/history.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -git log --pretty=format:"%h%x09%an%x09%ad%x09%s" \ No newline at end of file diff --git a/windowsApplicationInstallerSetup.iss b/windowsApplicationInstallerSetup.iss index bc4d31ed2..1b03910b4 100644 --- a/windowsApplicationInstallerSetup.iss +++ b/windowsApplicationInstallerSetup.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "flutter_map Demo" -#define MyAppVersion "4.0.0" +#define MyAppVersion "for 5.0.0" #define MyAppPublisher "fleaflet" #define MyAppURL "https://github.com/fleaflet/flutter_map" #define MyAppSupportURL "https://github.com/fleaflet/flutter_map/issues"