diff --git a/CHANGELOG.md b/CHANGELOG.md index afe44df8d..1bc5c5856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ -### main +### 2.25.0-rc.1 + +* Deprecate `MapboxMap.onTapListener` and `MapboxMap.onLongTapListener` in favor of the `MapboxMap.addInterfaction` API. +* Add `MapboxMap.httpService.setMaxRequestsPerHost` to cap the number of concurrent HTTP requests per host issued by the underlying HTTP service. Useful for reducing the chance of hitting per-token rate limits during offline tile region downloads. + +### 2.24.0-rc.1 * Add `TileStore.setOptionForKey` to allow setting custom tile store options by arbitrary string key, in addition to the existing predefined options (`diskQuota`, `mapboxApiUrl`, `tileUrlTemplate`). diff --git a/LICENSE b/LICENSE index d693897ea..dcd9a9d96 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ ## License -Mapbox Maps for Flutter version 2.24.0 +Mapbox Maps for Flutter version 2.25.0 Mapbox Maps Flutter SDK Copyright © 2022 - 2026 Mapbox, Inc. All rights reserved. @@ -194,6 +194,149 @@ https://www.mapbox.com/legal/tos/ --- +### libjpeg-turbo,3.1.4.1,IJG AND BSD-3-Clause AND Zlib,The libjpeg-turbo Project,https://libjpeg-turbo.org/ + +``` +libjpeg-turbo Licenses +====================== + +libjpeg-turbo is covered by two compatible BSD-style open source licenses: + +- The IJG (Independent JPEG Group) License, which is listed in + [README.ijg](README.ijg) + + This license applies to the libjpeg API library and associated programs, + including any code inherited from libjpeg and any modifications to that + code. Note that the libjpeg-turbo SIMD source code bears the + [zlib License](https://opensource.org/licenses/Zlib), but in the context of + the overall libjpeg API library, the terms of the zlib License are subsumed + by the terms of the IJG License. + +- The Modified (3-clause) BSD License, which is listed below + + This license applies to the TurboJPEG API library and associated programs, as + well as the build system. Note that the TurboJPEG API library wraps the + libjpeg API library, so in the context of the overall TurboJPEG API library, + both the terms of the IJG License and the terms of the Modified (3-clause) + BSD License apply. + + +Complying with the libjpeg-turbo Licenses +========================================= + +This section provides a roll-up of the libjpeg-turbo licensing terms, to the +best of our understanding. This is not a license in and of itself. It is +intended solely for clarification. + +1. If you are distributing a modified version of the libjpeg-turbo source, + then: + + 1. You cannot alter or remove any existing copyright or license notices + from the source. + + **Origin** + - Clause 1 of the IJG License + - Clause 1 of the Modified BSD License + - Clauses 1 and 3 of the zlib License + + 2. You must add your own copyright notice to the header of each source + file you modified, so others can tell that you modified that file. (If + there is not an existing copyright header in that file, then you can + simply add a notice stating that you modified the file.) + + **Origin** + - Clause 1 of the IJG License + - Clause 2 of the zlib License + + 3. You must include the IJG README file, and you must not alter any of the + copyright or license text in that file. + + **Origin** + - Clause 1 of the IJG License + +2. If you are distributing only libjpeg-turbo binaries without the source, or + if you are distributing an application that statically links with + libjpeg-turbo, then: + + 1. Your product documentation must include a message stating: + + This software is based in part on the work of the Independent JPEG + Group. + + **Origin** + - Clause 2 of the IJG license + + 2. If your binary distribution includes or uses the TurboJPEG API, then + your product documentation must include the text of the Modified BSD + License (see below.) + + **Origin** + - Clause 2 of the Modified BSD License + +3. You cannot use the name of the IJG or The libjpeg-turbo Project or the + contributors thereof in advertising, publicity, etc. + + **Origin** + - IJG License + - Clause 3 of the Modified BSD License + +4. The IJG and The libjpeg-turbo Project do not warrant libjpeg-turbo to be + free of defects, nor do we accept any liability for undesirable + consequences resulting from your use of the software. + + **Origin** + - IJG License + - Modified BSD License + - zlib License + + +The Modified (3-clause) BSD License +=================================== + +Copyright (C) 2009-2026 D. R. Commander. All Rights Reserved.
+Copyright (C) 2015 Viktor Szathmáry. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +- Neither the name of the libjpeg-turbo Project nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES LOSS OF USE, DATA, OR PROFITS OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Why Two Licenses? +================= + +The zlib License could have been used instead of the Modified (3-clause) BSD +License, and since the IJG License effectively subsumes the distribution +conditions of the zlib License, this would have effectively placed +libjpeg-turbo binary distributions under the IJG License. However, the IJG +License specifically refers to the Independent JPEG Group and does not extend +attribution and endorsement protections to other entities. Thus, it was +desirable to choose a license that granted us the same protections for new code +that were granted to the IJG for code derived from their software. + +``` + +--- + ### icu,75.1,Unicode-3.0,Unicode Inc,https://github.com/unicode-org/icu ``` diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 431329f6d..000000000 --- a/android/build.gradle +++ /dev/null @@ -1,73 +0,0 @@ -group 'com.mapbox.maps.mapbox_maps' -version '1.0.0' - -buildscript { - ext.kotlin_version = '1.8.22' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.10.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -rootProject.allprojects { - repositories { - google() - mavenCentral() - maven { - url 'https://api.mapbox.com/downloads/v2/releases/maven' - } - maven { - url 'https://api.mapbox.com/downloads/v2/snapshots/maven' - authentication { - basic(BasicAuthentication) - } - credentials { - username = "mapbox" - password = System.getenv("SDK_REGISTRY_TOKEN") ?: project.findProperty("SDK_REGISTRY_TOKEN") as String - } - } - } -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - compileSdk 35 - - namespace 'com.mapbox.maps.mapbox_maps' - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - defaultConfig { - minSdkVersion 21 - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - } -} - -if (file("$rootDir/gradle/ktlint.gradle").exists() && file("$rootDir/gradle/lint.gradle").exists()) { - project.apply { - from("$rootDir/gradle/ktlint.gradle") - from("$rootDir/gradle/lint.gradle") - } -} - -dependencies { - implementation "com.mapbox.maps:android-ndk27:11.25.0" - - implementation "androidx.annotation:annotation:1.5.0" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2" -} diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 000000000..4549625c8 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,68 @@ +group = "com.mapbox.maps.mapbox_maps" +version = "1.0.0" + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:9.0.1") + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + maven { + url = uri("https://api.mapbox.com/downloads/v2/releases/maven") + } + maven { + url = uri("https://api.mapbox.com/downloads/v2/snapshots/maven") + authentication { + register("basic", BasicAuthentication::class.java) + } + credentials { + username = "mapbox" + password = (System.getenv("SDK_REGISTRY_TOKEN") ?: (project.findProperty("SDK_REGISTRY_TOKEN") as? String)) ?: "" + } + } + } +} + +plugins { + id("com.android.library") +} + +android { + compileSdk = 35 + + namespace = "com.mapbox.maps.mapbox_maps" + sourceSets { + getByName("main") { + java.srcDirs("src/main/kotlin") + } + } + defaultConfig { + minSdk = 21 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} + +val ktlintFile = file("$rootDir/gradle/ktlint.gradle") +val lintFile = file("$rootDir/gradle/lint.gradle") +if (ktlintFile.exists() && lintFile.exists()) { + apply(from = ktlintFile) + apply(from = lintFile) +} + +dependencies { + implementation("com.mapbox.maps:android-ndk27:11.25.0") + implementation("androidx.annotation:annotation:1.5.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") +} diff --git a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt index d6add334a..e7bf20a38 100644 --- a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt +++ b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt @@ -369,6 +369,19 @@ class MapboxMapController( result.error("HEADER_ERROR", e.message, null) } } + "map#setMaxRequestsPerHost" -> { + try { + val max = call.argument("max") + if (max == null) { + result.error("INVALID_ARGUMENTS", "max cannot be null", null) + } else { + com.mapbox.common.HttpServiceFactory.setMaxRequestsPerHost(max.toByte()) + result.success(null) + } + } catch (e: Exception) { + result.error("MAX_REQUESTS_PER_HOST_ERROR", e.message, null) + } + } else -> { result.notImplemented() } diff --git a/example/integration_test/all_test.dart b/example/integration_test/all_test.dart index a0758de34..86bda0ffa 100644 --- a/example/integration_test/all_test.dart +++ b/example/integration_test/all_test.dart @@ -45,6 +45,7 @@ import 'logo_test.dart' as logo_test; import 'attribution_test.dart' as attribution_test; import 'compass_test.dart' as compass_test; import 'scale_bar_test.dart' as scale_bar_test; +import 'http_service_test.dart' as http_service_test; import 'offline_test.dart' as offline_test; import 'snapshotter/snapshotter_test.dart' as snapshotter_test; import 'viewport_test.dart' as viewport_test; @@ -74,6 +75,7 @@ void main() { // offline_test offline_test.main(); + http_service_test.main(); // style tests style_test.main(); diff --git a/example/integration_test/http_service_test.dart b/example/integration_test/http_service_test.dart new file mode 100644 index 000000000..f4041b0ed --- /dev/null +++ b/example/integration_test/http_service_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'empty_map_widget.dart' as app; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('MapboxHttpService.setMaxRequestsPerHost', () { + testWidgets('accepts valid values across the supported range', + (widgetTester) async { + final mapFuture = app.main(); + await widgetTester.pumpAndSettle(); + final mapboxMap = await mapFuture; + + // Reasonable mid-range value. + await mapboxMap.httpService.setMaxRequestsPerHost(2); + + // Boundary values (1..255 maps to uint8 on the native side). + await mapboxMap.httpService.setMaxRequestsPerHost(1); + await mapboxMap.httpService.setMaxRequestsPerHost(255); + + // Repeated calls should not throw — setting is idempotent / overwriting. + await mapboxMap.httpService.setMaxRequestsPerHost(8); + await mapboxMap.httpService.setMaxRequestsPerHost(8); + }); + }); +} diff --git a/ios/mapbox_maps_flutter.podspec b/ios/mapbox_maps_flutter.podspec index 53644f6f9..6b33d8fc1 100644 --- a/ios/mapbox_maps_flutter.podspec +++ b/ios/mapbox_maps_flutter.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'mapbox_maps_flutter' - s.version = '2.24.0' + s.version = '2.25.0' s.summary = 'Mapbox Maps SDK Flutter Plugin.' s.description = 'An officially developed solution from Mapbox that enables use of our latest Maps SDK product.' diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift index b17312d00..63b911ae3 100644 --- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift +++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift @@ -1,4 +1,5 @@ import Flutter +import MapboxCommon @_spi(Experimental) import MapboxMaps import UIKit @@ -173,6 +174,20 @@ public final class MapboxMapController: NSObject, FlutterPlatformView { customInterceptor.customHeaders = headers result(nil) + case "map#setMaxRequestsPerHost": + guard let arguments = methodCall.arguments as? [String: Any], + let max = arguments["max"] as? Int + else { + result(FlutterError( + code: "setMaxRequestsPerHost", + message: "could not decode arguments", + details: nil + )) + return + } + HttpServiceFactory.setMaxRequestsPerHostForMax(UInt8(clamping: max)) + result(nil) + default: result(FlutterMethodNotImplemented) } diff --git a/lib/src/http/http_service.dart b/lib/src/http/http_service.dart index cc2d081b6..aafb3386d 100644 --- a/lib/src/http/http_service.dart +++ b/lib/src/http/http_service.dart @@ -11,10 +11,15 @@ class MapboxHttpService { /// [binaryMessenger] is used for platform channel communication /// [channelSuffix] is used to create a unique channel identifier when multiple instances /// of the service are needed. This should match the suffix used on the platform side. - MapboxHttpService( - {required this.binaryMessenger, required this.channelSuffix}) { - _channel = MethodChannel('plugins.flutter.io.${channelSuffix.toString()}', - const StandardMethodCodec(), binaryMessenger); + MapboxHttpService({ + required this.binaryMessenger, + required this.channelSuffix, + }) { + _channel = MethodChannel( + 'plugins.flutter.io.${channelSuffix.toString()}', + const StandardMethodCodec(), + binaryMessenger, + ); } /// Sets custom headers for all Mapbox HTTP requests @@ -40,4 +45,38 @@ class MapboxHttpService { ); } } + + /// Sets the maximum number of concurrent HTTP requests per host that the + /// underlying HTTP service may issue. + /// + /// Lowering this value reduces the chance of hitting per-token rate limits + /// when downloading large amounts of data (for example, offline tile + /// regions), at the cost of slower throughput. + /// + /// This is a global setting that applies to all Mapbox HTTP requests across + /// the process; calling it on any map instance updates it for all of them. + /// + /// [max] must be in the range 1..255. + /// + /// Throws a [PlatformException] if the native implementation is not available + /// or if the operation fails. + Future setMaxRequestsPerHost(int max) async { + try { + await _channel + .invokeMethod('map#setMaxRequestsPerHost', {'max': max}); + } on MissingPluginException catch (e) { + throw PlatformException( + code: 'MISSING_IMPLEMENTATION', + message: + 'Native implementation for setMaxRequestsPerHost is not available', + details: e.toString(), + ); + } catch (e) { + throw PlatformException( + code: 'SET_MAX_REQUESTS_PER_HOST_FAILED', + message: 'Failed to set max requests per host', + details: e.toString(), + ); + } + } } diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index 355eb242b..02edbc4ef 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -270,7 +270,9 @@ class MapboxMap extends ChangeNotifier { late final MapboxHttpService httpService = MapboxHttpService( binaryMessenger: _mapboxMapsPlatform.binaryMessenger, channelSuffix: _mapboxMapsPlatform.channelSuffix); + @Deprecated('Use [MapboxMap.addInteraction] instead') OnMapTapListener? onMapTapListener; + @Deprecated('Use [MapboxMap.addInteraction] instead') OnMapLongTapListener? onMapLongTapListener; OnMapScrollListener? onMapScrollListener; OnMapZoomListener? onMapZoomListener; @@ -840,11 +842,13 @@ class MapboxMap extends ChangeNotifier { messageChannelSuffix: _mapboxMapsPlatform.channelSuffix.toString()); } + @Deprecated('Use [MapboxMap.addInteraction] instead') void setOnMapTapListener(OnMapTapListener? onMapTapListener) { this.onMapTapListener = onMapTapListener; _setupGestures(); } + @Deprecated('Use [MapboxMap.addInteraction] instead') void setOnMapLongTapListener(OnMapLongTapListener? onMapLongTapListener) { this.onMapLongTapListener = onMapLongTapListener; _setupGestures(); diff --git a/lib/src/package_info.dart b/lib/src/package_info.dart index 8f5cf187d..781420b09 100644 --- a/lib/src/package_info.dart +++ b/lib/src/package_info.dart @@ -1,4 +1,4 @@ part of mapbox_maps_flutter; -const String mapboxPluginVersion = '2.24.0'; +const String mapboxPluginVersion = '2.25.0'; diff --git a/pubspec.yaml b/pubspec.yaml index d4e91f725..1d235e42b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: mapbox_maps_flutter description: Interactive, thoroughly customizable maps powered by Mapbox Maps mobile SDKs. -version: 2.24.0 +version: 2.25.0 homepage: https://github.com/mapbox/mapbox-maps-flutter environment: