From 045b3124214709503ba67a8a2ebbe4e2d5a6763e Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 10 Oct 2026 14:18:53 -0700 Subject: [PATCH 1/2] feat: integrate vector style support and pending map fixes --- api/android-for-mappls.api | 2 +- build.gradle.kts | 362 ++++----- gradle/libs.versions.toml | 2 +- settings.gradle.kts | 97 ++- .../com/mapconductor/mappls/MapplsMapView.kt | 748 +++++++++--------- 5 files changed, 611 insertions(+), 600 deletions(-) diff --git a/api/android-for-mappls.api b/api/android-for-mappls.api index df63c9f..7e0bddd 100644 --- a/api/android-for-mappls.api +++ b/api/android-for-mappls.api @@ -86,7 +86,7 @@ public final class com.mapconductor.mappls.MapplsMapViewHolderInterface$DefaultI public static com.mapconductor.core.features.GeoPoint fromScreenOffsetSync-k-4lQ0M(com.mapconductor.mappls.MapplsMapViewHolderInterface, long); } public final class com.mapconductor.mappls.MapplsMapViewKt { - public static final void MapplsMapView(com.mapconductor.mappls.MapplsViewState, androidx.compose.ui.Modifier, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.CameraRestriction, kotlin.jvm.functions.Function2, ? extends java.lang.Object>, kotlin.jvm.functions.Function1, kotlin.Unit>, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer, int, int, int); + public static final void MapplsMapView(com.mapconductor.mappls.MapplsViewState, androidx.compose.ui.Modifier, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.CameraRestriction, kotlin.jvm.functions.Function2, ? extends java.lang.Object>, kotlin.jvm.functions.Function1, kotlin.Unit>, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, com.mapconductor.core.map.MapViewStyle, kotlin.jvm.functions.Function1, kotlin.Unit>, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer, int, int, int); public static final com.mapconductor.mappls.MapplsViewController createMapplsViewController(com.mapconductor.mappls.MapplsMapViewHolderInterface, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.MutableMapServiceRegistry); public static final com.mapconductor.mappls.marker.MapplsMarkerController getMarkerController(com.mapconductor.mappls.MapplsMapViewHolderInterface, com.mapconductor.core.marker.MarkerTilingOptions); public static final com.mapconductor.mappls.polyline.MapplsPolylineController getPolylineController(com.mapconductor.mappls.MapplsMapViewHolderInterface); diff --git a/build.gradle.kts b/build.gradle.kts index 4fd12f6..028b4c5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,181 +1,181 @@ -plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.compose) - id("org.jlleitschuh.gradle.ktlint") version "14.2.0" - id("maven-publish") - id("signing") - id("com.gradleup.nmcp") version "1.5.0" -} - -ktlint { - android.set(true) - reporters { - reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) - reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) - } -} - -// ドライバー実装点(@InternalMapConductorApi)を使うためのオプトイン。 -// android-for-* は地図SDKドライバーなので、モジュール単位で許可する。 -kotlin { - compilerOptions { - optIn.add("com.mapconductor.core.InternalMapConductorApi") - } -} - -android { - namespace = "com.mapconductor.mappls" - compileSdk = project.property("compileSdk").toString().toInt() - - defaultConfig { - minSdk = project.property("minSdk").toString().toInt() - consumerProguardFiles("consumer-rules.pro") - } - - buildFeatures { - compose = true - } - - compileOptions { - sourceCompatibility = JavaVersion.toVersion(project.property("javaVersion").toString()) - targetCompatibility = JavaVersion.toVersion(project.property("javaVersion").toString()) - } - - publishing { - singleVariant("release") { - withSourcesJar() - } - } -} - -val libraryGroupId = project.findProperty("libraryGroupId") as String? ?: "com.mapconductor" -val libraryArtifactId = "for-mappls" -val libraryVersion = project.findProperty("libraryVersion") as String? ?: "1.0.0" - -dependencies { - // CancellableContinuation.resume の 3 引数 onCancellation は coroutines 1.9 以降。 - // 明示しないと推移的に古い版が入り、単体ビルドだけがコンパイルエラーになる。 - implementation(libs.kotlinx.coroutines.android) - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.androidx.ui) - implementation(libs.androidx.foundation) - - // Lifecycle(MapView が Lifecycle を要求する) - implementation(libs.androidx.lifecycle.runtime.ktx) - implementation(libs.androidx.lifecycle.common.java8) - - // Mappls SDK(Mapbox GL Android v9 系のリネームフォーク。 - // 認証はアプリ側の .a.conf / .a.olf + com.mappls.services.android プラグイン) - api(libs.mappls.sdk) - - // Mappls のタイル取得は OkHttp。`HttpRequestUtil.setOkHttpClient()` に - // Interceptor 付きクライアントを渡してラスタタイルのヘッダを差し替える - // (MapplsRasterHeaderInjector)。 - implementation(libs.okhttp) - - // 集約ビルド(android-sdk)ではプロジェクト参照、単体ビルド(CI のリリース)では - // Maven 座標で解決する。他プロバイダと同じ形。 - if (findProject(":android-sdk-compose") != null) { - api(project(":android-sdk-compose")) - } else { - api("com.mapconductor:compose:$libraryVersion") - } - - testImplementation(libs.junit) -} - - -// Set project version for NMCP plugin -version = libraryVersion -val libraryName = "MapConductor for Mappls" -val libraryDescription = "Mappls (MapmyIndia) implementation for MapConductor unified mapping library" - -// Gradle 9.6 で `by tasks.registering` は非推奨(スクリプトコンパイルエラーになる)。 -// googlemaps 等の既存モジュールは古い形のままなので、再コンパイル時に同じ問題が出る。 -val javadocJar = tasks.register("javadocJar") { - archiveClassifier.set("javadoc") -} - -afterEvaluate { - publishing { - publications { - create("release") { - from(components["release"]) - - groupId = libraryGroupId - artifactId = libraryArtifactId - version = libraryVersion - - artifact(javadocJar.get()) - - pom { - name.set(libraryName) - description.set(libraryDescription) - url.set( - project.findProperty("libraryUrl") as String? - ?: "https://github.com/MapConductor/android-for-mappls", - ) - - licenses { - license { - name.set("The Apache License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - - developers { - developer { - id.set(project.findProperty("developerId") as String? ?: "mapconductor") - name.set(project.findProperty("developerName") as String? ?: "MapConductor Team") - email.set(project.findProperty("developerEmail") as String? ?: "info@mkgeeklab.com") - } - } - - scm { - connection.set("scm:git:git://github.com/MapConductor/android-for-mappls.git") - developerConnection - .set("scm:git:ssh://github.com:MapConductor/android-for-mappls.git") - url.set( - project.findProperty("scmUrl") as String? - ?: "https://github.com/MapConductor/android-for-mappls.git", - ) - } - } - } - } - - repositories { - maven { - name = "GitHubPackages" - setUrl("https://maven.pkg.github.com/MapConductor/android-for-mappls") - credentials { - username = - project.findProperty("gpr.user") as String? ?: System.getenv("GPR_USER") - ?: System.getenv("GITHUB_ACTOR") - password = - project.findProperty("gpr.key") as String? ?: System.getenv("GPR_TOKEN") - ?: System.getenv("GITHUB_TOKEN") - } - } - } - } - - signing { - val signingKey = findProperty("signingKey") as String? - val signingPassword = findProperty("signingPassword") as String? - if (!signingKey.isNullOrEmpty() && !signingPassword.isNullOrEmpty()) { - useInMemoryPgpKeys(signingKey, signingPassword) - sign(publishing.publications["release"]) - } - } - - if (project == rootProject) { - // standalone build only — in multi-project (android-sdk), parent configures nmcp - nmcp { - publishAllPublicationsToCentralPortal { - username.set(findProperty("ossrh_username") as String? ?: System.getenv("OSSRH_USERNAME") ?: "") - password.set(findProperty("ossrh_password") as String? ?: System.getenv("OSSRH_PASSWORD") ?: "") - } - } - } -} +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.compose) + id("org.jlleitschuh.gradle.ktlint") version "14.2.0" + id("maven-publish") + id("signing") + id("com.gradleup.nmcp") version "1.5.0" +} + +ktlint { + android.set(true) + reporters { + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) + } +} + +// ドライバー実装点(@InternalMapConductorApi)を使うためのオプトイン。 +// android-for-* は地図SDKドライバーなので、モジュール単位で許可する。 +kotlin { + compilerOptions { + optIn.add("com.mapconductor.core.InternalMapConductorApi") + } +} + +android { + namespace = "com.mapconductor.mappls" + compileSdk = project.property("compileSdk").toString().toInt() + + defaultConfig { + minSdk = project.property("minSdk").toString().toInt() + consumerProguardFiles("consumer-rules.pro") + } + + buildFeatures { + compose = true + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(project.property("javaVersion").toString()) + targetCompatibility = JavaVersion.toVersion(project.property("javaVersion").toString()) + } + + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +val libraryGroupId = project.findProperty("libraryGroupId") as String? ?: "com.mapconductor" +val libraryArtifactId = "for-mappls" +val libraryVersion = project.findProperty("libraryVersion") as String? ?: "1.0.0" + +dependencies { + // CancellableContinuation.resume の 3 引数 onCancellation は coroutines 1.9 以降。 + // 明示しないと推移的に古い版が入り、単体ビルドだけがコンパイルエラーになる。 + implementation(libs.kotlinx.coroutines.android) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.foundation) + + // Lifecycle(MapView が Lifecycle を要求する) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.common.java8) + + // Mappls SDK(Mapbox GL Android v9 系のリネームフォーク。 + // 認証はアプリ側の .a.conf / .a.olf + com.mappls.services.android プラグイン) + api(libs.mappls.sdk) + + // Mappls のタイル取得は OkHttp。`HttpRequestUtil.setOkHttpClient()` に + // Interceptor 付きクライアントを渡してラスタタイルのヘッダを差し替える + // (MapplsRasterHeaderInjector)。 + implementation(libs.okhttp) + + // 集約ビルド(android-sdk)ではプロジェクト参照、単体ビルド(CI のリリース)では + // Maven 座標で解決する。他プロバイダと同じ形。 + if (findProject(":android-sdk-compose") != null) { + api(project(":android-sdk-compose")) + } else { + api("com.mapconductor:compose:$libraryVersion") + } + + testImplementation(libs.junit) +} + +// Set project version for NMCP plugin +version = libraryVersion +val libraryName = "MapConductor for Mappls" +val libraryDescription = "Mappls (MapmyIndia) implementation for MapConductor unified mapping library" + +// Gradle 9.6 で `by tasks.registering` は非推奨(スクリプトコンパイルエラーになる)。 +// googlemaps 等の既存モジュールは古い形のままなので、再コンパイル時に同じ問題が出る。 +val javadocJar = + tasks.register("javadocJar") { + archiveClassifier.set("javadoc") + } + +afterEvaluate { + publishing { + publications { + create("release") { + from(components["release"]) + + groupId = libraryGroupId + artifactId = libraryArtifactId + version = libraryVersion + + artifact(javadocJar.get()) + + pom { + name.set(libraryName) + description.set(libraryDescription) + url.set( + project.findProperty("libraryUrl") as String? + ?: "https://github.com/MapConductor/android-for-mappls", + ) + + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + + developers { + developer { + id.set(project.findProperty("developerId") as String? ?: "mapconductor") + name.set(project.findProperty("developerName") as String? ?: "MapConductor Team") + email.set(project.findProperty("developerEmail") as String? ?: "info@mkgeeklab.com") + } + } + + scm { + connection.set("scm:git:git://github.com/MapConductor/android-for-mappls.git") + developerConnection + .set("scm:git:ssh://github.com:MapConductor/android-for-mappls.git") + url.set( + project.findProperty("scmUrl") as String? + ?: "https://github.com/MapConductor/android-for-mappls.git", + ) + } + } + } + } + + repositories { + maven { + name = "GitHubPackages" + setUrl("https://maven.pkg.github.com/MapConductor/android-for-mappls") + credentials { + username = + project.findProperty("gpr.user") as String? ?: System.getenv("GPR_USER") + ?: System.getenv("GITHUB_ACTOR") + password = + project.findProperty("gpr.key") as String? ?: System.getenv("GPR_TOKEN") + ?: System.getenv("GITHUB_TOKEN") + } + } + } + } + + signing { + val signingKey = findProperty("signingKey") as String? + val signingPassword = findProperty("signingPassword") as String? + if (!signingKey.isNullOrEmpty() && !signingPassword.isNullOrEmpty()) { + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications["release"]) + } + } + + if (project == rootProject) { + // standalone build only — in multi-project (android-sdk), parent configures nmcp + nmcp { + publishAllPublicationsToCentralPortal { + username.set(findProperty("ossrh_username") as String? ?: System.getenv("OSSRH_USERNAME") ?: "") + password.set(findProperty("ossrh_password") as String? ?: System.getenv("OSSRH_PASSWORD") ?: "") + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1f9ef83..eecaa46 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ composeLibs = "1.7.8" # material3 は compose-ui とは別のバージョン系列。composeLibs(ui 系)を流用すると # 存在しない material3:1.7.8 を要求してクラスパスごと解決に失敗する。 material3 = "1.4.0" -mapplsSdk = "9.0.3" +mapplsSdk = "9.0.4" coroutines = "1.9.0" agp = "9.2.1" kotlin = "2.0.21" diff --git a/settings.gradle.kts b/settings.gradle.kts index 981619a..cc470ad 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,49 +1,48 @@ -pluginManagement { - repositories { - google { - content { - includeGroupByRegex("com\\.android.*") - includeGroupByRegex("com\\.google.*") - includeGroupByRegex("androidx.*") - } - } - mavenCentral() - gradlePluginPortal() - } -} - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - mavenLocal() - google() - mavenCentral() - maven { - // Mappls (MapmyIndia) Maven repository - url = uri("https://maven.mappls.com/repository/mappls/") - } - if (!System.getenv("GPR_USER").isNullOrEmpty() && !System.getenv("GPR_TOKEN").isNullOrEmpty()) { - maven { - name = "GithubPackages" - url = uri("https://maven.pkg.github.com/mapconductor/android-for-mappls") - credentials { - username = System.getenv("GPR_USER") ?: "" - password = System.getenv("GPR_TOKEN") ?: "" - } - } - } - if (!System.getenv("GPR_USER").isNullOrEmpty() || !System.getenv("GITHUB_ACTOR").isNullOrEmpty()) { - maven { - name = "GithubPackages-core" - url = uri("https://maven.pkg.github.com/MapConductor/android-sdk-core") - credentials { - username = System.getenv("GPR_USER") ?: System.getenv("GITHUB_ACTOR") ?: "" - password = System.getenv("GPR_TOKEN") ?: System.getenv("GITHUB_TOKEN") ?: "" - } - } - } - } -} - -rootProject.name = "android-for-mappls" - +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenLocal() + google() + mavenCentral() + maven { + // Mappls (MapmyIndia) Maven repository + url = uri("https://maven.mappls.com/repository/mappls/") + } + if (!System.getenv("GPR_USER").isNullOrEmpty() && !System.getenv("GPR_TOKEN").isNullOrEmpty()) { + maven { + name = "GithubPackages" + url = uri("https://maven.pkg.github.com/mapconductor/android-for-mappls") + credentials { + username = System.getenv("GPR_USER") ?: "" + password = System.getenv("GPR_TOKEN") ?: "" + } + } + } + if (!System.getenv("GPR_USER").isNullOrEmpty() || !System.getenv("GITHUB_ACTOR").isNullOrEmpty()) { + maven { + name = "GithubPackages-core" + url = uri("https://maven.pkg.github.com/MapConductor/android-sdk-core") + credentials { + username = System.getenv("GPR_USER") ?: System.getenv("GITHUB_ACTOR") ?: "" + password = System.getenv("GPR_TOKEN") ?: System.getenv("GITHUB_TOKEN") ?: "" + } + } + } + } +} + +rootProject.name = "android-for-mappls" diff --git a/src/main/java/com/mapconductor/mappls/MapplsMapView.kt b/src/main/java/com/mapconductor/mappls/MapplsMapView.kt index 150f80e..77409ab 100644 --- a/src/main/java/com/mapconductor/mappls/MapplsMapView.kt +++ b/src/main/java/com/mapconductor/mappls/MapplsMapView.kt @@ -1,368 +1,380 @@ -package com.mapconductor.mappls - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import com.mapconductor.compose.map.MapViewBase -import com.mapconductor.core.OnCameraMoveHandler -import com.mapconductor.core.OnMapEventHandler -import com.mapconductor.core.OnMapLoadedHandler -import com.mapconductor.core.circle.CircleManager -import com.mapconductor.core.map.CameraRestriction -import com.mapconductor.core.map.MapCameraPositionInterface -import com.mapconductor.core.map.MutableMapServiceRegistry -import com.mapconductor.core.marker.MarkerEventControllerInterface -import com.mapconductor.core.marker.MarkerManager -import com.mapconductor.core.marker.MarkerOverlayRendererInterface -import com.mapconductor.core.marker.MarkerRenderingStrategyInterface -import com.mapconductor.core.marker.MarkerRenderingSupport -import com.mapconductor.core.marker.MarkerRenderingSupportKey -import com.mapconductor.core.marker.MarkerTilingOptions -import com.mapconductor.core.marker.StrategyMarkerController -import com.mapconductor.core.polygon.PolygonManager -import com.mapconductor.core.polyline.PolylineManager -import com.mapconductor.mappls.circle.MapplsCircleController -import com.mapconductor.mappls.circle.MapplsCircleLayer -import com.mapconductor.mappls.circle.MapplsCircleOverlayRenderer -import com.mapconductor.mappls.groundimage.MapplsGroundImageController -import com.mapconductor.mappls.groundimage.MapplsGroundImageOverlayRenderer -import com.mapconductor.mappls.marker.MapplsMarkerController -import com.mapconductor.mappls.marker.MapplsMarkerOverlayRenderer -import com.mapconductor.mappls.marker.MarkerDragLayer -import com.mapconductor.mappls.marker.MarkerLayer -import com.mapconductor.mappls.polygon.MapplsPolygonConductor -import com.mapconductor.mappls.polygon.MapplsPolygonLayer -import com.mapconductor.mappls.polygon.MapplsPolygonOverlayRenderer -import com.mapconductor.mappls.polyline.MapplsPolylineController -import com.mapconductor.mappls.polyline.MapplsPolylineLayer -import com.mapconductor.mappls.polyline.MapplsPolylineOverlayRenderer -import com.mapconductor.mappls.raster.MapplsRasterLayerController -import com.mapconductor.mappls.raster.MapplsRasterLayerOverlayRenderer -import com.mappls.sdk.maps.MapView -import com.mappls.sdk.maps.Mappls -import com.mappls.sdk.maps.MapplsMap -import com.mappls.sdk.maps.MapplsMapOptions -import com.mappls.sdk.maps.OnMapReadyCallback -import com.mappls.sdk.maps.Style -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.util.Log -import kotlinx.coroutines.suspendCancellableCoroutine - -@Composable -fun MapplsMapView( - state: MapplsViewState, - modifier: Modifier = Modifier, - markerTiling: MarkerTilingOptions? = null, - cameraRestriction: CameraRestriction? = null, - sdkInitialize: (suspend (Context) -> Boolean)? = null, - onMapLoaded: OnMapLoadedHandler? = null, - onMapClick: OnMapEventHandler? = null, - onMapLongClick: OnMapEventHandler? = null, - onCameraMoveStart: OnCameraMoveHandler? = null, - onCameraMove: OnCameraMoveHandler? = null, - onCameraMoveEnd: OnCameraMoveHandler? = null, - content: (@Composable MapplsMapViewScope.() -> Unit)? = null, -) { - val context = LocalContext.current - val scope = remember { MapplsMapViewScope() } - val registry = remember { scope.buildRegistry() } - val cameraState = remember { mutableStateOf(state.cameraPosition) } - - MapViewBase( - state = state, - cameraState = cameraState, - modifier = modifier, - viewProvider = { - val cameraPosition = - state.cameraPosition.toCameraPosition() - val mapInitOptions = - MapplsMapOptions - .createFromAttributes(context) - .camera(cameraPosition) - .textureMode(true) - // Don't set style here - it will be set in holderProvider - - MapView(context, mapInitOptions) - }, - scope = scope, - registry = registry, - onMapLoaded = onMapLoaded, - holderProvider = { mapView -> - suspendCancellableCoroutine { continuation -> - mapView.getMapAsync( - object : OnMapReadyCallback { - override fun onMapReady(map: MapplsMap) { - // 認証が通ると SDK がアカウントの既定スタイルを読み込む。 - // 名前指定があればそのスタイルへ差し替え、読み込み完了後に resume する。 - val designName = state.mapDesignType.styleName - if (designName.isEmpty()) { - map.getStyle { - continuation.resume( - MapplsMapViewHolder(mapView, map), - ) { _, _, _ -> } - } - } else { - map.setMapplsStyle( - designName, - Style.OnStyleLoaded { - continuation.resume( - MapplsMapViewHolder(mapView, map), - ) { _, _, _ -> } - }, - ) - } - } - - override fun onMapError( - code: Int, - message: String?, - ) { - // 認証エラー(コンフィグファイル不一致など)はここへ来る。 - // resume しないので地図は表示されないまま — ログで原因を残す。 - Log.e("Mappls", "Map init error $code: $message") - } - }, - ) - } - }, - controllerProvider = { holder -> - createMapplsViewController( - holder = holder, - markerTiling = markerTiling ?: MarkerTilingOptions.Default, - serviceRegistry = state.serviceRegistry, - ).also { mapController -> - // Store controller reference in holder - mapController.setCameraMoveStartListener { - cameraState.value = it - state.updateCameraPosition(it) - onCameraMoveStart?.invoke(it) - } - mapController.setCameraMoveListener { - cameraState.value = it - state.updateCameraPosition(it) - onCameraMove?.invoke(it) - } - mapController.setCameraMoveEndListener { - cameraState.value = it - state.updateCameraPosition(it) - onCameraMoveEnd?.invoke(it) - } - mapController.setMapClickListener(onMapClick) - mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange) - mapController.setMapLongClickListener(onMapLongClick) - cameraRestriction?.let { mapController.setCameraRestriction(it) } - state.setController(mapController) - // Post an initial camera update after layout to compute visibleRegion correctly - holder.mapView.post { mapController.sendInitialCameraUpdate() } - } - }, - sdkInitialize = { - // 外部タイルのホワイトリスト回避(MapplsHttpBridge のコメント参照)は - // 地図生成前に必ず入れる。sdkInitialize を差し替えられても失われないよう - // ここで無条件に呼ぶ。 - MapplsHttpBridge.install() - if (sdkInitialize != null) { - sdkInitialize(context) - } else { - Mappls.getInstance(context) - true - } - }, - // Pass content if it needs to be rendered within the overlay providers in MapViewBase, - // or handle it here if it's specific to MapplsMapView structure before calling MapViewBase. - // For now, assuming content relates to overlay definitions. - content = content, // This might need adjustment based on how overlays are handled - ) -} - -/** - * Creates the imperative controller graph used by both the Compose MapView and non-Compose hosts - * such as React Native. Keeping this construction here prevents provider-specific layer setup from - * being duplicated by each UI integration. - * - * @param serviceRegistry 登録先のサービスレジストリ。Compose からは `state.serviceRegistry` を渡す - * (react-sdk / ios-sdk と同じく持ち主は state)。React Native / Cordova のような非 Compose - * ホストは state を持たないので、自前のレジストリを渡す。 - */ -fun createMapplsViewController( - holder: MapplsMapViewHolderInterface, - markerTiling: MarkerTilingOptions = MarkerTilingOptions.Default, - serviceRegistry: MutableMapServiceRegistry? = null, -): MapplsViewController { - // 非 Compose ホスト(React Native / Cordova)はこちらが入口なので、ここでも入れる - MapplsHttpBridge.install() - val markerController = getMarkerController(holder, markerTiling) - val mapController = - MapplsViewController( - holder = holder, - markerController = markerController, - polylineController = getPolylineController(holder), - polygonController = getPolygonController(holder), - groundImageController = getGroundImageController(holder), - circleController = getCircleController(holder), - rasterLayerController = getRasterLayerController(holder), - ) - - serviceRegistry?.let { registry -> - MapplsCapabilities.declare(registry) - registry.put( - MarkerRenderingSupportKey, - object : MarkerRenderingSupport { - override fun createMarkerRenderer( - strategy: MarkerRenderingStrategyInterface, - ): MarkerOverlayRendererInterface = mapController.createMarkerRenderer(strategy) - - override fun createMarkerEventController( - controller: StrategyMarkerController, - renderer: MarkerOverlayRendererInterface, - ): MarkerEventControllerInterface = - mapController.createMarkerEventController(controller, renderer) - - override fun registerMarkerEventController( - controller: MarkerEventControllerInterface, - ) { - mapController.registerMarkerEventController(controller) - } - - override fun onMarkerRenderingReady() { - mapController.sendInitialCameraUpdate() - } - }, - ) - } - return mapController -} - -fun getMarkerController( - holder: MapplsMapViewHolderInterface, - markerTiling: MarkerTilingOptions, -): MapplsMarkerController { - val manager = MarkerManager.defaultManager() - val markerLayer = - MarkerLayer( - sourceId = "markers-source", - layerId = "markers-layer", - ) - val dragLayer = - MarkerDragLayer( - sourceId = "marker-drag-source", - layerId = "marker-drag-layer", - ) - val renderer = - MapplsMarkerOverlayRenderer( - holder = holder, - markerLayer = markerLayer, - dragLayer = dragLayer, - markerManager = manager, - ) - - val controller = - MapplsMarkerController( - renderer = renderer, - markerTiling = markerTiling, - ) - return controller -} - -fun getPolylineController(holder: MapplsMapViewHolderInterface): MapplsPolylineController { - val polylineLayer = - MapplsPolylineLayer( - sourceId = "polyline-source", - layerId = "polyline-layer", - ) - val polylineManager = PolylineManager() - - val renderer = - MapplsPolylineOverlayRenderer( - layer = polylineLayer, - polylineManager = polylineManager, - holder = holder, - ) - - val controller = - MapplsPolylineController( - renderer = renderer, - ) - return controller -} - -fun getPolygonController(holder: MapplsMapViewHolderInterface): MapplsPolygonConductor { - val polylineLayer = - MapplsPolylineLayer( - sourceId = "polygon-outline-source", - layerId = "polygon-outline-layer", - ) - val polylineManager = PolylineManager() - val polylineOverlayRenderer = - MapplsPolylineOverlayRenderer( - layer = polylineLayer, - polylineManager = polylineManager, - holder = holder, - ) - - val polygonManager = PolygonManager() - val polygonLayer = - MapplsPolygonLayer( - sourceId = "polygon-fill-source", - layerId = "polygon-fill-layer", - ) - val polygonOverlayRenderer = - MapplsPolygonOverlayRenderer( - layer = polygonLayer, - polygonManager = polygonManager, - holder = holder, - ) - - return MapplsPolygonConductor( - polygonOverlay = polygonOverlayRenderer, - polylineOverlay = polylineOverlayRenderer, - ) -} - -fun getCircleController(holder: MapplsMapViewHolderInterface): MapplsCircleController { - val circleLayer = - MapplsCircleLayer( - sourceId = "circle-source", - layerId = "circle-layer", - ) - val circleManager = CircleManager() - val renderer = - MapplsCircleOverlayRenderer( - layer = circleLayer, - circleManager = circleManager, - holder = holder, - ) - return MapplsCircleController( - renderer = renderer, - circleManager = circleManager, - ) -} - -fun getRasterLayerController(holder: MapplsMapViewHolderInterface): MapplsRasterLayerController { - val renderer = - MapplsRasterLayerOverlayRenderer( - holder = holder, - ) - return MapplsRasterLayerController( - renderer = renderer, - ) -} - -fun getGroundImageController(holder: MapplsMapViewHolderInterface): MapplsGroundImageController { - val renderer = - MapplsGroundImageOverlayRenderer( - holder = holder, - ) - return MapplsGroundImageController(renderer = renderer) -} - -internal fun Context.findActivity(): Activity? = - when (this) { - is Activity -> this - is ContextWrapper -> baseContext.findActivity() - else -> null - } +package com.mapconductor.mappls + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import com.mapconductor.compose.map.MapViewBase +import com.mapconductor.core.OnCameraMoveHandler +import com.mapconductor.core.OnMapEventHandler +import com.mapconductor.core.OnMapLoadedHandler +import com.mapconductor.core.circle.CircleManager +import com.mapconductor.core.map.CameraRestriction +import com.mapconductor.core.map.MapCameraPositionInterface +import com.mapconductor.core.map.MapViewStyle +import com.mapconductor.core.map.MutableMapServiceRegistry +import com.mapconductor.core.marker.MarkerEventControllerInterface +import com.mapconductor.core.marker.MarkerManager +import com.mapconductor.core.marker.MarkerOverlayRendererInterface +import com.mapconductor.core.marker.MarkerRenderingStrategyInterface +import com.mapconductor.core.marker.MarkerRenderingSupport +import com.mapconductor.core.marker.MarkerRenderingSupportKey +import com.mapconductor.core.marker.MarkerTilingOptions +import com.mapconductor.core.marker.StrategyMarkerController +import com.mapconductor.core.polygon.PolygonManager +import com.mapconductor.core.polyline.PolylineManager +import com.mapconductor.mappls.circle.MapplsCircleController +import com.mapconductor.mappls.circle.MapplsCircleLayer +import com.mapconductor.mappls.circle.MapplsCircleOverlayRenderer +import com.mapconductor.mappls.groundimage.MapplsGroundImageController +import com.mapconductor.mappls.groundimage.MapplsGroundImageOverlayRenderer +import com.mapconductor.mappls.marker.MapplsMarkerController +import com.mapconductor.mappls.marker.MapplsMarkerOverlayRenderer +import com.mapconductor.mappls.marker.MarkerDragLayer +import com.mapconductor.mappls.marker.MarkerLayer +import com.mapconductor.mappls.polygon.MapplsPolygonConductor +import com.mapconductor.mappls.polygon.MapplsPolygonLayer +import com.mapconductor.mappls.polygon.MapplsPolygonOverlayRenderer +import com.mapconductor.mappls.polyline.MapplsPolylineController +import com.mapconductor.mappls.polyline.MapplsPolylineLayer +import com.mapconductor.mappls.polyline.MapplsPolylineOverlayRenderer +import com.mapconductor.mappls.raster.MapplsRasterLayerController +import com.mapconductor.mappls.raster.MapplsRasterLayerOverlayRenderer +import com.mappls.sdk.maps.MapView +import com.mappls.sdk.maps.Mappls +import com.mappls.sdk.maps.MapplsMap +import com.mappls.sdk.maps.MapplsMapOptions +import com.mappls.sdk.maps.OnMapReadyCallback +import com.mappls.sdk.maps.Style +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.util.Log +import kotlinx.coroutines.suspendCancellableCoroutine + +@Composable +fun MapplsMapView( + state: MapplsViewState, + modifier: Modifier = Modifier, + markerTiling: MarkerTilingOptions? = null, + cameraRestriction: CameraRestriction? = null, + sdkInitialize: (suspend (Context) -> Boolean)? = null, + onMapLoaded: OnMapLoadedHandler? = null, + onMapClick: OnMapEventHandler? = null, + onMapLongClick: OnMapEventHandler? = null, + onCameraMoveStart: OnCameraMoveHandler? = null, + onCameraMove: OnCameraMoveHandler? = null, + onCameraMoveEnd: OnCameraMoveHandler? = null, + /** + * How the map looks, when the app states it rather than naming a design. + * + * A vector style *is* the basemap. `com.mapconductor:vectorstyle` builds + * one; what happens underneath depends on this backend and the app does + * not have to know. + */ + style: MapViewStyle? = null, + onStyleDiagnostics: ((List) -> Unit)? = null, + content: (@Composable MapplsMapViewScope.() -> Unit)? = null, +) { + val context = LocalContext.current + val scope = remember { MapplsMapViewScope() } + val registry = remember { scope.buildRegistry() } + val cameraState = remember { mutableStateOf(state.cameraPosition) } + + MapViewBase( + state = state, + cameraState = cameraState, + modifier = modifier, + viewProvider = { + val cameraPosition = + state.cameraPosition.toCameraPosition() + val mapInitOptions = + MapplsMapOptions + .createFromAttributes(context) + .camera(cameraPosition) + .textureMode(true) + // Don't set style here - it will be set in holderProvider + + MapView(context, mapInitOptions) + }, + scope = scope, + registry = registry, + onMapLoaded = onMapLoaded, + holderProvider = { mapView -> + suspendCancellableCoroutine { continuation -> + mapView.getMapAsync( + object : OnMapReadyCallback { + override fun onMapReady(map: MapplsMap) { + // 認証が通ると SDK がアカウントの既定スタイルを読み込む。 + // 名前指定があればそのスタイルへ差し替え、読み込み完了後に resume する。 + val designName = state.mapDesignType.styleName + if (designName.isEmpty()) { + map.getStyle { + continuation.resume( + MapplsMapViewHolder(mapView, map), + ) { _, _, _ -> } + } + } else { + map.setMapplsStyle( + designName, + Style.OnStyleLoaded { + continuation.resume( + MapplsMapViewHolder(mapView, map), + ) { _, _, _ -> } + }, + ) + } + } + + override fun onMapError( + code: Int, + message: String?, + ) { + // 認証エラー(コンフィグファイル不一致など)はここへ来る。 + // resume しないので地図は表示されないまま — ログで原因を残す。 + Log.e("Mappls", "Map init error $code: $message") + } + }, + ) + } + }, + controllerProvider = { holder -> + createMapplsViewController( + holder = holder, + markerTiling = markerTiling ?: MarkerTilingOptions.Default, + serviceRegistry = state.serviceRegistry, + ).also { mapController -> + // Store controller reference in holder + mapController.setCameraMoveStartListener { + cameraState.value = it + state.updateCameraPosition(it) + onCameraMoveStart?.invoke(it) + } + mapController.setCameraMoveListener { + cameraState.value = it + state.updateCameraPosition(it) + onCameraMove?.invoke(it) + } + mapController.setCameraMoveEndListener { + cameraState.value = it + state.updateCameraPosition(it) + onCameraMoveEnd?.invoke(it) + } + mapController.setMapClickListener(onMapClick) + mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange) + mapController.setMapLongClickListener(onMapLongClick) + cameraRestriction?.let { mapController.setCameraRestriction(it) } + state.setController(mapController) + // Post an initial camera update after layout to compute visibleRegion correctly + holder.mapView.post { mapController.sendInitialCameraUpdate() } + } + }, + sdkInitialize = { + // 外部タイルのホワイトリスト回避(MapplsHttpBridge のコメント参照)は + // 地図生成前に必ず入れる。sdkInitialize を差し替えられても失われないよう + // ここで無条件に呼ぶ。 + MapplsHttpBridge.install() + if (sdkInitialize != null) { + sdkInitialize(context) + } else { + Mappls.getInstance(context) + true + } + }, + // Pass content if it needs to be rendered within the overlay providers in MapViewBase, + // or handle it here if it's specific to MapplsMapView structure before calling MapViewBase. + // For now, assuming content relates to overlay definitions. + style = style, + onStyleDiagnostics = onStyleDiagnostics, + content = content, // This might need adjustment based on how overlays are handled + ) +} + +/** + * Creates the imperative controller graph used by both the Compose MapView and non-Compose hosts + * such as React Native. Keeping this construction here prevents provider-specific layer setup from + * being duplicated by each UI integration. + * + * @param serviceRegistry 登録先のサービスレジストリ。Compose からは `state.serviceRegistry` を渡す + * (react-sdk / ios-sdk と同じく持ち主は state)。React Native / Cordova のような非 Compose + * ホストは state を持たないので、自前のレジストリを渡す。 + */ +fun createMapplsViewController( + holder: MapplsMapViewHolderInterface, + markerTiling: MarkerTilingOptions = MarkerTilingOptions.Default, + serviceRegistry: MutableMapServiceRegistry? = null, +): MapplsViewController { + // 非 Compose ホスト(React Native / Cordova)はこちらが入口なので、ここでも入れる + MapplsHttpBridge.install() + val markerController = getMarkerController(holder, markerTiling) + val mapController = + MapplsViewController( + holder = holder, + markerController = markerController, + polylineController = getPolylineController(holder), + polygonController = getPolygonController(holder), + groundImageController = getGroundImageController(holder), + circleController = getCircleController(holder), + rasterLayerController = getRasterLayerController(holder), + ) + + serviceRegistry?.let { registry -> + MapplsCapabilities.declare(registry) + registry.put( + MarkerRenderingSupportKey, + object : MarkerRenderingSupport { + override fun createMarkerRenderer( + strategy: MarkerRenderingStrategyInterface, + ): MarkerOverlayRendererInterface = mapController.createMarkerRenderer(strategy) + + override fun createMarkerEventController( + controller: StrategyMarkerController, + renderer: MarkerOverlayRendererInterface, + ): MarkerEventControllerInterface = + mapController.createMarkerEventController(controller, renderer) + + override fun registerMarkerEventController( + controller: MarkerEventControllerInterface, + ) { + mapController.registerMarkerEventController(controller) + } + + override fun onMarkerRenderingReady() { + mapController.sendInitialCameraUpdate() + } + }, + ) + } + return mapController +} + +fun getMarkerController( + holder: MapplsMapViewHolderInterface, + markerTiling: MarkerTilingOptions, +): MapplsMarkerController { + val manager = MarkerManager.defaultManager() + val markerLayer = + MarkerLayer( + sourceId = "markers-source", + layerId = "markers-layer", + ) + val dragLayer = + MarkerDragLayer( + sourceId = "marker-drag-source", + layerId = "marker-drag-layer", + ) + val renderer = + MapplsMarkerOverlayRenderer( + holder = holder, + markerLayer = markerLayer, + dragLayer = dragLayer, + markerManager = manager, + ) + + val controller = + MapplsMarkerController( + renderer = renderer, + markerTiling = markerTiling, + ) + return controller +} + +fun getPolylineController(holder: MapplsMapViewHolderInterface): MapplsPolylineController { + val polylineLayer = + MapplsPolylineLayer( + sourceId = "polyline-source", + layerId = "polyline-layer", + ) + val polylineManager = PolylineManager() + + val renderer = + MapplsPolylineOverlayRenderer( + layer = polylineLayer, + polylineManager = polylineManager, + holder = holder, + ) + + val controller = + MapplsPolylineController( + renderer = renderer, + ) + return controller +} + +fun getPolygonController(holder: MapplsMapViewHolderInterface): MapplsPolygonConductor { + val polylineLayer = + MapplsPolylineLayer( + sourceId = "polygon-outline-source", + layerId = "polygon-outline-layer", + ) + val polylineManager = PolylineManager() + val polylineOverlayRenderer = + MapplsPolylineOverlayRenderer( + layer = polylineLayer, + polylineManager = polylineManager, + holder = holder, + ) + + val polygonManager = PolygonManager() + val polygonLayer = + MapplsPolygonLayer( + sourceId = "polygon-fill-source", + layerId = "polygon-fill-layer", + ) + val polygonOverlayRenderer = + MapplsPolygonOverlayRenderer( + layer = polygonLayer, + polygonManager = polygonManager, + holder = holder, + ) + + return MapplsPolygonConductor( + polygonOverlay = polygonOverlayRenderer, + polylineOverlay = polylineOverlayRenderer, + ) +} + +fun getCircleController(holder: MapplsMapViewHolderInterface): MapplsCircleController { + val circleLayer = + MapplsCircleLayer( + sourceId = "circle-source", + layerId = "circle-layer", + ) + val circleManager = CircleManager() + val renderer = + MapplsCircleOverlayRenderer( + layer = circleLayer, + circleManager = circleManager, + holder = holder, + ) + return MapplsCircleController( + renderer = renderer, + circleManager = circleManager, + ) +} + +fun getRasterLayerController(holder: MapplsMapViewHolderInterface): MapplsRasterLayerController { + val renderer = + MapplsRasterLayerOverlayRenderer( + holder = holder, + ) + return MapplsRasterLayerController( + renderer = renderer, + ) +} + +fun getGroundImageController(holder: MapplsMapViewHolderInterface): MapplsGroundImageController { + val renderer = + MapplsGroundImageOverlayRenderer( + holder = holder, + ) + return MapplsGroundImageController(renderer = renderer) +} + +internal fun Context.findActivity(): Activity? = + when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null + } From eedf31090ef9a389d31485a4f4348a1c74514e3f Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 24 Oct 2026 03:50:18 -0700 Subject: [PATCH 2/2] Keep each file's line endings as they are on develop The checkpoint commit rewrote whole files between LF and CRLF, which buried its real changes under line-ending noise. Content is unchanged. --- build.gradle.kts | 362 ++++----- settings.gradle.kts | 96 +-- .../com/mapconductor/mappls/MapplsMapView.kt | 760 +++++++++--------- 3 files changed, 609 insertions(+), 609 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 028b4c5..92281d1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,181 +1,181 @@ -plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.compose) - id("org.jlleitschuh.gradle.ktlint") version "14.2.0" - id("maven-publish") - id("signing") - id("com.gradleup.nmcp") version "1.5.0" -} - -ktlint { - android.set(true) - reporters { - reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) - reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) - } -} - -// ドライバー実装点(@InternalMapConductorApi)を使うためのオプトイン。 -// android-for-* は地図SDKドライバーなので、モジュール単位で許可する。 -kotlin { - compilerOptions { - optIn.add("com.mapconductor.core.InternalMapConductorApi") - } -} - -android { - namespace = "com.mapconductor.mappls" - compileSdk = project.property("compileSdk").toString().toInt() - - defaultConfig { - minSdk = project.property("minSdk").toString().toInt() - consumerProguardFiles("consumer-rules.pro") - } - - buildFeatures { - compose = true - } - - compileOptions { - sourceCompatibility = JavaVersion.toVersion(project.property("javaVersion").toString()) - targetCompatibility = JavaVersion.toVersion(project.property("javaVersion").toString()) - } - - publishing { - singleVariant("release") { - withSourcesJar() - } - } -} - -val libraryGroupId = project.findProperty("libraryGroupId") as String? ?: "com.mapconductor" -val libraryArtifactId = "for-mappls" -val libraryVersion = project.findProperty("libraryVersion") as String? ?: "1.0.0" - -dependencies { - // CancellableContinuation.resume の 3 引数 onCancellation は coroutines 1.9 以降。 - // 明示しないと推移的に古い版が入り、単体ビルドだけがコンパイルエラーになる。 - implementation(libs.kotlinx.coroutines.android) - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.androidx.ui) - implementation(libs.androidx.foundation) - - // Lifecycle(MapView が Lifecycle を要求する) - implementation(libs.androidx.lifecycle.runtime.ktx) - implementation(libs.androidx.lifecycle.common.java8) - - // Mappls SDK(Mapbox GL Android v9 系のリネームフォーク。 - // 認証はアプリ側の .a.conf / .a.olf + com.mappls.services.android プラグイン) - api(libs.mappls.sdk) - - // Mappls のタイル取得は OkHttp。`HttpRequestUtil.setOkHttpClient()` に - // Interceptor 付きクライアントを渡してラスタタイルのヘッダを差し替える - // (MapplsRasterHeaderInjector)。 - implementation(libs.okhttp) - - // 集約ビルド(android-sdk)ではプロジェクト参照、単体ビルド(CI のリリース)では - // Maven 座標で解決する。他プロバイダと同じ形。 - if (findProject(":android-sdk-compose") != null) { - api(project(":android-sdk-compose")) - } else { - api("com.mapconductor:compose:$libraryVersion") - } - - testImplementation(libs.junit) -} - -// Set project version for NMCP plugin -version = libraryVersion -val libraryName = "MapConductor for Mappls" -val libraryDescription = "Mappls (MapmyIndia) implementation for MapConductor unified mapping library" - -// Gradle 9.6 で `by tasks.registering` は非推奨(スクリプトコンパイルエラーになる)。 -// googlemaps 等の既存モジュールは古い形のままなので、再コンパイル時に同じ問題が出る。 -val javadocJar = - tasks.register("javadocJar") { - archiveClassifier.set("javadoc") - } - -afterEvaluate { - publishing { - publications { - create("release") { - from(components["release"]) - - groupId = libraryGroupId - artifactId = libraryArtifactId - version = libraryVersion - - artifact(javadocJar.get()) - - pom { - name.set(libraryName) - description.set(libraryDescription) - url.set( - project.findProperty("libraryUrl") as String? - ?: "https://github.com/MapConductor/android-for-mappls", - ) - - licenses { - license { - name.set("The Apache License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - - developers { - developer { - id.set(project.findProperty("developerId") as String? ?: "mapconductor") - name.set(project.findProperty("developerName") as String? ?: "MapConductor Team") - email.set(project.findProperty("developerEmail") as String? ?: "info@mkgeeklab.com") - } - } - - scm { - connection.set("scm:git:git://github.com/MapConductor/android-for-mappls.git") - developerConnection - .set("scm:git:ssh://github.com:MapConductor/android-for-mappls.git") - url.set( - project.findProperty("scmUrl") as String? - ?: "https://github.com/MapConductor/android-for-mappls.git", - ) - } - } - } - } - - repositories { - maven { - name = "GitHubPackages" - setUrl("https://maven.pkg.github.com/MapConductor/android-for-mappls") - credentials { - username = - project.findProperty("gpr.user") as String? ?: System.getenv("GPR_USER") - ?: System.getenv("GITHUB_ACTOR") - password = - project.findProperty("gpr.key") as String? ?: System.getenv("GPR_TOKEN") - ?: System.getenv("GITHUB_TOKEN") - } - } - } - } - - signing { - val signingKey = findProperty("signingKey") as String? - val signingPassword = findProperty("signingPassword") as String? - if (!signingKey.isNullOrEmpty() && !signingPassword.isNullOrEmpty()) { - useInMemoryPgpKeys(signingKey, signingPassword) - sign(publishing.publications["release"]) - } - } - - if (project == rootProject) { - // standalone build only — in multi-project (android-sdk), parent configures nmcp - nmcp { - publishAllPublicationsToCentralPortal { - username.set(findProperty("ossrh_username") as String? ?: System.getenv("OSSRH_USERNAME") ?: "") - password.set(findProperty("ossrh_password") as String? ?: System.getenv("OSSRH_PASSWORD") ?: "") - } - } - } -} +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.compose) + id("org.jlleitschuh.gradle.ktlint") version "14.2.0" + id("maven-publish") + id("signing") + id("com.gradleup.nmcp") version "1.5.0" +} + +ktlint { + android.set(true) + reporters { + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) + } +} + +// ドライバー実装点(@InternalMapConductorApi)を使うためのオプトイン。 +// android-for-* は地図SDKドライバーなので、モジュール単位で許可する。 +kotlin { + compilerOptions { + optIn.add("com.mapconductor.core.InternalMapConductorApi") + } +} + +android { + namespace = "com.mapconductor.mappls" + compileSdk = project.property("compileSdk").toString().toInt() + + defaultConfig { + minSdk = project.property("minSdk").toString().toInt() + consumerProguardFiles("consumer-rules.pro") + } + + buildFeatures { + compose = true + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(project.property("javaVersion").toString()) + targetCompatibility = JavaVersion.toVersion(project.property("javaVersion").toString()) + } + + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +val libraryGroupId = project.findProperty("libraryGroupId") as String? ?: "com.mapconductor" +val libraryArtifactId = "for-mappls" +val libraryVersion = project.findProperty("libraryVersion") as String? ?: "1.0.0" + +dependencies { + // CancellableContinuation.resume の 3 引数 onCancellation は coroutines 1.9 以降。 + // 明示しないと推移的に古い版が入り、単体ビルドだけがコンパイルエラーになる。 + implementation(libs.kotlinx.coroutines.android) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.foundation) + + // Lifecycle(MapView が Lifecycle を要求する) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.common.java8) + + // Mappls SDK(Mapbox GL Android v9 系のリネームフォーク。 + // 認証はアプリ側の .a.conf / .a.olf + com.mappls.services.android プラグイン) + api(libs.mappls.sdk) + + // Mappls のタイル取得は OkHttp。`HttpRequestUtil.setOkHttpClient()` に + // Interceptor 付きクライアントを渡してラスタタイルのヘッダを差し替える + // (MapplsRasterHeaderInjector)。 + implementation(libs.okhttp) + + // 集約ビルド(android-sdk)ではプロジェクト参照、単体ビルド(CI のリリース)では + // Maven 座標で解決する。他プロバイダと同じ形。 + if (findProject(":android-sdk-compose") != null) { + api(project(":android-sdk-compose")) + } else { + api("com.mapconductor:compose:$libraryVersion") + } + + testImplementation(libs.junit) +} + +// Set project version for NMCP plugin +version = libraryVersion +val libraryName = "MapConductor for Mappls" +val libraryDescription = "Mappls (MapmyIndia) implementation for MapConductor unified mapping library" + +// Gradle 9.6 で `by tasks.registering` は非推奨(スクリプトコンパイルエラーになる)。 +// googlemaps 等の既存モジュールは古い形のままなので、再コンパイル時に同じ問題が出る。 +val javadocJar = + tasks.register("javadocJar") { + archiveClassifier.set("javadoc") + } + +afterEvaluate { + publishing { + publications { + create("release") { + from(components["release"]) + + groupId = libraryGroupId + artifactId = libraryArtifactId + version = libraryVersion + + artifact(javadocJar.get()) + + pom { + name.set(libraryName) + description.set(libraryDescription) + url.set( + project.findProperty("libraryUrl") as String? + ?: "https://github.com/MapConductor/android-for-mappls", + ) + + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + + developers { + developer { + id.set(project.findProperty("developerId") as String? ?: "mapconductor") + name.set(project.findProperty("developerName") as String? ?: "MapConductor Team") + email.set(project.findProperty("developerEmail") as String? ?: "info@mkgeeklab.com") + } + } + + scm { + connection.set("scm:git:git://github.com/MapConductor/android-for-mappls.git") + developerConnection + .set("scm:git:ssh://github.com:MapConductor/android-for-mappls.git") + url.set( + project.findProperty("scmUrl") as String? + ?: "https://github.com/MapConductor/android-for-mappls.git", + ) + } + } + } + } + + repositories { + maven { + name = "GitHubPackages" + setUrl("https://maven.pkg.github.com/MapConductor/android-for-mappls") + credentials { + username = + project.findProperty("gpr.user") as String? ?: System.getenv("GPR_USER") + ?: System.getenv("GITHUB_ACTOR") + password = + project.findProperty("gpr.key") as String? ?: System.getenv("GPR_TOKEN") + ?: System.getenv("GITHUB_TOKEN") + } + } + } + } + + signing { + val signingKey = findProperty("signingKey") as String? + val signingPassword = findProperty("signingPassword") as String? + if (!signingKey.isNullOrEmpty() && !signingPassword.isNullOrEmpty()) { + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications["release"]) + } + } + + if (project == rootProject) { + // standalone build only — in multi-project (android-sdk), parent configures nmcp + nmcp { + publishAllPublicationsToCentralPortal { + username.set(findProperty("ossrh_username") as String? ?: System.getenv("OSSRH_USERNAME") ?: "") + password.set(findProperty("ossrh_password") as String? ?: System.getenv("OSSRH_PASSWORD") ?: "") + } + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index cc470ad..68da554 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,48 +1,48 @@ -pluginManagement { - repositories { - google { - content { - includeGroupByRegex("com\\.android.*") - includeGroupByRegex("com\\.google.*") - includeGroupByRegex("androidx.*") - } - } - mavenCentral() - gradlePluginPortal() - } -} - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - mavenLocal() - google() - mavenCentral() - maven { - // Mappls (MapmyIndia) Maven repository - url = uri("https://maven.mappls.com/repository/mappls/") - } - if (!System.getenv("GPR_USER").isNullOrEmpty() && !System.getenv("GPR_TOKEN").isNullOrEmpty()) { - maven { - name = "GithubPackages" - url = uri("https://maven.pkg.github.com/mapconductor/android-for-mappls") - credentials { - username = System.getenv("GPR_USER") ?: "" - password = System.getenv("GPR_TOKEN") ?: "" - } - } - } - if (!System.getenv("GPR_USER").isNullOrEmpty() || !System.getenv("GITHUB_ACTOR").isNullOrEmpty()) { - maven { - name = "GithubPackages-core" - url = uri("https://maven.pkg.github.com/MapConductor/android-sdk-core") - credentials { - username = System.getenv("GPR_USER") ?: System.getenv("GITHUB_ACTOR") ?: "" - password = System.getenv("GPR_TOKEN") ?: System.getenv("GITHUB_TOKEN") ?: "" - } - } - } - } -} - -rootProject.name = "android-for-mappls" +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenLocal() + google() + mavenCentral() + maven { + // Mappls (MapmyIndia) Maven repository + url = uri("https://maven.mappls.com/repository/mappls/") + } + if (!System.getenv("GPR_USER").isNullOrEmpty() && !System.getenv("GPR_TOKEN").isNullOrEmpty()) { + maven { + name = "GithubPackages" + url = uri("https://maven.pkg.github.com/mapconductor/android-for-mappls") + credentials { + username = System.getenv("GPR_USER") ?: "" + password = System.getenv("GPR_TOKEN") ?: "" + } + } + } + if (!System.getenv("GPR_USER").isNullOrEmpty() || !System.getenv("GITHUB_ACTOR").isNullOrEmpty()) { + maven { + name = "GithubPackages-core" + url = uri("https://maven.pkg.github.com/MapConductor/android-sdk-core") + credentials { + username = System.getenv("GPR_USER") ?: System.getenv("GITHUB_ACTOR") ?: "" + password = System.getenv("GPR_TOKEN") ?: System.getenv("GITHUB_TOKEN") ?: "" + } + } + } + } +} + +rootProject.name = "android-for-mappls" diff --git a/src/main/java/com/mapconductor/mappls/MapplsMapView.kt b/src/main/java/com/mapconductor/mappls/MapplsMapView.kt index 77409ab..862b41f 100644 --- a/src/main/java/com/mapconductor/mappls/MapplsMapView.kt +++ b/src/main/java/com/mapconductor/mappls/MapplsMapView.kt @@ -1,380 +1,380 @@ -package com.mapconductor.mappls - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import com.mapconductor.compose.map.MapViewBase -import com.mapconductor.core.OnCameraMoveHandler -import com.mapconductor.core.OnMapEventHandler -import com.mapconductor.core.OnMapLoadedHandler -import com.mapconductor.core.circle.CircleManager -import com.mapconductor.core.map.CameraRestriction -import com.mapconductor.core.map.MapCameraPositionInterface -import com.mapconductor.core.map.MapViewStyle -import com.mapconductor.core.map.MutableMapServiceRegistry -import com.mapconductor.core.marker.MarkerEventControllerInterface -import com.mapconductor.core.marker.MarkerManager -import com.mapconductor.core.marker.MarkerOverlayRendererInterface -import com.mapconductor.core.marker.MarkerRenderingStrategyInterface -import com.mapconductor.core.marker.MarkerRenderingSupport -import com.mapconductor.core.marker.MarkerRenderingSupportKey -import com.mapconductor.core.marker.MarkerTilingOptions -import com.mapconductor.core.marker.StrategyMarkerController -import com.mapconductor.core.polygon.PolygonManager -import com.mapconductor.core.polyline.PolylineManager -import com.mapconductor.mappls.circle.MapplsCircleController -import com.mapconductor.mappls.circle.MapplsCircleLayer -import com.mapconductor.mappls.circle.MapplsCircleOverlayRenderer -import com.mapconductor.mappls.groundimage.MapplsGroundImageController -import com.mapconductor.mappls.groundimage.MapplsGroundImageOverlayRenderer -import com.mapconductor.mappls.marker.MapplsMarkerController -import com.mapconductor.mappls.marker.MapplsMarkerOverlayRenderer -import com.mapconductor.mappls.marker.MarkerDragLayer -import com.mapconductor.mappls.marker.MarkerLayer -import com.mapconductor.mappls.polygon.MapplsPolygonConductor -import com.mapconductor.mappls.polygon.MapplsPolygonLayer -import com.mapconductor.mappls.polygon.MapplsPolygonOverlayRenderer -import com.mapconductor.mappls.polyline.MapplsPolylineController -import com.mapconductor.mappls.polyline.MapplsPolylineLayer -import com.mapconductor.mappls.polyline.MapplsPolylineOverlayRenderer -import com.mapconductor.mappls.raster.MapplsRasterLayerController -import com.mapconductor.mappls.raster.MapplsRasterLayerOverlayRenderer -import com.mappls.sdk.maps.MapView -import com.mappls.sdk.maps.Mappls -import com.mappls.sdk.maps.MapplsMap -import com.mappls.sdk.maps.MapplsMapOptions -import com.mappls.sdk.maps.OnMapReadyCallback -import com.mappls.sdk.maps.Style -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.util.Log -import kotlinx.coroutines.suspendCancellableCoroutine - -@Composable -fun MapplsMapView( - state: MapplsViewState, - modifier: Modifier = Modifier, - markerTiling: MarkerTilingOptions? = null, - cameraRestriction: CameraRestriction? = null, - sdkInitialize: (suspend (Context) -> Boolean)? = null, - onMapLoaded: OnMapLoadedHandler? = null, - onMapClick: OnMapEventHandler? = null, - onMapLongClick: OnMapEventHandler? = null, - onCameraMoveStart: OnCameraMoveHandler? = null, - onCameraMove: OnCameraMoveHandler? = null, - onCameraMoveEnd: OnCameraMoveHandler? = null, - /** - * How the map looks, when the app states it rather than naming a design. - * - * A vector style *is* the basemap. `com.mapconductor:vectorstyle` builds - * one; what happens underneath depends on this backend and the app does - * not have to know. - */ - style: MapViewStyle? = null, - onStyleDiagnostics: ((List) -> Unit)? = null, - content: (@Composable MapplsMapViewScope.() -> Unit)? = null, -) { - val context = LocalContext.current - val scope = remember { MapplsMapViewScope() } - val registry = remember { scope.buildRegistry() } - val cameraState = remember { mutableStateOf(state.cameraPosition) } - - MapViewBase( - state = state, - cameraState = cameraState, - modifier = modifier, - viewProvider = { - val cameraPosition = - state.cameraPosition.toCameraPosition() - val mapInitOptions = - MapplsMapOptions - .createFromAttributes(context) - .camera(cameraPosition) - .textureMode(true) - // Don't set style here - it will be set in holderProvider - - MapView(context, mapInitOptions) - }, - scope = scope, - registry = registry, - onMapLoaded = onMapLoaded, - holderProvider = { mapView -> - suspendCancellableCoroutine { continuation -> - mapView.getMapAsync( - object : OnMapReadyCallback { - override fun onMapReady(map: MapplsMap) { - // 認証が通ると SDK がアカウントの既定スタイルを読み込む。 - // 名前指定があればそのスタイルへ差し替え、読み込み完了後に resume する。 - val designName = state.mapDesignType.styleName - if (designName.isEmpty()) { - map.getStyle { - continuation.resume( - MapplsMapViewHolder(mapView, map), - ) { _, _, _ -> } - } - } else { - map.setMapplsStyle( - designName, - Style.OnStyleLoaded { - continuation.resume( - MapplsMapViewHolder(mapView, map), - ) { _, _, _ -> } - }, - ) - } - } - - override fun onMapError( - code: Int, - message: String?, - ) { - // 認証エラー(コンフィグファイル不一致など)はここへ来る。 - // resume しないので地図は表示されないまま — ログで原因を残す。 - Log.e("Mappls", "Map init error $code: $message") - } - }, - ) - } - }, - controllerProvider = { holder -> - createMapplsViewController( - holder = holder, - markerTiling = markerTiling ?: MarkerTilingOptions.Default, - serviceRegistry = state.serviceRegistry, - ).also { mapController -> - // Store controller reference in holder - mapController.setCameraMoveStartListener { - cameraState.value = it - state.updateCameraPosition(it) - onCameraMoveStart?.invoke(it) - } - mapController.setCameraMoveListener { - cameraState.value = it - state.updateCameraPosition(it) - onCameraMove?.invoke(it) - } - mapController.setCameraMoveEndListener { - cameraState.value = it - state.updateCameraPosition(it) - onCameraMoveEnd?.invoke(it) - } - mapController.setMapClickListener(onMapClick) - mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange) - mapController.setMapLongClickListener(onMapLongClick) - cameraRestriction?.let { mapController.setCameraRestriction(it) } - state.setController(mapController) - // Post an initial camera update after layout to compute visibleRegion correctly - holder.mapView.post { mapController.sendInitialCameraUpdate() } - } - }, - sdkInitialize = { - // 外部タイルのホワイトリスト回避(MapplsHttpBridge のコメント参照)は - // 地図生成前に必ず入れる。sdkInitialize を差し替えられても失われないよう - // ここで無条件に呼ぶ。 - MapplsHttpBridge.install() - if (sdkInitialize != null) { - sdkInitialize(context) - } else { - Mappls.getInstance(context) - true - } - }, - // Pass content if it needs to be rendered within the overlay providers in MapViewBase, - // or handle it here if it's specific to MapplsMapView structure before calling MapViewBase. - // For now, assuming content relates to overlay definitions. - style = style, - onStyleDiagnostics = onStyleDiagnostics, - content = content, // This might need adjustment based on how overlays are handled - ) -} - -/** - * Creates the imperative controller graph used by both the Compose MapView and non-Compose hosts - * such as React Native. Keeping this construction here prevents provider-specific layer setup from - * being duplicated by each UI integration. - * - * @param serviceRegistry 登録先のサービスレジストリ。Compose からは `state.serviceRegistry` を渡す - * (react-sdk / ios-sdk と同じく持ち主は state)。React Native / Cordova のような非 Compose - * ホストは state を持たないので、自前のレジストリを渡す。 - */ -fun createMapplsViewController( - holder: MapplsMapViewHolderInterface, - markerTiling: MarkerTilingOptions = MarkerTilingOptions.Default, - serviceRegistry: MutableMapServiceRegistry? = null, -): MapplsViewController { - // 非 Compose ホスト(React Native / Cordova)はこちらが入口なので、ここでも入れる - MapplsHttpBridge.install() - val markerController = getMarkerController(holder, markerTiling) - val mapController = - MapplsViewController( - holder = holder, - markerController = markerController, - polylineController = getPolylineController(holder), - polygonController = getPolygonController(holder), - groundImageController = getGroundImageController(holder), - circleController = getCircleController(holder), - rasterLayerController = getRasterLayerController(holder), - ) - - serviceRegistry?.let { registry -> - MapplsCapabilities.declare(registry) - registry.put( - MarkerRenderingSupportKey, - object : MarkerRenderingSupport { - override fun createMarkerRenderer( - strategy: MarkerRenderingStrategyInterface, - ): MarkerOverlayRendererInterface = mapController.createMarkerRenderer(strategy) - - override fun createMarkerEventController( - controller: StrategyMarkerController, - renderer: MarkerOverlayRendererInterface, - ): MarkerEventControllerInterface = - mapController.createMarkerEventController(controller, renderer) - - override fun registerMarkerEventController( - controller: MarkerEventControllerInterface, - ) { - mapController.registerMarkerEventController(controller) - } - - override fun onMarkerRenderingReady() { - mapController.sendInitialCameraUpdate() - } - }, - ) - } - return mapController -} - -fun getMarkerController( - holder: MapplsMapViewHolderInterface, - markerTiling: MarkerTilingOptions, -): MapplsMarkerController { - val manager = MarkerManager.defaultManager() - val markerLayer = - MarkerLayer( - sourceId = "markers-source", - layerId = "markers-layer", - ) - val dragLayer = - MarkerDragLayer( - sourceId = "marker-drag-source", - layerId = "marker-drag-layer", - ) - val renderer = - MapplsMarkerOverlayRenderer( - holder = holder, - markerLayer = markerLayer, - dragLayer = dragLayer, - markerManager = manager, - ) - - val controller = - MapplsMarkerController( - renderer = renderer, - markerTiling = markerTiling, - ) - return controller -} - -fun getPolylineController(holder: MapplsMapViewHolderInterface): MapplsPolylineController { - val polylineLayer = - MapplsPolylineLayer( - sourceId = "polyline-source", - layerId = "polyline-layer", - ) - val polylineManager = PolylineManager() - - val renderer = - MapplsPolylineOverlayRenderer( - layer = polylineLayer, - polylineManager = polylineManager, - holder = holder, - ) - - val controller = - MapplsPolylineController( - renderer = renderer, - ) - return controller -} - -fun getPolygonController(holder: MapplsMapViewHolderInterface): MapplsPolygonConductor { - val polylineLayer = - MapplsPolylineLayer( - sourceId = "polygon-outline-source", - layerId = "polygon-outline-layer", - ) - val polylineManager = PolylineManager() - val polylineOverlayRenderer = - MapplsPolylineOverlayRenderer( - layer = polylineLayer, - polylineManager = polylineManager, - holder = holder, - ) - - val polygonManager = PolygonManager() - val polygonLayer = - MapplsPolygonLayer( - sourceId = "polygon-fill-source", - layerId = "polygon-fill-layer", - ) - val polygonOverlayRenderer = - MapplsPolygonOverlayRenderer( - layer = polygonLayer, - polygonManager = polygonManager, - holder = holder, - ) - - return MapplsPolygonConductor( - polygonOverlay = polygonOverlayRenderer, - polylineOverlay = polylineOverlayRenderer, - ) -} - -fun getCircleController(holder: MapplsMapViewHolderInterface): MapplsCircleController { - val circleLayer = - MapplsCircleLayer( - sourceId = "circle-source", - layerId = "circle-layer", - ) - val circleManager = CircleManager() - val renderer = - MapplsCircleOverlayRenderer( - layer = circleLayer, - circleManager = circleManager, - holder = holder, - ) - return MapplsCircleController( - renderer = renderer, - circleManager = circleManager, - ) -} - -fun getRasterLayerController(holder: MapplsMapViewHolderInterface): MapplsRasterLayerController { - val renderer = - MapplsRasterLayerOverlayRenderer( - holder = holder, - ) - return MapplsRasterLayerController( - renderer = renderer, - ) -} - -fun getGroundImageController(holder: MapplsMapViewHolderInterface): MapplsGroundImageController { - val renderer = - MapplsGroundImageOverlayRenderer( - holder = holder, - ) - return MapplsGroundImageController(renderer = renderer) -} - -internal fun Context.findActivity(): Activity? = - when (this) { - is Activity -> this - is ContextWrapper -> baseContext.findActivity() - else -> null - } +package com.mapconductor.mappls + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import com.mapconductor.compose.map.MapViewBase +import com.mapconductor.core.OnCameraMoveHandler +import com.mapconductor.core.OnMapEventHandler +import com.mapconductor.core.OnMapLoadedHandler +import com.mapconductor.core.circle.CircleManager +import com.mapconductor.core.map.CameraRestriction +import com.mapconductor.core.map.MapCameraPositionInterface +import com.mapconductor.core.map.MapViewStyle +import com.mapconductor.core.map.MutableMapServiceRegistry +import com.mapconductor.core.marker.MarkerEventControllerInterface +import com.mapconductor.core.marker.MarkerManager +import com.mapconductor.core.marker.MarkerOverlayRendererInterface +import com.mapconductor.core.marker.MarkerRenderingStrategyInterface +import com.mapconductor.core.marker.MarkerRenderingSupport +import com.mapconductor.core.marker.MarkerRenderingSupportKey +import com.mapconductor.core.marker.MarkerTilingOptions +import com.mapconductor.core.marker.StrategyMarkerController +import com.mapconductor.core.polygon.PolygonManager +import com.mapconductor.core.polyline.PolylineManager +import com.mapconductor.mappls.circle.MapplsCircleController +import com.mapconductor.mappls.circle.MapplsCircleLayer +import com.mapconductor.mappls.circle.MapplsCircleOverlayRenderer +import com.mapconductor.mappls.groundimage.MapplsGroundImageController +import com.mapconductor.mappls.groundimage.MapplsGroundImageOverlayRenderer +import com.mapconductor.mappls.marker.MapplsMarkerController +import com.mapconductor.mappls.marker.MapplsMarkerOverlayRenderer +import com.mapconductor.mappls.marker.MarkerDragLayer +import com.mapconductor.mappls.marker.MarkerLayer +import com.mapconductor.mappls.polygon.MapplsPolygonConductor +import com.mapconductor.mappls.polygon.MapplsPolygonLayer +import com.mapconductor.mappls.polygon.MapplsPolygonOverlayRenderer +import com.mapconductor.mappls.polyline.MapplsPolylineController +import com.mapconductor.mappls.polyline.MapplsPolylineLayer +import com.mapconductor.mappls.polyline.MapplsPolylineOverlayRenderer +import com.mapconductor.mappls.raster.MapplsRasterLayerController +import com.mapconductor.mappls.raster.MapplsRasterLayerOverlayRenderer +import com.mappls.sdk.maps.MapView +import com.mappls.sdk.maps.Mappls +import com.mappls.sdk.maps.MapplsMap +import com.mappls.sdk.maps.MapplsMapOptions +import com.mappls.sdk.maps.OnMapReadyCallback +import com.mappls.sdk.maps.Style +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.util.Log +import kotlinx.coroutines.suspendCancellableCoroutine + +@Composable +fun MapplsMapView( + state: MapplsViewState, + modifier: Modifier = Modifier, + markerTiling: MarkerTilingOptions? = null, + cameraRestriction: CameraRestriction? = null, + sdkInitialize: (suspend (Context) -> Boolean)? = null, + onMapLoaded: OnMapLoadedHandler? = null, + onMapClick: OnMapEventHandler? = null, + onMapLongClick: OnMapEventHandler? = null, + onCameraMoveStart: OnCameraMoveHandler? = null, + onCameraMove: OnCameraMoveHandler? = null, + onCameraMoveEnd: OnCameraMoveHandler? = null, + /** + * How the map looks, when the app states it rather than naming a design. + * + * A vector style *is* the basemap. `com.mapconductor:vectorstyle` builds + * one; what happens underneath depends on this backend and the app does + * not have to know. + */ + style: MapViewStyle? = null, + onStyleDiagnostics: ((List) -> Unit)? = null, + content: (@Composable MapplsMapViewScope.() -> Unit)? = null, +) { + val context = LocalContext.current + val scope = remember { MapplsMapViewScope() } + val registry = remember { scope.buildRegistry() } + val cameraState = remember { mutableStateOf(state.cameraPosition) } + + MapViewBase( + state = state, + cameraState = cameraState, + modifier = modifier, + viewProvider = { + val cameraPosition = + state.cameraPosition.toCameraPosition() + val mapInitOptions = + MapplsMapOptions + .createFromAttributes(context) + .camera(cameraPosition) + .textureMode(true) + // Don't set style here - it will be set in holderProvider + + MapView(context, mapInitOptions) + }, + scope = scope, + registry = registry, + onMapLoaded = onMapLoaded, + holderProvider = { mapView -> + suspendCancellableCoroutine { continuation -> + mapView.getMapAsync( + object : OnMapReadyCallback { + override fun onMapReady(map: MapplsMap) { + // 認証が通ると SDK がアカウントの既定スタイルを読み込む。 + // 名前指定があればそのスタイルへ差し替え、読み込み完了後に resume する。 + val designName = state.mapDesignType.styleName + if (designName.isEmpty()) { + map.getStyle { + continuation.resume( + MapplsMapViewHolder(mapView, map), + ) { _, _, _ -> } + } + } else { + map.setMapplsStyle( + designName, + Style.OnStyleLoaded { + continuation.resume( + MapplsMapViewHolder(mapView, map), + ) { _, _, _ -> } + }, + ) + } + } + + override fun onMapError( + code: Int, + message: String?, + ) { + // 認証エラー(コンフィグファイル不一致など)はここへ来る。 + // resume しないので地図は表示されないまま — ログで原因を残す。 + Log.e("Mappls", "Map init error $code: $message") + } + }, + ) + } + }, + controllerProvider = { holder -> + createMapplsViewController( + holder = holder, + markerTiling = markerTiling ?: MarkerTilingOptions.Default, + serviceRegistry = state.serviceRegistry, + ).also { mapController -> + // Store controller reference in holder + mapController.setCameraMoveStartListener { + cameraState.value = it + state.updateCameraPosition(it) + onCameraMoveStart?.invoke(it) + } + mapController.setCameraMoveListener { + cameraState.value = it + state.updateCameraPosition(it) + onCameraMove?.invoke(it) + } + mapController.setCameraMoveEndListener { + cameraState.value = it + state.updateCameraPosition(it) + onCameraMoveEnd?.invoke(it) + } + mapController.setMapClickListener(onMapClick) + mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange) + mapController.setMapLongClickListener(onMapLongClick) + cameraRestriction?.let { mapController.setCameraRestriction(it) } + state.setController(mapController) + // Post an initial camera update after layout to compute visibleRegion correctly + holder.mapView.post { mapController.sendInitialCameraUpdate() } + } + }, + sdkInitialize = { + // 外部タイルのホワイトリスト回避(MapplsHttpBridge のコメント参照)は + // 地図生成前に必ず入れる。sdkInitialize を差し替えられても失われないよう + // ここで無条件に呼ぶ。 + MapplsHttpBridge.install() + if (sdkInitialize != null) { + sdkInitialize(context) + } else { + Mappls.getInstance(context) + true + } + }, + // Pass content if it needs to be rendered within the overlay providers in MapViewBase, + // or handle it here if it's specific to MapplsMapView structure before calling MapViewBase. + // For now, assuming content relates to overlay definitions. + style = style, + onStyleDiagnostics = onStyleDiagnostics, + content = content, // This might need adjustment based on how overlays are handled + ) +} + +/** + * Creates the imperative controller graph used by both the Compose MapView and non-Compose hosts + * such as React Native. Keeping this construction here prevents provider-specific layer setup from + * being duplicated by each UI integration. + * + * @param serviceRegistry 登録先のサービスレジストリ。Compose からは `state.serviceRegistry` を渡す + * (react-sdk / ios-sdk と同じく持ち主は state)。React Native / Cordova のような非 Compose + * ホストは state を持たないので、自前のレジストリを渡す。 + */ +fun createMapplsViewController( + holder: MapplsMapViewHolderInterface, + markerTiling: MarkerTilingOptions = MarkerTilingOptions.Default, + serviceRegistry: MutableMapServiceRegistry? = null, +): MapplsViewController { + // 非 Compose ホスト(React Native / Cordova)はこちらが入口なので、ここでも入れる + MapplsHttpBridge.install() + val markerController = getMarkerController(holder, markerTiling) + val mapController = + MapplsViewController( + holder = holder, + markerController = markerController, + polylineController = getPolylineController(holder), + polygonController = getPolygonController(holder), + groundImageController = getGroundImageController(holder), + circleController = getCircleController(holder), + rasterLayerController = getRasterLayerController(holder), + ) + + serviceRegistry?.let { registry -> + MapplsCapabilities.declare(registry) + registry.put( + MarkerRenderingSupportKey, + object : MarkerRenderingSupport { + override fun createMarkerRenderer( + strategy: MarkerRenderingStrategyInterface, + ): MarkerOverlayRendererInterface = mapController.createMarkerRenderer(strategy) + + override fun createMarkerEventController( + controller: StrategyMarkerController, + renderer: MarkerOverlayRendererInterface, + ): MarkerEventControllerInterface = + mapController.createMarkerEventController(controller, renderer) + + override fun registerMarkerEventController( + controller: MarkerEventControllerInterface, + ) { + mapController.registerMarkerEventController(controller) + } + + override fun onMarkerRenderingReady() { + mapController.sendInitialCameraUpdate() + } + }, + ) + } + return mapController +} + +fun getMarkerController( + holder: MapplsMapViewHolderInterface, + markerTiling: MarkerTilingOptions, +): MapplsMarkerController { + val manager = MarkerManager.defaultManager() + val markerLayer = + MarkerLayer( + sourceId = "markers-source", + layerId = "markers-layer", + ) + val dragLayer = + MarkerDragLayer( + sourceId = "marker-drag-source", + layerId = "marker-drag-layer", + ) + val renderer = + MapplsMarkerOverlayRenderer( + holder = holder, + markerLayer = markerLayer, + dragLayer = dragLayer, + markerManager = manager, + ) + + val controller = + MapplsMarkerController( + renderer = renderer, + markerTiling = markerTiling, + ) + return controller +} + +fun getPolylineController(holder: MapplsMapViewHolderInterface): MapplsPolylineController { + val polylineLayer = + MapplsPolylineLayer( + sourceId = "polyline-source", + layerId = "polyline-layer", + ) + val polylineManager = PolylineManager() + + val renderer = + MapplsPolylineOverlayRenderer( + layer = polylineLayer, + polylineManager = polylineManager, + holder = holder, + ) + + val controller = + MapplsPolylineController( + renderer = renderer, + ) + return controller +} + +fun getPolygonController(holder: MapplsMapViewHolderInterface): MapplsPolygonConductor { + val polylineLayer = + MapplsPolylineLayer( + sourceId = "polygon-outline-source", + layerId = "polygon-outline-layer", + ) + val polylineManager = PolylineManager() + val polylineOverlayRenderer = + MapplsPolylineOverlayRenderer( + layer = polylineLayer, + polylineManager = polylineManager, + holder = holder, + ) + + val polygonManager = PolygonManager() + val polygonLayer = + MapplsPolygonLayer( + sourceId = "polygon-fill-source", + layerId = "polygon-fill-layer", + ) + val polygonOverlayRenderer = + MapplsPolygonOverlayRenderer( + layer = polygonLayer, + polygonManager = polygonManager, + holder = holder, + ) + + return MapplsPolygonConductor( + polygonOverlay = polygonOverlayRenderer, + polylineOverlay = polylineOverlayRenderer, + ) +} + +fun getCircleController(holder: MapplsMapViewHolderInterface): MapplsCircleController { + val circleLayer = + MapplsCircleLayer( + sourceId = "circle-source", + layerId = "circle-layer", + ) + val circleManager = CircleManager() + val renderer = + MapplsCircleOverlayRenderer( + layer = circleLayer, + circleManager = circleManager, + holder = holder, + ) + return MapplsCircleController( + renderer = renderer, + circleManager = circleManager, + ) +} + +fun getRasterLayerController(holder: MapplsMapViewHolderInterface): MapplsRasterLayerController { + val renderer = + MapplsRasterLayerOverlayRenderer( + holder = holder, + ) + return MapplsRasterLayerController( + renderer = renderer, + ) +} + +fun getGroundImageController(holder: MapplsMapViewHolderInterface): MapplsGroundImageController { + val renderer = + MapplsGroundImageOverlayRenderer( + holder = holder, + ) + return MapplsGroundImageController(renderer = renderer) +} + +internal fun Context.findActivity(): Activity? = + when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null + }