diff --git a/example/.metadata b/example/.metadata index b8041ab28..31ae8bd5c 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,8 +4,8 @@ # This file should be version controlled and should not be manually edited. version: - revision: "53c27e519d33b4e13b01a8710b38a3591d6ca6f1" - channel: "beta" + revision: "463e7516406b4ba7814703616da0e475523db54c" + channel: "master" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 53c27e519d33b4e13b01a8710b38a3591d6ca6f1 - base_revision: 53c27e519d33b4e13b01a8710b38a3591d6ca6f1 - - platform: windows - create_revision: 53c27e519d33b4e13b01a8710b38a3591d6ca6f1 - base_revision: 53c27e519d33b4e13b01a8710b38a3591d6ca6f1 + create_revision: 463e7516406b4ba7814703616da0e475523db54c + base_revision: 463e7516406b4ba7814703616da0e475523db54c + - platform: ios + create_revision: 463e7516406b4ba7814703616da0e475523db54c + base_revision: 463e7516406b4ba7814703616da0e475523db54c # User provided section diff --git a/example/ios/.gitignore b/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9367d483e..7c5696400 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index ec97fc6f3..592ceee85 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index c4855bfe2..592ceee85 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile deleted file mode 100644 index 88359b225..000000000 --- a/example/ios/Podfile +++ /dev/null @@ -1,41 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '11.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index ab9fc9ecf..ee8e72be9 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,12 +3,12 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ - 127B2300FA5CE87549434CC2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A72EECE5117AE803156E244 /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -16,6 +16,16 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -32,9 +42,9 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 3A72EECE5117AE803156E244 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 4928082F7E2BC0BADA4A6955 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -45,8 +55,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - CF9AFCBF84244C1D44C2E795 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - E1DF330ADC8450B8B567025E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,30 +62,18 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 127B2300FA5CE87549434CC2 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 13FB0627975E846BA76A343C /* Pods */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( - CF9AFCBF84244C1D44C2E795 /* Pods-Runner.debug.xcconfig */, - 4928082F7E2BC0BADA4A6955 /* Pods-Runner.release.xcconfig */, - E1DF330ADC8450B8B567025E /* Pods-Runner.profile.xcconfig */, + 331C807B294A618700263BE5 /* RunnerTests.swift */, ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - 1DB0744A0D67D7FBC7C14ADA /* Frameworks */ = { - isa = PBXGroup; - children = ( - 3A72EECE5117AE803156E244 /* Pods_Runner.framework */, - ); - name = Frameworks; + path = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { @@ -97,8 +93,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 13FB0627975E846BA76A343C /* Pods */, - 1DB0744A0D67D7FBC7C14ADA /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -106,6 +101,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -128,18 +124,33 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 2655651DCB6AEB0882B90CC1 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - C1C85694F6D3CCB2E45E23F9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -156,9 +167,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -179,11 +195,19 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -198,34 +222,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 2655651DCB6AEB0882B90CC1 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -236,6 +240,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -248,26 +253,17 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - C1C85694F6D3CCB2E45E23F9 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -279,6 +275,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -303,6 +307,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -332,6 +337,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -340,7 +346,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -358,8 +364,11 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.jpryan.flutterMapExample; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.fleaflet.fluttermap.flutterMapExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -367,10 +376,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.fleaflet.fluttermap.flutterMapExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.fleaflet.fluttermap.flutterMapExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.fleaflet.fluttermap.flutterMapExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -400,6 +457,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -414,7 +472,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -426,6 +484,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -455,6 +514,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -463,11 +523,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -482,8 +543,11 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.jpryan.flutterMapExample; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.fleaflet.fluttermap.flutterMapExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -501,8 +565,11 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.jpryan.flutterMapExample; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.fleaflet.fluttermap.flutterMapExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -513,6 +580,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..e3773d42e 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + + + + + @@ -61,8 +73,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - - - diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4a8..626664468 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png deleted file mode 100644 index 861963cda..000000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png deleted file mode 100644 index 0d83bfc31..000000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png deleted file mode 100644 index 7ef45797d..000000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png deleted file mode 100644 index fd1f03a7b..000000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png deleted file mode 100644 index d48a9b98d..000000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and /dev/null differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png deleted file mode 100644 index 980d106b5..000000000 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and /dev/null differ diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 7f2c8fc99..7680919e1 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + flutter_map Demo CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 4eb4d17f2..ab778424d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -9,6 +9,7 @@ import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/fallback_url_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/inverted_polygons.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; import 'package:flutter_map_example/pages/many_circles.dart'; import 'package:flutter_map_example/pages/many_markers.dart'; @@ -68,6 +69,7 @@ class MyApp extends StatelessWidget { OverlayImagePage.route: (context) => const OverlayImagePage(), PolygonPage.route: (context) => const PolygonPage(), MultiWorldsPage.route: (context) => const MultiWorldsPage(), + InvertedPolygonsPage.route: (context) => const InvertedPolygonsPage(), PolygonPerfStressPage.route: (context) => const PolygonPerfStressPage(), SlidingMapPage.route: (_) => const SlidingMapPage(), WMSLayerPage.route: (context) => const WMSLayerPage(), diff --git a/example/lib/pages/inverted_polygons.dart b/example/lib/pages/inverted_polygons.dart new file mode 100644 index 000000000..b030ecfb2 --- /dev/null +++ b/example/lib/pages/inverted_polygons.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_example/misc/tile_providers.dart'; +import 'package:flutter_map_example/widgets/drawer/menu_drawer.dart'; +import 'package:latlong2/latlong.dart'; + +/// Example dedicated to [PolygonLayer.invertedFill] +class InvertedPolygonsPage extends StatefulWidget { + static const String route = '/inverted_polygons'; + + const InvertedPolygonsPage({super.key}); + + @override + State createState() => _InvertedPolygonsPageState(); +} + +class _InvertedPolygonsPageState extends State { + final LayerHitNotifier _hitNotifier = ValueNotifier(null); + + final _customMarkers = []; + + Marker _buildPin(LatLng point) => Marker( + point: point, + width: 60, + height: 60, + child: GestureDetector( + onTap: () => ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Tapped existing marker'), + duration: Duration(seconds: 1), + showCloseIcon: true, + ), + ), + child: const Icon(Icons.location_pin, color: Colors.red), + ), + ); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Advanced polygons')), + drawer: const MenuDrawer(InvertedPolygonsPage.route), + body: Stack( + children: [ + FlutterMap( + options: MapOptions( + initialCenter: const LatLng(45.5, 2), + initialZoom: 0, + initialRotation: 0, + onTap: (_, p) => setState(() => _customMarkers.add(_buildPin(p))), + ), + children: [ + openStreetMapTileLayer, + GestureDetector( + onTap: () => ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(_hitNotifier.value!.hitValues.join(', ')), + duration: const Duration(seconds: 1), + showCloseIcon: true, + ), + ), + child: PolygonLayer( + hitNotifier: _hitNotifier, + simplificationTolerance: 0, + useAltRendering: false, + drawLabelsLast: false, + invertedFill: const Color(0x20FF0000), + polygons: [ + Polygon( + rotateLabel: false, + borderColor: const Color(0xFF0000FF), + borderStrokeWidth: 3, + color: const Color(0x4000FFFF), + hitValue: 'France', + points: const [ + // France + // Calais 50° 56′ 53″ nord, 1° 51′ 23″ est + LatLng(50.948056, 1.856389), + // Brest 48° 23′ 27″ nord, 4° 29′ 08″ ouest + LatLng(48.390833, -4.485556), + // Biarritz 43° 28′ 54″ nord, 1° 33′ 22″ ouest + LatLng(43.481667, -1.556111), + // Perpignan 42° 41′ 55″ nord, 2° 53′ 44″ est + LatLng(42.698611, 2.895556), + // Menton 43° 46′ 33″ nord, 7° 30′ 10″ est + LatLng(43.775833, 7.502778), + // Strasbourg 48° 34′ 24″ nord, 7° 45′ 08″ est + LatLng(48.573333, 7.752222), + ], + ), + Polygon( + rotateLabel: false, + borderColor: const Color(0xFFFF0000), + borderStrokeWidth: 3, + pattern: StrokePattern.dashed(segments: [20, 10, 10, 10]), + color: null, + hitValue: 'South America', + points: const [ + // South America + // Ushuaia 54° 48′ 35″ sud, 68° 18′ 50″ ouest + LatLng(-54.809722, -68.313889), + // Fortaleza 3° 43′ 01″ sud, 38° 32′ 34″ ouest + LatLng(-3.716944, -38.542778), + // Panama 8° 58′ nord, 79° 32′ ouest + LatLng(8.966667, -79.533333), + // Quito 0° 14′ 18″ sud, 78° 31′ 02″ ouest + LatLng(-0.238333, -78.517222), + ], + ), + Polygon( + rotateLabel: false, + borderColor: const Color(0xFF00FF00), + borderStrokeWidth: 3, + pattern: const StrokePattern.dotted(spacingFactor: 3), + color: const Color(0x20000000), + hitValue: 'Across the border', + points: const [ + // Across the border + LatLng(26.69, -137.39), + LatLng(37.91, 150.65), + LatLng(-7.67, 151.20), + LatLng(-7.32, -140.78), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/example/lib/pages/polygon.dart b/example/lib/pages/polygon.dart index 02717514e..02f646041 100644 --- a/example/lib/pages/polygon.dart +++ b/example/lib/pages/polygon.dart @@ -4,6 +4,7 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_example/misc/tile_providers.dart'; import 'package:flutter_map_example/widgets/drawer/menu_drawer.dart'; import 'package:latlong2/latlong.dart'; +import 'package:url_launcher/url_launcher.dart'; typedef HitValue = ({String title, String subtitle}); @@ -21,6 +22,8 @@ class _PolygonPageState extends State { List? _prevHitValues; List>? _hoverGons; + bool _useInvertedFill = false; + final _polygonsRaw = >[ Polygon( points: const [ @@ -108,27 +111,26 @@ class _PolygonPageState extends State { pattern: const StrokePattern.dotted(), holePointsList: [ const [ - LatLng(52, -17), - LatLng(52, -16), - LatLng(51.5, -15.5), - LatLng(51, -16), - LatLng(51, -17), + LatLng(52, -9), + LatLng(52, -8), + LatLng(51.5, -7.5), + LatLng(51, -8), + LatLng(51, -9), ], const [ - LatLng(53.5, -17), - LatLng(53.5, -16), - LatLng(53, -15), - LatLng(52.25, -15), - LatLng(52.25, -16), - LatLng(52.75, -17), + LatLng(53.5, -9), + LatLng(53.5, -8), + LatLng(53, -7), + LatLng(52.25, -7), + LatLng(52.25, -8), + LatLng(52.75, -9), ], - ] - .map( - (latlngs) => latlngs - .map((latlng) => LatLng(latlng.latitude, latlng.longitude + 8)) - .toList(), - ) - .toList(), + const [ + LatLng(52.683614, -8.141285), + LatLng(51.663083, -8.684529), + LatLng(51.913924, -7.2193), + ], + ], borderStrokeWidth: 4, borderColor: Colors.orange, color: Colors.orange.withAlpha(128), @@ -154,30 +156,26 @@ class _PolygonPageState extends State { pattern: const StrokePattern.dotted(), holePointsList: [ const [ - LatLng(52, -17), - LatLng(52, -16), - LatLng(51.5, -15.5), - LatLng(51, -16), - LatLng(51, -17), - ], + LatLng(46, -9), + LatLng(46, -8), + LatLng(45.5, -7.5), + LatLng(45, -8), + LatLng(45, -9), + ].reversed.toList(growable: false), // Testing winding consitency const [ - LatLng(53.5, -17), - LatLng(53.5, -16), - LatLng(53, -15), - LatLng(52.25, -15), - LatLng(52.25, -16), - LatLng(52.75, -17), - ], - ] - .map( - (latlngs) => latlngs - .map((latlng) => - LatLng(latlng.latitude - 6, latlng.longitude + 8)) - .toList() - .reversed // Test that holes are always cut, no matter winding - .toList(), - ) - .toList(), + LatLng(47.5, -9), + LatLng(47.5, -8), + LatLng(47, -7), + LatLng(46.25, -7), + LatLng(46.25, -8), + LatLng(46.75, -9), + ].reversed.toList(growable: false), + const [ + LatLng(46.683614, -8.141285), + LatLng(45.663083, -8.684529), + LatLng(45.913924, -7.2193), + ].reversed.toList(growable: false), + ], borderStrokeWidth: 4, borderColor: Colors.orange, color: Colors.orange.withAlpha(128), @@ -255,6 +253,35 @@ class _PolygonPageState extends State { "Holes shouldn't be cut, and colors should be mixed correctly", ), ), + Polygon( + points: const [ + LatLng(40, 150), + LatLng(45, 160), + LatLng(50, 170), + LatLng(55, 180), + LatLng(50, -170), + LatLng(45, -160), + LatLng(40, -150), + LatLng(35, -160), + LatLng(30, -170), + LatLng(25, -180), + LatLng(30, 170), + LatLng(35, 160), + ], + holePointsList: const [ + [ + LatLng(45, 175), + LatLng(45, -175), + LatLng(35, -175), + LatLng(35, 175), + ], + ], + color: const Color(0xFFFF0000), + hitValue: ( + title: 'Red Line', + subtitle: 'Across the universe...', + ), + ), ]; late final _polygons = Map.fromEntries(_polygonsRaw.map((e) => MapEntry(e.hitValue, e))); @@ -268,7 +295,7 @@ class _PolygonPageState extends State { children: [ FlutterMap( options: const MapOptions( - initialCenter: LatLng(51.5, -0.09), + initialCenter: LatLng(51.5, -2), initialZoom: 5, ), children: [ @@ -320,6 +347,8 @@ class _PolygonPageState extends State { child: PolygonLayer( hitNotifier: _hitNotifier, simplificationTolerance: 0, + invertedFill: + _useInvertedFill ? Colors.pink.withAlpha(170) : null, polygons: [..._polygonsRaw, ...?_hoverGons], ), ), @@ -328,35 +357,6 @@ class _PolygonPageState extends State { simplificationTolerance: 0, useAltRendering: true, polygons: [ - Polygon( - points: const [ - LatLng(40, 150), - LatLng(45, 160), - LatLng(50, 170), - LatLng(55, 180), - LatLng(50, -170), - LatLng(45, -160), - LatLng(40, -150), - LatLng(35, -160), - LatLng(30, -170), - LatLng(25, -180), - LatLng(30, 170), - LatLng(35, 160), - ], - holePointsList: const [ - [ - LatLng(45, 175), - LatLng(45, -175), - LatLng(35, -175), - LatLng(35, 175), - ], - ], - color: const Color(0xFFFF0000), - hitValue: ( - title: 'Red Line', - subtitle: 'Across the universe...', - ), - ), Polygon( points: const [ LatLng(50, -18), @@ -385,50 +385,117 @@ class _PolygonPageState extends State { borderStrokeWidth: 4, borderColor: Colors.black, color: Colors.green, + label: + 'This one is performantly rendered\n& non-interactive', ), Polygon( points: const [ - LatLng(50, -18), - LatLng(53, -16), - LatLng(51.5, -12.5), - LatLng(54, -14), - LatLng(54, -18), - ] - .map((latlng) => - LatLng(latlng.latitude - 6, latlng.longitude)) - .toList(), + LatLng(44, -18), + LatLng(47, -16), + LatLng(45.5, -12.5), + LatLng(48, -14), + LatLng(48, -18), + ], holePointsList: [ const [ - LatLng(52, -17), - LatLng(52, -16), - LatLng(51.5, -15.5), - LatLng(51, -16), - LatLng(51, -17), + LatLng(46, -17), + LatLng(46, -16), + LatLng(45.5, -15.5), + LatLng(45, -16), + LatLng(45, -17), ], const [ - LatLng(53.5, -17), - LatLng(53.5, -16), - LatLng(53, -15), - LatLng(52.25, -15), - LatLng(52.25, -16), - LatLng(52.75, -17), + LatLng(47.5, -17), + LatLng(47.5, -16), + LatLng(47, -15), + LatLng(46.25, -15), + LatLng(46.25, -16), + LatLng(46.75, -17), ], - ] - .map( - (latlngs) => latlngs - .map((latlng) => - LatLng(latlng.latitude - 6, latlng.longitude)) - .toList(), - ) - .toList(), + const [ + LatLng(46.683614, -16.141285), + LatLng(45.663083, -16.684529), + LatLng(45.913924, -15.2193), + ].reversed.toList(growable: false), + ], borderStrokeWidth: 4, borderColor: Colors.black, color: Colors.green, + label: + "Performant-rendering doesn't\nhandle malformed polygons", ), ], ), ], ), + Positioned( + top: 16, + right: 16, + child: ClipRRect( + borderRadius: BorderRadius.circular(kIsWeb ? 16 : 32), + child: ColoredBox( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 8, + top: 4, + bottom: 4, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + spacing: 8, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Tooltip( + message: 'Use Inverted Fill', + child: Icon(Icons.invert_colors), + ), + Switch.adaptive( + value: _useInvertedFill, + onChanged: (v) => + setState(() => _useInvertedFill = v), + ), + ], + ), + ), + if (kIsWeb) + ColoredBox( + color: Colors.amber, + child: Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 6, + bottom: 6, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + const Icon(Icons.warning), + const Icon(Icons.web_asset_off), + IconButton( + onPressed: () => launchUrl(Uri.parse( + 'https://docs.fleaflet.dev/layers/polygon-layer#inverted-filling', + )), + style: ButtonStyle( + backgroundColor: + WidgetStatePropertyAll(Colors.amber[100]), + ), + icon: const Icon(Icons.open_in_new), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), ], ), ); diff --git a/example/lib/widgets/drawer/menu_drawer.dart b/example/lib/widgets/drawer/menu_drawer.dart index 5d130c072..ad6563476 100644 --- a/example/lib/widgets/drawer/menu_drawer.dart +++ b/example/lib/widgets/drawer/menu_drawer.dart @@ -10,6 +10,7 @@ import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/fallback_url_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/inverted_polygons.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; import 'package:flutter_map_example/pages/many_circles.dart'; import 'package:flutter_map_example/pages/many_markers.dart'; @@ -178,6 +179,11 @@ class MenuDrawer extends StatelessWidget { routeName: ManyCirclesPage.route, currentRoute: currentRoute, ), + MenuItemWidget( + caption: 'Inverted Polygons', + routeName: InvertedPolygonsPage.route, + currentRoute: currentRoute, + ), const Divider(), MenuItemWidget( caption: 'Zoom Buttons Plugin', diff --git a/lib/src/layer/polygon_layer/painter.dart b/lib/src/layer/polygon_layer/painter.dart index bbfbea9a0..8406fa299 100644 --- a/lib/src/layer/polygon_layer/painter.dart +++ b/lib/src/layer/polygon_layer/painter.dart @@ -8,6 +8,9 @@ class _PolygonPainter extends CustomPainter /// Reference to the list of [_ProjectedPolygon]s final List<_ProjectedPolygon> polygons; + @override + Iterable<_ProjectedPolygon> get elements => polygons; + /// Triangulated [polygons] if available /// /// Expected to be in same/corresponding order as [polygons]. @@ -35,6 +38,9 @@ class _PolygonPainter extends CustomPainter /// See [PolygonLayer.debugAltRenderer] final bool debugAltRenderer; + /// See [PolygonLayer.invertedFill] + final Color? invertedFill; + @override final MapCamera camera; @@ -49,9 +55,41 @@ class _PolygonPainter extends CustomPainter required this.drawLabelsLast, required this.debugAltRenderer, required this.camera, + required this.invertedFill, required this.hitNotifier, }) : bounds = camera.visibleBounds; + /// Corner coordinates of the polygon painted onto the entire world when using + /// inverted fill. + static const _minMaxLatitude = [LatLng(90, 0), LatLng(-90, 0)]; + + /// Whether to use `PathFillType.evenOdd` (true) or `Path.combine` (false). + /// + /// * `Path.combine` doesn't work & isn't stable/consistent on web + /// * `evenOdd` gives broken results when polygons intersect when inverted + /// * `Path.combine` has slightly worse performance than `evenOdd` + /// + /// The best option is to use `evenOdd` on web, as it at least works + /// sometimes. On native, we use `Path.combine` when inverted filling, or + /// `evenOdd` otherwise. + /// + /// See https://github.com/fleaflet/flutter_map/pull/2046. + late final _useEvenOdd = kIsWeb || invertedFill == null; + + /// Do we also remove the holes from the inverted map? + /// Should be `true`. + static const _invertedHoles = true; + + /// Do we also fill the holes with inverted fill? + /// Should be `true`. + static const _fillInvertedHoles = true; + + /// Whether to draw the batch of polygons when a polygon with translucency is + /// encountered. + /// Should be `true`. + // TODO: Verify if still necessary. + static const _flushBatchOnTranslucency = true; + @override bool elementHitTest( _ProjectedPolygon projectedPolygon, { @@ -112,101 +150,103 @@ class _PolygonPainter extends CustomPainter return workAcrossWorlds(checkIfHit); } - @override - Iterable<_ProjectedPolygon> get elements => polygons; - @override void paint(Canvas canvas, Size size) { - const checkOpacity = true; // for debugging purposes only, should be true super.paint(canvas, size); final trianglePoints = []; - final filledPath = Path(); + Path filledPath = Path(); + Path invertedHolePaths = Path(); final borderPath = Path(); - Polygon? lastPolygon; + Color? lastColor; int? lastHash; + Paint? borderPaint; + + /// Draw polygon outline + void drawBorders() { + if (borderPaint != null) { + canvas.drawPath(borderPath, borderPaint); + } + borderPath.reset(); + lastHash = null; + } // This functions flushes the batched fill and border paths constructed below void drawPaths() { - if (lastPolygon == null) return; - final polygon = lastPolygon!; + final Color? color = lastColor; + if (color == null) { + drawBorders(); + return; + } // Draw filled polygon - if (polygon.color case final color?) { - final paint = Paint() - ..style = PaintingStyle.fill - ..color = color; - - if (trianglePoints.isNotEmpty) { - final points = Float32List(trianglePoints.length * 2); - for (int i = 0; i < trianglePoints.length; ++i) { - points[i * 2] = trianglePoints[i].dx; - points[i * 2 + 1] = trianglePoints[i].dy; - } - final vertices = Vertices.raw(VertexMode.triangles, points); - canvas.drawVertices(vertices, BlendMode.src, paint); - - if (debugAltRenderer) { - for (int i = 0; i < trianglePoints.length; i += 3) { - canvas.drawCircle( - trianglePoints[i], - 5, - Paint()..color = const Color(0x7EFF0000), - ); - canvas.drawCircle( - trianglePoints[i + 1], - 5, - Paint()..color = const Color(0x7E00FF00), - ); - canvas.drawCircle( - trianglePoints[i + 2], - 5, - Paint()..color = const Color(0x7E0000FF), - ); + final paint = Paint() + ..style = PaintingStyle.fill + ..color = color; + + if (trianglePoints.isNotEmpty) { + final points = Float32List(trianglePoints.length * 2); + for (int i = 0; i < trianglePoints.length; ++i) { + points[i * 2] = trianglePoints[i].dx; + points[i * 2 + 1] = trianglePoints[i].dy; + } + final vertices = Vertices.raw(VertexMode.triangles, points); + canvas.drawVertices(vertices, BlendMode.src, paint); - final path = Path() - ..addPolygon( - [ - trianglePoints[i], - trianglePoints[i + 1], - trianglePoints[i + 2], - ], - true, - ); - - canvas.drawPath( - path, - Paint() - ..color = const Color(0x7EFFFFFF) - ..style = PaintingStyle.fill, - ); + if (debugAltRenderer) { + for (int i = 0; i < trianglePoints.length; i += 3) { + canvas.drawCircle( + trianglePoints[i], + 5, + Paint()..color = const Color(0x7EFF0000), + ); + canvas.drawCircle( + trianglePoints[i + 1], + 5, + Paint()..color = const Color(0x7E00FF00), + ); + canvas.drawCircle( + trianglePoints[i + 2], + 5, + Paint()..color = const Color(0x7E0000FF), + ); - canvas.drawPath( - path, - Paint() - ..color = const Color(0xFF000000) - ..style = PaintingStyle.stroke, + final path = Path() + ..addPolygon( + [ + trianglePoints[i], + trianglePoints[i + 1], + trianglePoints[i + 2], + ], + true, ); - } + + canvas.drawPath( + path, + Paint() + ..color = const Color(0x7EFFFFFF) + ..style = PaintingStyle.fill, + ); + + canvas.drawPath( + path, + Paint() + ..color = const Color(0xFF000000) + ..style = PaintingStyle.stroke, + ); } - } else { - canvas.drawPath(filledPath, paint); } - } - - // Draw polygon outline - if (polygon.borderStrokeWidth > 0) { - canvas.drawPath(borderPath, _getBorderPaint(polygon)); + } else { + canvas.drawPath(filledPath, paint); } trianglePoints.clear(); filledPath.reset(); - borderPath.reset(); + lastColor = null; - lastPolygon = null; - lastHash = null; + drawBorders(); } /// Draws labels on a "single-world" @@ -238,11 +278,106 @@ class _PolygonPainter extends CustomPainter return WorldWorkControl.visible; } - // Main loop constructing batched fill and border paths from given polygons. + void invertFillPolygonHole(List offsets) { + // For debugging purposes, should be compiled out + // ignore: dead_code + if (!_fillInvertedHoles) return; + + if (_useEvenOdd) { + invertedHolePaths.addPolygon(offsets, true); + return; + } + invertedHolePaths = Path.combine( + PathOperation.union, + invertedHolePaths, + Path()..addPolygon(offsets, true), + ); + } + + void unfillPolygon(List offsets) { + if (_useEvenOdd) { + filledPath.fillType = PathFillType.evenOdd; + filledPath.addPolygon(offsets, true); + return; + } + filledPath = Path.combine( + PathOperation.difference, + filledPath, + Path()..addPolygon(offsets, true), + ); + } + + // Specific map treatment with `invertFill`. + if (invertedFill != null) { + filledPath.reset(); + final minMaxProjected = + camera.crs.projection.projectList(_minMaxLatitude); + final minMaxY = getOffsetsXY( + camera: camera, + origin: origin, + points: minMaxProjected, + ); + final maxX = viewportRect.right; + final minX = viewportRect.left; + final maxY = minMaxY[0].dy; + final minY = minMaxY[1].dy; + final rect = Rect.fromLTRB(minX, minY, maxX, maxY); + filledPath.addRect(rect); + + for (int i = 0; i <= polygons.length - 1; i++) { + final projectedPolygon = polygons[i]; + if (projectedPolygon.points.isEmpty) continue; + + /// Draws each full polygon as a hole on a "single-world" + WorldWorkControl drawPolygonAsHole(double shift) { + final fillOffsets = getOffsetsXY( + camera: camera, + origin: origin, + points: projectedPolygon.points, + shift: shift, + ); + if (!areOffsetsVisible(fillOffsets)) { + return WorldWorkControl.invisible; + } + + unfillPolygon(fillOffsets); + + if (_invertedHoles) { + for (final singleHolePoints in projectedPolygon.holePoints) { + final holeOffsets = getOffsetsXY( + camera: camera, + origin: origin, + points: singleHolePoints, + shift: shift, + ); + unfillPolygon(holeOffsets); + invertFillPolygonHole(holeOffsets); + } + } + return WorldWorkControl.visible; + } + + workAcrossWorlds(drawPolygonAsHole); + } + + // Draw filled map with polygons as holes + final paint = Paint() + ..style = PaintingStyle.fill + ..color = invertedFill!; + + canvas.drawPath(filledPath, paint); + if (_fillInvertedHoles) { + canvas.drawPath(invertedHolePaths, paint); + } + + filledPath.reset(); + } + for (int i = 0; i <= polygons.length - 1; i++) { final projectedPolygon = polygons[i]; - if (projectedPolygon.points.isEmpty) continue; final polygon = projectedPolygon.polygon; + if (projectedPolygon.points.isEmpty) continue; + borderPaint = _getBorderPaint(polygon); final polygonTriangles = triangles?[i]; @@ -256,7 +391,9 @@ class _PolygonPainter extends CustomPainter polygonTriangles != null ? projectedPolygon.holePoints : null, shift: shift, ); - if (!areOffsetsVisible(fillOffsets)) return WorldWorkControl.invisible; + if (!areOffsetsVisible(fillOffsets)) { + return WorldWorkControl.invisible; + } if (debugAltRenderer) { const offsetsLabelStyle = TextStyle( @@ -285,10 +422,11 @@ class _PolygonPainter extends CustomPainter // depending on the holes handler. final hash = polygon.renderHashCode; final opacity = polygon.color?.a ?? 0; - if (lastHash != hash || (checkOpacity && opacity > 0 && opacity < 1)) { + if (lastHash != hash || + (_flushBatchOnTranslucency && opacity > 0 && opacity < 1)) { drawPaths(); } - lastPolygon = polygon; + lastColor = polygon.color; lastHash = hash; // First add fills and borders to path. @@ -303,20 +441,23 @@ class _PolygonPainter extends CustomPainter } } - if (polygon.borderStrokeWidth > 0.0) { - _addBorderToPath( - borderPath, - polygon, + void addBorderToPath(List offsets) => _addBorderToPath( + borderPath, + polygon, + offsets, + size, + canvas, + borderPaint!, + ); + + if (borderPaint != null) { + addBorderToPath( getOffsetsXY( camera: camera, origin: origin, points: projectedPolygon.points, shift: shift, ), - size, - canvas, - _getBorderPaint(polygon), - polygon.borderStrokeWidth, ); } @@ -324,52 +465,16 @@ class _PolygonPainter extends CustomPainter // Improper handling of opacity and fill methods may result in normal // polygons cutting holes into other polygons, when they should be mixing: // https://github.com/fleaflet/flutter_map/issues/1898. - final holePointsList = polygon.holePointsList; - if (holePointsList != null && holePointsList.isNotEmpty) { - // See `Path.combine` comments below - // Avoids failing to cut holes if the winding directions of the holes - // and the normal points are the same - filledPath.fillType = PathFillType.evenOdd; - - for (final singleHolePoints in projectedPolygon.holePoints) { - final holeOffsets = getOffsetsXY( - camera: camera, - origin: origin, - points: singleHolePoints, - shift: shift, - ); - filledPath.addPolygon(holeOffsets, true); - - // TODO: Potentially more efficient and may change the need to do - // opacity checking - needs testing. Also need to verify if `xor` or - // `difference` is preferred. - // No longer blocked by lack of HTML support in Flutter 3.29 - /*filledPath = Path.combine( - PathOperation.xor, - filledPath, - Path()..addPolygon(holeOffsets, true), - );*/ - } - - if (!polygon.disableHolesBorder && polygon.borderStrokeWidth > 0.0) { - final borderPaint = _getBorderPaint(polygon); - for (final singleHolePoints in projectedPolygon.holePoints) { - final holeOffsets = getOffsetsXY( - camera: camera, - origin: origin, - points: singleHolePoints, - shift: shift, - ); - _addBorderToPath( - borderPath, - polygon, - holeOffsets, - size, - canvas, - borderPaint, - polygon.borderStrokeWidth, - ); - } + for (final singleHolePoints in projectedPolygon.holePoints) { + final holeOffsets = getOffsetsXY( + camera: camera, + origin: origin, + points: singleHolePoints, + shift: shift, + ); + unfillPolygon(holeOffsets); + if (!polygon.disableHolesBorder && borderPaint != null) { + addBorderToPath(holeOffsets); } } @@ -392,10 +497,9 @@ class _PolygonPainter extends CustomPainter (double shift) => drawLabelIfVisible(shift, projectedPolygon), ); } + drawPaths(); } - drawPaths(); - if (polygonLabels && drawLabelsLast) { for (final projectedPolygon in polygons) { if (projectedPolygon.points.isEmpty) { @@ -411,7 +515,10 @@ class _PolygonPainter extends CustomPainter } } - Paint _getBorderPaint(Polygon polygon) { + Paint? _getBorderPaint(Polygon polygon) { + if (polygon.borderStrokeWidth <= 0) { + return null; + } final isDotted = polygon.pattern.spacingFactor != null; return Paint() ..color = polygon.borderColor @@ -428,11 +535,11 @@ class _PolygonPainter extends CustomPainter Size canvasSize, Canvas canvas, Paint paint, - double strokeWidth, ) { final isSolid = polygon.pattern == const StrokePattern.solid(); final isDashed = polygon.pattern.segments != null; final isDotted = polygon.pattern.spacingFactor != null; + final strokeWidth = polygon.borderStrokeWidth; if (isSolid) { final SolidPixelHiker hiker = SolidPixelHiker( @@ -445,7 +552,7 @@ class _PolygonPainter extends CustomPainter } else if (isDotted) { final DottedPixelHiker hiker = DottedPixelHiker( offsets: offsets, - stepLength: polygon.borderStrokeWidth * polygon.pattern.spacingFactor!, + stepLength: strokeWidth * polygon.pattern.spacingFactor!, patternFit: polygon.pattern.patternFit!, closePath: true, canvasSize: canvasSize, @@ -485,6 +592,7 @@ class _PolygonPainter extends CustomPainter triangles != oldDelegate.triangles || camera != oldDelegate.camera || bounds != oldDelegate.bounds || + invertedFill != oldDelegate.invertedFill || drawLabelsLast != oldDelegate.drawLabelsLast || polygonLabels != oldDelegate.polygonLabels || hitNotifier != oldDelegate.hitNotifier; diff --git a/lib/src/layer/polygon_layer/polygon_layer.dart b/lib/src/layer/polygon_layer/polygon_layer.dart index 6cf7172b5..dfddb6d31 100644 --- a/lib/src/layer/polygon_layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer/polygon_layer.dart @@ -15,6 +15,7 @@ import 'package:flutter_map/src/misc/offsets.dart'; import 'package:flutter_map/src/misc/point_in_polygon.dart'; import 'package:flutter_map/src/misc/simplify.dart'; import 'package:latlong2/latlong.dart' hide Path; +import 'package:logger/logger.dart'; import 'package:polylabel/polylabel.dart'; part 'label.dart'; @@ -66,6 +67,17 @@ base class PolygonLayer /// Defaults to `false`. final bool drawLabelsLast; + /// Color to apply to the map where not covered by a polygon + /// + /// > [!WARNING] + /// > On the web, inverted filling may not work as expected in some cases. + /// > It will not match the behaviour seen on native platforms. Avoid allowing + /// > polygons to intersect, and avoid using holes within polygons. + /// > This is due to multiple limitations/bugs within Flutter. See the + /// > [online documentation](docs.fleaflet.dev/layers/polygon-layer#inverted-filling) + /// > for more info. + final Color? invertedFill; + /// {@macro fm.lhn.layerHitNotifier.usage} final LayerHitNotifier? hitNotifier; @@ -78,6 +90,7 @@ base class PolygonLayer this.polygonCulling = true, this.polygonLabels = true, this.drawLabelsLast = false, + this.invertedFill, this.hitNotifier, super.simplificationTolerance, }) : super(); @@ -90,6 +103,25 @@ class _PolygonLayerState extends State> with ProjectionSimplificationManagement<_ProjectedPolygon, Polygon, PolygonLayer> { + @override + void didUpdateWidget(covariant PolygonLayer oldWidget) { + super.didUpdateWidget(oldWidget); + + if (kDebugMode && + kIsWeb && + oldWidget.invertedFill == null && + widget.invertedFill != null) { + Logger(printer: PrettyPrinter(methodCount: 0)).w( + '\x1B[1m\x1B[3mflutter_map\x1B[0m\nOn the web, inverted filling may ' + 'not work as expected in some cases. It will not match the behaviour\n' + 'seen on native platforms.\nAvoid allowing polygons to intersect, and ' + 'avoid using holes within polygons.\nThis is due to multiple ' + 'limitations/bugs within Flutter.\nSee ' + 'https://docs.fleaflet.dev/layers/polyline-layer#culling for more info.', + ); + } + } + @override _ProjectedPolygon projectElement({ required Projection projection, @@ -174,6 +206,7 @@ class _PolygonLayerState extends State> camera: camera, polygonLabels: widget.polygonLabels, drawLabelsLast: widget.drawLabelsLast, + invertedFill: widget.invertedFill, debugAltRenderer: widget.debugAltRenderer, hitNotifier: widget.hitNotifier, ), diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index f46b981b4..75d7df43d 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -29,8 +29,9 @@ base class PolylineLayer /// Acceptable extent outside of viewport before culling polyline segments /// /// May need to be increased if the [Polyline.strokeWidth] + - /// [Polyline.borderStrokeWidth] is large. See online documentation for more - /// information. + /// [Polyline.borderStrokeWidth] is large. See the + /// [online documentation](https://docs.fleaflet.dev/layers/polyline-layer#culling) + /// for more info. /// /// Defaults to 10. Set to `null` to disable culling. final double? cullingMargin; diff --git a/lib/src/layer/shared/layer_interactivity/layer_hit_notifier.dart b/lib/src/layer/shared/layer_interactivity/layer_hit_notifier.dart index 076d118bc..50b2d1474 100644 --- a/lib/src/layer/shared/layer_interactivity/layer_hit_notifier.dart +++ b/lib/src/layer/shared/layer_interactivity/layer_hit_notifier.dart @@ -21,7 +21,7 @@ typedef LayerHitNotifier = ValueNotifier?>; /// /// Hit testing still occurs even if this is `null`. /// -/// See online documentation for more detailed usage instructions. See the +/// See the online documentation for more detailed usage instructions. See the /// example project for an example implementation. /// {@endtemplate} // ignore: unused_element, constant_identifier_names diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index 0bee21485..f980257ac 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -277,21 +277,11 @@ class TileLayer extends StatefulWidget { tileProvider = tileProvider ?? NetworkTileProvider(), tileUpdateTransformer = tileUpdateTransformer ?? TileUpdateTransformers.ignoreTapEvents { - // Debug Logging - if (kDebugMode && - urlTemplate != null && - urlTemplate!.contains('{s}.tile.openstreetmap.org')) { - Logger(printer: PrettyPrinter(methodCount: 0)).w( - '\x1B[1m\x1B[3mflutter_map\x1B[0m\nAvoid using subdomains with OSM\'s tile ' - 'server. Support may be become slow or be removed in future.\nSee ' - 'https://github.com/openstreetmap/operations/issues/737 for more info.', - ); - } if (kDebugMode && retinaMode == null && urlTemplate != null && urlTemplate!.contains('{r}')) { - Logger(printer: PrettyPrinter(methodCount: 0)).w( + Logger(printer: PrettyPrinter(methodCount: 0)).i( '\x1B[1m\x1B[3mflutter_map\x1B[0m\nThe URL template includes a retina ' "mode placeholder ('{r}') to retrieve native high-resolution\ntiles, " 'which improve appearance especially on high-density displays.\n' @@ -299,8 +289,7 @@ class TileLayer extends StatefulWidget { 'will never retrieve these tiles.\nConsider using ' '`RetinaMode.isHighDensity` to toggle this property automatically, ' 'otherwise ensure\nit is set appropriately.\n' - 'See https://docs.fleaflet.dev/layers/tile-layer#retina-mode for ' - 'more info.', + 'See https://docs.fleaflet.dev/layers/tile-layer for more info.', ); } if (kDebugMode && kIsWeb && tileProvider is NetworkTileProvider?) {