diff --git a/bom/build.gradle.kts b/bom/build.gradle.kts index 941902952..326cc2bca 100644 --- a/bom/build.gradle.kts +++ b/bom/build.gradle.kts @@ -17,79 +17,7 @@ */ plugins { - id("maven-publish") - id("java-platform") + `maven-publish` + `java-platform` + alias(libs.plugins.arcgismaps.kotlin.bom.convention) } - -// Find these in properties passed through command line or read from GRADLE_HOME/gradle.properties -// or local gradle.properties -val artifactoryGroupId: String by project -val artifactoryArtifactBaseId: String by project -val artifactoryArtifactId: String = "$artifactoryArtifactBaseId-${project.name}" -val artifactoryUrl: String by project -val artifactoryUsername: String by project -val artifactoryPassword: String by project -val versionNumber: String by project -val buildNumber: String by project -val finalBuild: Boolean = (project.properties["finalBuild"] ?: "false") - .run { this == "true" } -val artifactVersion: String = if (finalBuild) { - versionNumber -} else { - "$versionNumber-$buildNumber" -} - -// ensure that the evaluation of the bom project happens after all other projects -// so that plugins are applied to all projects, and can be used to identify -// which projects should get written into the BOM's pom file. -rootProject.subprojects.filter { - it.name != project.name -}.forEach { - evaluationDependsOn(":${it.name}") -} - -// now find projects which are publishable based on their inclusion -// of the publishing plugin, and add them as api dependencies. -dependencies { - constraints { - project.rootProject.subprojects.filter { - it.plugins.findPlugin("artifact-deploy") != null - }.forEach { subproject -> - // add all the intended library projects as api dependencies. - api(subproject) - } - } -} - -afterEvaluate { - /** - * Maven publication configuration for aar and pom file. Run as follows: - * ./gradlew publishAarPublicationToMavenRepository -PartifactoryUsername= -PartifactoryPassword= - * - * More details: - * https://docs.gradle.org/current/userguide/publishing_maven.html - */ - publishing { - publications { - create("bom") { - groupId = artifactoryGroupId - artifactId = artifactoryArtifactId - version = artifactVersion - - from(components["javaPlatform"]) - } - } - - repositories { - maven { - url = uri(artifactoryUrl) - credentials { - username = artifactoryUsername - password = artifactoryPassword - } - } - } - } -} - - diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index fac128bb2..80fe41e8e 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -19,10 +19,12 @@ tasks.withType().configureEach { } dependencies { + compileOnly(gradleApi()) compileOnly(libs.android.gradlePlugin) compileOnly(libs.android.tools.common) compileOnly(libs.kotlin.gradlePlugin) compileOnly(libs.ksp.gradlePlugin) + implementation(libs.dokka.gradle.plugin) } tasks { @@ -58,5 +60,21 @@ gradlePlugin { id = "arcgismaps.kotlin.microapp" implementationClass = "ArcGISMapsKotlinMicroappConventionPlugin" } + register("arcGISMapsKotlinSDK") { + id = "arcgismaps.kotlin.sdk" + implementationClass = "ArcGISMapsKotlinSDKConventionPlugin" + } + register("arcGISMapsRootConventionPlugin") { + id = "arcgismaps.kotlin.root.convention" + implementationClass = "ArcGISMapsRootConventionPlugin" + } + register("arcGISMapsKdocConventionPlugin") { + id = "arcgismaps.kotlin.kdoc.convention" + implementationClass = "ArcGISMapsKdocConventionPlugin" + } + register("arcGISMapsBomConventionPlugin") { + id = "arcgismaps.kotlin.bom.convention" + implementationClass = "ArcGISMapsBomConventionPlugin" + } } } diff --git a/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt index 06dc52b38..ef6e46266 100644 --- a/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt @@ -1,19 +1,35 @@ import com.android.build.api.dsl.ApplicationExtension import com.esri.arcgismaps.kotlin.build_logic.convention.configureAndroidCompose +import com.esri.arcgismaps.kotlin.build_logic.convention.configureKotlinAndroid +import com.esri.arcgismaps.kotlin.build_logic.extensions.debugImplementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.implementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getByType class AndroidApplicationComposeConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { with(pluginManager) { - apply("com.android.application") - apply("org.jetbrains.kotlin.android") - apply("org.jetbrains.kotlin.plugin.compose") + apply(libs.findPlugin("android-application").get().get().pluginId) + apply(libs.findPlugin("kotlin-android").get().get().pluginId) + apply(libs.findPlugin("compose-compiler").get().get().pluginId) } val extension = extensions.getByType() + configureKotlinAndroid(extension) configureAndroidCompose(extension) + + // Add common Compose dependencies for application modules + dependencies { + implementation(platform(libs.findLibrary("androidx-compose-bom").get())) + implementation(libs.findBundle("composeCore").get()) + implementation(libs.findBundle("core").get()) + implementation(libs.findLibrary("androidx-activity-compose").get()) + implementation(libs.findLibrary("androidx-lifecycle-viewmodel-compose").get()) + debugImplementation(libs.findBundle("debug").get()) + } } } } diff --git a/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt index 67049682e..03bdc1f2e 100644 --- a/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt +++ b/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt @@ -1,32 +1,34 @@ import com.android.build.api.dsl.ApplicationExtension import com.esri.arcgismaps.kotlin.build_logic.convention.configureKotlinAndroid -import com.esri.arcgismaps.kotlin.build_logic.convention.libs +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.get -class AndroidApplicationConventionPlugin: Plugin { +class AndroidApplicationConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { with(pluginManager) { - apply("com.android.application") - apply("org.jetbrains.kotlin.android") + apply(libs.findPlugin("android-application").get().get().pluginId) + apply(libs.findPlugin("kotlin-android").get().get().pluginId) } extensions.configure { configureKotlinAndroid(this) - compileSdk = 35 defaultConfig { - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } minSdk = libs.findVersion("minSdk").get().toString().toInt() - targetSdk = libs.findVersion("targetSdk").get().toString().toInt() - versionCode = libs.findVersion("versionCode").get().toString().toInt() - versionName = libs.findVersion("versionName").get().toString() + targetSdk = libs.findVersion("compileSdk").get().toString().toInt() + versionCode = 1 + versionName = "1.0" + } + + buildFeatures { + buildConfig = true } buildTypes { diff --git a/build-logic/convention/src/main/java/AndroidLibraryComposeConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidLibraryComposeConventionPlugin.kt index c20d05e75..629ccf508 100644 --- a/build-logic/convention/src/main/java/AndroidLibraryComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/java/AndroidLibraryComposeConventionPlugin.kt @@ -1,19 +1,44 @@ import com.android.build.api.dsl.LibraryExtension import com.esri.arcgismaps.kotlin.build_logic.convention.configureAndroidCompose +import com.esri.arcgismaps.kotlin.build_logic.convention.configureKotlinAndroid +import com.esri.arcgismaps.kotlin.build_logic.extensions.implementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs +import com.esri.arcgismaps.kotlin.build_logic.extensions.testImplementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.androidTestImplementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.debugImplementation import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getByType class AndroidLibraryComposeConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { with(pluginManager) { - apply("com.android.library") - apply("org.jetbrains.kotlin.android") - apply("org.jetbrains.kotlin.plugin.compose") + apply(libs.findPlugin("android-library").get().get().pluginId) + apply(libs.findPlugin("kotlin-android").get().get().pluginId) + apply(libs.findPlugin("compose-compiler").get().get().pluginId) + apply(libs.findPlugin("kotlin-parcelize").get().get().pluginId) + apply(libs.findPlugin("kotlin-serialization").get().get().pluginId) } val extension = extensions.getByType() + configureKotlinAndroid(extension) configureAndroidCompose(extension) + + // Add common Compose dependencies for library modules + dependencies { + implementation(platform(libs.findLibrary("androidx-compose-bom").get())) + implementation(libs.findBundle("composeCore").get()) + implementation(libs.findBundle("core").get()) + implementation(libs.findLibrary("androidx-lifecycle-runtime-compose").get()) + implementation(libs.findLibrary("androidx-activity-compose").get()) + implementation(libs.findLibrary("androidx-material-icons").get()) + implementation(libs.findLibrary("kotlinx-serialization-json").get()) + testImplementation(libs.findBundle("unitTest").get()) + androidTestImplementation(libs.findBundle("composeTest").get()) + androidTestImplementation(libs.findBundle("androidXTest").get()) + debugImplementation(libs.findBundle("debug").get()) + } } } } diff --git a/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt index 53d5fc774..44514aec1 100644 --- a/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt @@ -1,8 +1,7 @@ import com.android.build.gradle.LibraryExtension import com.esri.arcgismaps.kotlin.build_logic.convention.configureKotlinAndroid -import com.esri.arcgismaps.kotlin.build_logic.convention.implementation -import com.esri.arcgismaps.kotlin.build_logic.convention.libs -import com.esri.arcgismaps.kotlin.build_logic.convention.testImplementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs +import com.esri.arcgismaps.kotlin.build_logic.extensions.testImplementation import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure @@ -13,20 +12,20 @@ class AndroidLibraryConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { with(pluginManager) { - apply("com.android.library") - apply("org.jetbrains.kotlin.android") + apply(libs.findPlugin("android-library").get().get().pluginId) + apply(libs.findPlugin("kotlin-android").get().get().pluginId) } extensions.configure { configureKotlinAndroid(this) - compileSdk = 35 defaultConfig { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } minSdk = libs.findVersion("minSdk").get().toString().toInt() - lint.targetSdk = libs.findVersion("targetSdk").get().toString().toInt() + lint.targetSdk = libs.findVersion("compileSdk").get().toString().toInt() + consumerProguardFiles("consumer-rules.pro") } buildTypes { @@ -48,10 +47,6 @@ class AndroidLibraryConventionPlugin : Plugin { dependencies { testImplementation(kotlin("test")) - // External libraries - implementation(libs.findLibrary("androidx-constraintlayout").get()) - implementation(libs.findLibrary("androidx-appcompat").get()) - implementation(libs.findLibrary("android-material").get()) } } } diff --git a/build-logic/convention/src/main/java/ArcGISMapsBomConventionPlugin.kt b/build-logic/convention/src/main/java/ArcGISMapsBomConventionPlugin.kt new file mode 100644 index 000000000..f99edca73 --- /dev/null +++ b/build-logic/convention/src/main/java/ArcGISMapsBomConventionPlugin.kt @@ -0,0 +1,74 @@ +import com.esri.arcgismaps.kotlin.build_logic.extensions.ToolkitRegistryExtension +import com.esri.arcgismaps.kotlin.build_logic.extensions.api +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.provideDelegate + +class ArcGISMapsBomConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + // Platform publishing + pluginManager.apply("maven-publish") + pluginManager.apply("java-platform") + + // Find these in properties passed through command line or read from GRADLE_HOME/gradle.properties + // or local gradle.properties + val artifactoryGroupId: String by project + val artifactoryArtifactBaseId: String by project + val artifactoryUrl: String by project + val artifactoryUsername: String by project + val artifactoryPassword: String by project + val versionNumber: String by project + val buildNumber: String by project + val finalBuild: Boolean = (project.properties["finalBuild"] ?: "false").toString() == "true" + val artifactVersion = if (finalBuild) versionNumber else "$versionNumber-$buildNumber" + val artifactoryArtifactId = "$artifactoryArtifactBaseId-${project.name}" + + // now find projects which are publishable based on their inclusion of the toolkit plugin, + // and add them as api dependencies. + afterEvaluate { + dependencies { + constraints { + // grab the populated set + val registry = rootProject.extensions.getByType(ToolkitRegistryExtension::class.java) + registry.toolkitProjects.forEach { p -> + val moduleArtifactId = "$artifactoryArtifactBaseId-${p.name}" + // add the toolkit project as api + api("$artifactoryGroupId:$moduleArtifactId:$artifactVersion") + } + } + } + } + + /** + * Maven publication configuration for aar and pom file. Run as follows: + * ./gradlew publishAarPublicationToMavenRepository -PartifactoryUsername= -PartifactoryPassword= + * + * More details: + * https://docs.gradle.org/current/userguide/publishing_maven.html + */ + extensions.configure(PublishingExtension::class.java) { + publications { + create("bom", MavenPublication::class.java) { + groupId = artifactoryGroupId + artifactId = artifactoryArtifactId + version = artifactVersion + + from(components["javaPlatform"]) + } + } + repositories { + maven { + url = uri(artifactoryUrl) + credentials { + username = artifactoryUsername + password = artifactoryPassword + } + } + } + } + } +} diff --git a/build-logic/convention/src/main/java/ArcGISMapsKdocConventionPlugin.kt b/build-logic/convention/src/main/java/ArcGISMapsKdocConventionPlugin.kt new file mode 100644 index 000000000..14c6ba42a --- /dev/null +++ b/build-logic/convention/src/main/java/ArcGISMapsKdocConventionPlugin.kt @@ -0,0 +1,70 @@ +import com.esri.arcgismaps.kotlin.build_logic.extensions.ToolkitRegistryExtension +import com.esri.arcgismaps.kotlin.build_logic.extensions.implementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.invoke +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.withType +import org.jetbrains.dokka.gradle.DokkaExtension +import org.jetbrains.dokka.gradle.engine.plugins.DokkaVersioningPluginParameters +import java.io.File + +class ArcGISMapsKdocConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + with(pluginManager) { + apply(libs.findPlugin("arcgismaps-android-library").get().get().pluginId) + apply(libs.findPlugin("dokka").get().get().pluginId) + // Put exposed dependencies in dokka's classpath + apply(libs.findPlugin("arcgismaps.kotlin.sdk").get().get().pluginId) + } + + dependencies { + // Puts the version in the KDoc + add("dokkaPlugin", libs.findLibrary("dokka.versioning").get()) + // Put exposed dependencies in dokka's classpath + implementation(platform(libs.findLibrary("androidx-compose-bom").get())) + implementation(libs.findBundle("composeCore").get()) + } + + val versionNumber: String by project + // Create the toolkit sources provider + val registry = rootProject.extensions.getByType(ToolkitRegistryExtension::class.java) + val sourceRootsProvider = providers.provider { + registry.toolkitProjects.map { p -> + File(rootDir, "toolkit/${p.name}/src/main/java").canonicalPath + } + } + + //./gradlew :kdoc:dokkaGenerate + // doc output will be under `kdoc/build/dokka/html` + extensions.configure { + pluginsConfiguration.withType().configureEach { + version.set(versionNumber) + } + + moduleName.set("arcgis-maps-kotlin-toolkit") + + dokkaSourceSets { + if (findByName("main") == null) { + register("main") + } + + named("main") { + sourceRoots.from(files(sourceRootsProvider)) + } + configureEach { + perPackageOption { + matchingRegex.set(".*internal.*") + suppress.set(true) + } + perPackageOption { + reportUndocumented.set(true) + } + } + } + } + } +} diff --git a/build-logic/convention/src/main/java/ArcGISMapsKotlinMicroappConventionPlugin.kt b/build-logic/convention/src/main/java/ArcGISMapsKotlinMicroappConventionPlugin.kt index 40db7dd42..4cdd8ad4e 100644 --- a/build-logic/convention/src/main/java/ArcGISMapsKotlinMicroappConventionPlugin.kt +++ b/build-logic/convention/src/main/java/ArcGISMapsKotlinMicroappConventionPlugin.kt @@ -1,28 +1,46 @@ -import com.esri.arcgismaps.kotlin.build_logic.convention.implementation -import com.esri.arcgismaps.kotlin.build_logic.convention.libs +import com.android.build.api.dsl.ApplicationExtension +import com.esri.arcgismaps.kotlin.build_logic.convention.ArcGISMapsKotlinSDKDependency +import com.esri.arcgismaps.kotlin.build_logic.extensions.implementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.project class ArcGISMapsKotlinMicroappConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - dependencies { - dependencies { - // if a "build" property is set from the command line like: "-D build=300.X.X-XXXX" - val buildVersion = System.getProperty("build") - // Override version in libs.versions.toml file - if (buildVersion != null) { - implementation("com.esri:arcgis-maps-kotlin:$buildVersion") - } else { - // Use version catalog when no build flag is provided - implementation(libs.findLibrary("arcgis-maps-kotlin").get()) + with(pluginManager) { + apply(libs.findPlugin("arcgismaps-android-application").get().get().pluginId) + apply(libs.findPlugin("arcgismaps-android-application-compose").get().get().pluginId) + apply(libs.findPlugin("gradle-secrets").get().get().pluginId) + } + + + // Configure secrets plugin defaults and custom BuildConfig fields + extensions.configure { + val defaultPropertiesFile = rootProject.file("secrets.defaults.properties") + if (defaultPropertiesFile.exists()) { + val properties = java.util.Properties() + defaultPropertiesFile.inputStream().use { properties.load(it) } + defaultConfig { + // Add each property as a BuildConfig field + properties.forEach { (key, value) -> + val escapedValue = value.toString().replace("\"", "\\\"") + buildConfigField("String", key.toString(), "\"${escapedValue}\"") + } } - // Local project common microapps library - implementation(project(":microapps-lib")) } } + + dependencies { + // Configure the ArcGIS Maps SDK dependency + ArcGISMapsKotlinSDKDependency.configureArcGISMapsDependencies(target) + + // Local project common microapps library + implementation(project(":microapps-lib")) + } } } } diff --git a/build-logic/convention/src/main/java/ArcGISMapsKotlinSDKConventionPlugin.kt b/build-logic/convention/src/main/java/ArcGISMapsKotlinSDKConventionPlugin.kt new file mode 100644 index 000000000..953931809 --- /dev/null +++ b/build-logic/convention/src/main/java/ArcGISMapsKotlinSDKConventionPlugin.kt @@ -0,0 +1,12 @@ +import com.esri.arcgismaps.kotlin.build_logic.convention.ArcGISMapsKotlinSDKDependency +import org.gradle.api.Plugin +import org.gradle.api.Project + +class ArcGISMapsKotlinSDKConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + // Configure the ArcGIS Maps SDK dependency + ArcGISMapsKotlinSDKDependency.configureArcGISMapsDependencies(target) + } + } +} diff --git a/build-logic/convention/src/main/java/ArcGISMapsKotlinToolkitConventionPlugin.kt b/build-logic/convention/src/main/java/ArcGISMapsKotlinToolkitConventionPlugin.kt index 00a8dd343..26d415e91 100644 --- a/build-logic/convention/src/main/java/ArcGISMapsKotlinToolkitConventionPlugin.kt +++ b/build-logic/convention/src/main/java/ArcGISMapsKotlinToolkitConventionPlugin.kt @@ -1,26 +1,104 @@ -import com.esri.arcgismaps.kotlin.build_logic.convention.implementation -import com.esri.arcgismaps.kotlin.build_logic.convention.libs +import com.android.build.api.dsl.LibraryExtension +import com.esri.arcgismaps.kotlin.build_logic.convention.ArcGISMapsKotlinSDKDependency +import com.esri.arcgismaps.kotlin.build_logic.convention.ArtifactPublisher +import com.esri.arcgismaps.kotlin.build_logic.extensions.ToolkitModuleExtension +import com.esri.arcgismaps.kotlin.build_logic.extensions.ToolkitRegistryExtension +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.tasks.testing.Test +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.project +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import java.io.File class ArcGISMapsKotlinToolkitConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - dependencies { - dependencies { - // if a "build" property is set from the command line like: "-D build=300.X.X-XXXX" - val buildVersion = System.getProperty("build") - // Override version in libs.versions.toml file - if (buildVersion != null) { - implementation("com.esri:arcgis-maps-kotlin:$buildVersion") - } else { - // Use version catalog when no build flag is provided - implementation(libs.findLibrary("arcgis-maps-kotlin").get()) + val toolkitExt = extensions.create("toolkit").apply { + applyDefaults() // releasable = true by default + } + + with(pluginManager) { + apply(libs.findPlugin("arcgismaps-android-library").get().get().pluginId) + apply(libs.findPlugin("arcgismaps-android-library-compose").get().get().pluginId) + if (toolkitExt.releasable.get()) { + apply(libs.findPlugin("binary-compatibility-validator").get().get().pluginId) + } + } + // Provide maven publication for releasable toolkit projects + ArtifactPublisher.configureArtifactPublisher(this) + + afterEvaluate { + val registry = rootProject.extensions.getByType(ToolkitRegistryExtension::class.java) + if (toolkitExt.releasable.orNull == true) { + logger.warn("SETTING RELEASABLE: ${this.name}") + registry.toolkitProjects.add(this) + } else { + registry.toolkitProjects.remove(this) + } + } + + extensions.configure { + packaging { + resources { + excludes += setOf( + "META-INF/LICENSE-notice.md", + "META-INF/LICENSE.md" + ) + } + } + + testOptions { + targetSdk = libs.findVersion("compileSdk").get().toString().toInt() + val connectedTestReportsPath = target.findProperty("connectedTestReportsPath") as? String + ?: "${target.rootProject.rootDir}/connectedTestReports" + reportDir = "$connectedTestReportsPath/${target.name}" + } + + publishing { + singleVariant("release") { + // This is the default variant. + } + } + } + + // Automatic detection of androidTest sources + val androidTestDir = File(projectDir, "src/androidTest") + val hasInstrumentedTests = androidTestDir.exists() && androidTestDir.walkTopDown().any { + it.isFile && (it.extension == "kt") + } + // Filter out generating test reports for modules without tests + if (!hasInstrumentedTests) { + // Disable unit tests + tasks.withType() + .configureEach { enabled = false } + // Disable connected Android tests + tasks.matching { it.name.startsWith("connected") && it.name.endsWith("AndroidTest") } + .configureEach { enabled = false } + } + + // Explicit API configuration for toolkit modules + tasks.withType { + // Only toolkit modules, exclude tests + if ("Test" !in name) { + compilerOptions { + freeCompilerArgs.addAll( + listOf( + "-Xexplicit-api=strict", + "-Xcontext-receivers" + ) + ) } } } + + dependencies { + // Configure the ArcGIS Maps SDK dependency + ArcGISMapsKotlinSDKDependency.configureArcGISMapsDependencies(target) + } } } } diff --git a/build-logic/convention/src/main/java/ArcGISMapsRootConventionPlugin.kt b/build-logic/convention/src/main/java/ArcGISMapsRootConventionPlugin.kt new file mode 100644 index 000000000..f3eb9c75e --- /dev/null +++ b/build-logic/convention/src/main/java/ArcGISMapsRootConventionPlugin.kt @@ -0,0 +1,14 @@ +import com.esri.arcgismaps.kotlin.build_logic.extensions.ToolkitRegistryExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.create + +class ArcGISMapsRootConventionPlugin : Plugin { + override fun apply(target: Project) { + require(target == target.rootProject) { + "The ArcGISMapsRootConventionPlugin must be applied to the root project only." + } + // Create the single registry extension on the root project + target.extensions.create("toolkitRegistry") + } +} diff --git a/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/AndroidCompose.kt b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/AndroidCompose.kt index be21e4159..344d27795 100644 --- a/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/AndroidCompose.kt +++ b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/AndroidCompose.kt @@ -1,6 +1,10 @@ package com.esri.arcgismaps.kotlin.build_logic.convention import com.android.build.api.dsl.CommonExtension +import com.esri.arcgismaps.kotlin.build_logic.extensions.androidTestImplementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.debugImplementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.implementation +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs import org.gradle.api.Project import org.gradle.kotlin.dsl.dependencies @@ -16,7 +20,7 @@ internal fun Project.configureAndroidCompose( } composeOptions { - kotlinCompilerExtensionVersion = libs.findVersion("kotlinVersion").get().toString() + kotlinCompilerExtensionVersion = libs.findVersion("kotlin").get().toString() } dependencies { @@ -27,11 +31,8 @@ internal fun Project.configureAndroidCompose( implementation(libs.findLibrary("androidx-compose-material3").get()) implementation(libs.findLibrary("androidx-lifecycle-viewmodel-compose").get()) implementation(libs.findLibrary("androidx-compose-ui-tooling-preview").get()) - debugImplementation(libs.findLibrary("androidx-compose-ui-tooling").get()) - debugImplementation(libs.findLibrary("androidx-compose-ui-test-manifest").get()) + debugImplementation(libs.findBundle("debug").get()) androidTestImplementation(libs.findLibrary("androidx-compose-ui-test").get()) - androidTestImplementation(libs.findLibrary("androidx-compose-ui-test-junit4").get()) - } } } diff --git a/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/ArcGISMapsKotlinSDKDependency.kt b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/ArcGISMapsKotlinSDKDependency.kt new file mode 100644 index 000000000..12ef0023e --- /dev/null +++ b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/ArcGISMapsKotlinSDKDependency.kt @@ -0,0 +1,77 @@ +package com.esri.arcgismaps.kotlin.build_logic.convention + +import com.esri.arcgismaps.kotlin.build_logic.extensions.api +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies +import java.io.File + +/** + * A helper object to configure the logic for applying the ArcGIS Maps SDK dependency, + * allowing for simple one-line calls in convention plugins or build scripts. + * It also handles dynamic versioning based on Gradle properties. + */ +object ArcGISMapsKotlinSDKDependency { + + /** + * Applies the ArcGIS Maps SDK for Kotlin dependency to the project. + * + * This function checks for Gradle properties (`versionNumber` or `sdkVersionNumber`) + * to determine the SDK version. If a version is provided, it can be combined with a + * `buildNumber` to specify a specific build. If no version property is found, + * it falls back to the version defined in `libs.versions.toml`. + * + * @param target The Gradle Project to which the dependency will be added. + */ + fun configureArcGISMapsDependencies(target: Project) { + target.dependencies { + + val localProperties = java.util.Properties().apply { + val localPropertiesFile = File("local.properties") + if (localPropertiesFile.exists()) { + load(localPropertiesFile.inputStream()) + } + } + + // For finalBuilds ignore the build number and pick up the released version of the SDK dependency + val finalBuild: Boolean = (target.rootProject.providers.gradleProperty("finalBuild").orNull ?: "false") + .toBoolean() + + // First look for the version number provided via command line (for CI builds), if not found, + // take the one defined in gradle.properties. + // CI builds pass -PversionNumber=${BUILDVER} + val sdkVersionNumber: String? = + target.rootProject.providers.gradleProperty("versionNumber").orNull + ?: target.rootProject.providers.gradleProperty("sdkVersionNumber").orNull + ?: localProperties["sdkVersionNumber"] as? String + + // The build number of the ArcGIS Maps SDK for Kotlin dependency. + // First look for the version number provided via command line (for CI builds), if not found, + // take the one defined in local.properties. + // CI builds pass -PbuildNumber=${BUILDNUM} + val sdkBuildNumber: String? = + target.rootProject.providers.gradleProperty("buildNumber").orNull + ?: target.rootProject.providers.gradleProperty("sdkBuildNumber").orNull + ?: localProperties["sdkBuildNumber"] as? String + + // ArcGIS Maps SDK dependency with build override support + if (sdkVersionNumber != null) { + val dependencyVersion = if (finalBuild) { + // For a final build, use the version number directly + sdkVersionNumber + } else { + // If a buildNumber is provided and not blank, append it to the version. + if (!sdkBuildNumber.isNullOrBlank()) { + "$sdkVersionNumber-$sdkBuildNumber" + } else { + sdkVersionNumber + } + } + api("com.esri:arcgis-maps-kotlin:$dependencyVersion") + } else { + // Use libs.versions.toml if no gradle property is provided + api(target.libs.findLibrary("arcgis-maps-kotlin").get()) + } + } + } +} diff --git a/gradle-plugins/src/main/kotlin/com/arcgismaps/ArtifactPublisher.kt b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/ArtifactPublisher.kt similarity index 77% rename from gradle-plugins/src/main/kotlin/com/arcgismaps/ArtifactPublisher.kt rename to build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/ArtifactPublisher.kt index 72ce61f73..a1b0b7323 100644 --- a/gradle-plugins/src/main/kotlin/com/arcgismaps/ArtifactPublisher.kt +++ b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/ArtifactPublisher.kt @@ -16,14 +16,13 @@ * */ -package deploy +package com.esri.arcgismaps.kotlin.build_logic.convention -import org.gradle.api.Plugin +import com.esri.arcgismaps.kotlin.build_logic.extensions.ToolkitRegistryExtension import org.gradle.api.Project import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.plugins.MavenPublishPlugin -import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.provideDelegate @@ -39,8 +38,8 @@ import java.net.URI * * @since 200.2.0 */ -class ArtifactPublisher : Plugin { - override fun apply(project: Project) { +object ArtifactPublisher { + fun configureArtifactPublisher(project: Project) { val artifactoryGroupId: String by project val artifactoryArtifactBaseId: String by project val artifactoryUrl: String by project @@ -55,13 +54,18 @@ class ArtifactPublisher : Plugin { } else { "$versionNumber-$buildNumber" } - val artifactoryArtifactId: String = "$artifactoryArtifactBaseId-${project.name}" - + val artifactoryArtifactId = "$artifactoryArtifactBaseId-${project.name}" + project.pluginManager.apply(MavenPublishPlugin::class.java) + project.afterEvaluate { + val registry = rootProject.extensions.getByType(ToolkitRegistryExtension::class.java) + if (!registry.toolkitProjects.contains(project)) return@afterEvaluate + + project.extensions.configure { repositories { - repositories.maven { + maven { url = URI.create(artifactoryUrl) credentials { username = artifactoryUsername @@ -70,22 +74,19 @@ class ArtifactPublisher : Plugin { } } publications { - publications.create( - project.name, - MavenPublication::class.java - ) { + create(project.name, MavenPublication::class.java) { groupId = artifactoryGroupId artifactId = artifactoryArtifactId version = artifactVersion - + from(project.components["release"]) } } } - tasks.findByName("publish${project.name.replaceFirstChar { it.uppercase() }}PublicationToMavenRepository") + project.tasks.findByName("publish${project.name.replaceFirstChar { it.uppercase() }}PublicationToMavenRepository") ?.dependsOn("assembleRelease") - tasks.findByName("publishToMavenLocal")?.dependsOn("assembleRelease") + project.tasks.findByName("publishToMavenLocal")?.dependsOn("assembleRelease") } } } diff --git a/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/KotlinAndroid.kt b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/KotlinAndroid.kt index 5dcdab882..f80fad946 100644 --- a/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/KotlinAndroid.kt +++ b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/KotlinAndroid.kt @@ -1,6 +1,7 @@ package com.esri.arcgismaps.kotlin.build_logic.convention import com.android.build.api.dsl.CommonExtension +import com.esri.arcgismaps.kotlin.build_logic.extensions.libs import org.gradle.api.JavaVersion import org.gradle.api.Project import org.gradle.kotlin.dsl.provideDelegate @@ -15,11 +16,9 @@ internal fun Project.configureKotlinAndroid( commonExtension: CommonExtension<*, *, *, *, *, *>, ) { commonExtension.apply { - compileSdk = libs.findVersion("targetSdk").get().toString().toInt() + compileSdk = libs.findVersion("compileSdk").get().toString().toInt() defaultConfig { - buildConfigField("String", "ACCESS_TOKEN", project.properties["ACCESS_TOKEN"].toString()) - manifestPlaceholders["GOOGLE_API_KEY"] = project.properties["GOOGLE_API_KEY"].toString() minSdk = libs.findVersion("minSdk").get().toString().toInt() } @@ -43,15 +42,6 @@ private fun Project.configureKotlin() { // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties val warningsAsErrors: String? by project allWarningsAsErrors.set(warningsAsErrors.toBoolean()) - freeCompilerArgs.set( - listOf( - "-opt-in=kotlin.RequiresOptIn", - // Enable experimental coroutines APIs, including Flow - "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=kotlinx.coroutines.FlowPreview", - "-Xcontext-receivers", - ) - ) } } } diff --git a/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/ProjectExtensions.kt b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/extensions/ProjectExtensions.kt similarity index 86% rename from build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/ProjectExtensions.kt rename to build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/extensions/ProjectExtensions.kt index 26af71d39..013572ce4 100644 --- a/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/convention/ProjectExtensions.kt +++ b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/extensions/ProjectExtensions.kt @@ -1,4 +1,4 @@ -package com.esri.arcgismaps.kotlin.build_logic.convention +package com.esri.arcgismaps.kotlin.build_logic.extensions import org.gradle.api.Project import org.gradle.api.artifacts.Dependency @@ -16,6 +16,9 @@ fun DependencyHandler.testRuntimeOnly(dependencyNotation: Any): Dependency? = fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? = add("implementation", dependencyNotation) +fun DependencyHandler.api(dependencyNotation: Any): Dependency? = + add("api", dependencyNotation) + fun DependencyHandler.androidTestImplementation(dependencyNotation: Any): Dependency? = add("androidTestImplementation", dependencyNotation) diff --git a/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/extensions/ToolkitModuleExtension.kt b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/extensions/ToolkitModuleExtension.kt new file mode 100644 index 000000000..0dbe36725 --- /dev/null +++ b/build-logic/convention/src/main/java/com/esri/arcgismaps/kotlin/build_logic/extensions/ToolkitModuleExtension.kt @@ -0,0 +1,30 @@ +package com.esri.arcgismaps.kotlin.build_logic.extensions + +import org.gradle.api.NamedDomainObjectSet +import org.gradle.api.Project +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import javax.inject.Inject + + +abstract class ToolkitModuleExtension @Inject constructor(objects: ObjectFactory) { + + /** Can/should this module be published as an artifact? */ + val releasable: Property = objects.property(Boolean::class.java) + + + internal fun applyDefaults() { + releasable.convention(true) + } + +} + + +abstract class ToolkitRegistryExtension @Inject constructor(objects: ObjectFactory) { +// Live set that will receive projects as they apply the toolkit plugin + val toolkitProjects: NamedDomainObjectSet = + objects.namedDomainObjectSet(Project::class.java) + +} + + diff --git a/build-logic/gradle.properties b/build-logic/gradle.properties index 6977b7191..bf586e553 100644 --- a/build-logic/gradle.properties +++ b/build-logic/gradle.properties @@ -1,3 +1,4 @@ org.gradle.parallel=true org.gradle.caching=true -org.gradle.configureondemand=true \ No newline at end of file +org.gradle.configureondemand=true +org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 87f35d3c2..401b6ad52 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { + alias(libs.plugins.arcgismaps.kotlin.root.convention) alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.binary.compatibility.validator) apply false @@ -32,13 +33,6 @@ plugins { } buildscript { - dependencies { - // there doesn't appear to be a better way to provide this to subprojects. - // this is what lets us put the version number dropdown list in the generated dokka. - // it is a "dokka plugin" which is not a gradle plugin, it needs to be on the classpath - // before any dependent subproject uses its symbols to configure a dokka task. - classpath(libs.dokka.versioning) - } val localProperties = java.util.Properties().apply { val localPropertiesFile = file("local.properties") if (localPropertiesFile.exists()) { @@ -57,16 +51,23 @@ buildscript { ?: localProperties.getProperty("artifactoryPassword") ?: "" + val sdkVersionNumber = project.findProperty("sdkVersionNumber") as String? + ?: localProperties.getProperty("sdkVersionNumber") + val sdkBuildNumber = project.findProperty("sdkBuildNumber") as String? + ?: localProperties.getProperty("sdkBuildNumber") + project.extra.set("artifactoryUrl", artifactoryUrl) project.extra.set("artifactoryUsername", artifactoryUsername) project.extra.set("artifactoryPassword", artifactoryPassword) + project.extra.set("sdkVersionNumber", sdkVersionNumber) + project.extra.set("sdkBuildNumber", sdkBuildNumber) val finalBuild: Boolean = (project.properties["finalBuild"] ?: "false") .run { this == "true" } if (finalBuild) { check(project.hasProperty("versionNumber")) - project.logger.info("release candidate build requested version ${project.properties["versionNumber"]}") + project.logger.warn("release candidate build requested version ${project.properties["versionNumber"]}") } else if (!project.hasProperty("versionNumber") && !project.hasProperty("buildNum")) { // both version number and build number must be set java.util.Properties().run { @@ -84,7 +85,7 @@ buildscript { } check(project.hasProperty("versionNumber")) check(project.hasProperty("buildNumber")) - project.logger.info("version and build number set from buildnum.txt to ${project.properties["versionNumber"]}-${project.properties["buildNumber"]}") + project.logger.warn("version and build number set from buildnum.txt to ${project.properties["versionNumber"]}-${project.properties["buildNumber"]}") } catch (t: Throwable) { // The buildnum file is not there. ignore it and log a warning. project.logger.warn("the buildnum.txt file is missing or not readable") @@ -95,9 +96,6 @@ buildscript { } } -// Path to the centralized folder in root directory where test reports for connected tests end up -val connectedTestReportsPath by extra("${rootDir}/connectedTestReports") - /** * Configures the [gmazzo test aggregation plugin](https://github.com/gmazzo/gradle-android-test-aggregation-plugin) * with all local tests to be aggregated into a single test report. @@ -109,27 +107,5 @@ val connectedTestReportsPath by extra("${rootDir}/connectedTestReports") * Test report to be found under `arcgis-maps-sdk-kotlin-toolkit/build/reports`. */ testAggregation { - getModulesExcept( - "bom", - "kdoc", - "template", - "microapps-lib", - "composable-map" - ).forEach { - this.modules.include(project(":$it")) - } + // TODO: getAllToolkitProjects(project).forEach { this.modules.include(project(":$it")) } } - -/** - * Returns all modules in this project, except the ones specified by [modulesToExclude]. - */ -fun getModulesExcept(vararg modulesToExclude: String): List = - with(File("$rootDir/settings.gradle.kts")) { - readLines() - .filter { it.startsWith("include") } - .map { - it.removePrefix("include(\":").removeSuffix("\")") - } - .filter { !modulesToExclude.contains(it) } // exclude specified modules - } - diff --git a/doc/README.md b/doc/README.md index 21b4627b7..6bc80c768 100644 --- a/doc/README.md +++ b/doc/README.md @@ -4,7 +4,6 @@ - [Design Patterns and Best Practices](./general/design_patterns.md) - [Developer Setup](./general/developer_setup.md) -- [Composable Map](./general/design_patterns.md#composable-map) ## Release diff --git a/gradle-plugins/build.gradle.kts b/gradle-plugins/build.gradle.kts deleted file mode 100644 index c48fdef85..000000000 --- a/gradle-plugins/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -repositories { - google() - mavenCentral() -} - -plugins { - `kotlin-dsl` - `maven-publish` -} - -gradlePlugin { - plugins { - create("artifactDeploy") { - group = "internal" - id = "artifact-deploy" - version = "1.0" - implementationClass = "deploy.ArtifactPublisher" - } - } -} - -dependencies { - implementation(libs.kotlinx.serialization.json) -} diff --git a/gradle-plugins/settings.gradle.kts b/gradle-plugins/settings.gradle.kts deleted file mode 100644 index b474f08dd..000000000 --- a/gradle-plugins/settings.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -rootProject.name = "gradle-plugins" - -dependencyResolutionManagement { - repositories { - google() - mavenCentral() - } - versionCatalogs { - create("libs") { - from(files("../gradle/libs.versions.toml")) - } - } -} diff --git a/gradle.properties b/gradle.properties index 6e4d49328..001209493 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,12 +41,4 @@ kotlin.code.style=official android.nonTransitiveRClass=true artifactoryGroupId=com.esri artifactoryArtifactBaseId=arcgis-maps-kotlin-toolkit - -# These versions define the dependency of the ArcGIS Maps SDK for Kotlin dependency -# when building the toolkit locally, typically from Android Studio. -# You will need to specify the build version in local.properties. -# Example: -# sdkBuildNumber=4678 -# When building the toolkit with CI, these versions are obtained from command line provided properties, -# see sdkVersionNumber in settings.gradle.kts. -sdkVersionNumber=300.0.0 +org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9b043ea7f..9033c9dcd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,12 @@ [versions] # ArcGIS Maps SDK for Kotlin version -arcgisMapsKotlinVersion = "300.0.0-4689" +arcgisMapsKotlinVersion = "200.8.0" ### Android versions androidGradlePlugin = "8.9.2" androidXBrowser = "1.8.0" androidxCamera = "1.4.2" -androidxComposeCompiler = "1.5.12" androidxCore = "1.16.0" androidxEspresso = "3.6.1" androidxHiltNavigationCompose = "1.2.0" @@ -79,7 +78,6 @@ play-services-location = { group = "com.google.android.gms", name = "play-servic ### Kotlin libs kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin"} kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" } -kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinxSerializationJson" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } ### Compose libs @@ -112,16 +110,15 @@ truth = { group = "com.google.truth", name = "truth", version.ref = "truth" } ### Third party libs coil = { group = "io.coil-kt", name = "coil" } -coil-base = { group = "io.coil-kt", name = "coil-base" } coil-bom = { group = "io.coil-kt", name = "coil-bom", version.ref = "coilBOM" } coil-compose = { group = "io.coil-kt", name = "coil-compose" } commonmark = { group = "org.commonmark", name = "commonmark", version.ref="commonMark" } commonmark-strikethrough = { group = "org.commonmark", name = "commonmark-ext-gfm-strikethrough", version.ref="commonMark" } -dokka-versioning = { group = "org.jetbrains.dokka", name = "versioning-plugin", version.ref = "dokka" } +dokka-all-modules = { module = "org.jetbrains.dokka:all-modules-page-plugin", version.ref = "dokka" } +dokka-versioning = { module = "org.jetbrains.dokka:versioning-plugin", version.ref = "dokka" } +dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } hilt-android-core = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } -hilt-ext-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hiltExt" } -hilt-ext-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltExt" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } room-ext = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } @@ -139,6 +136,7 @@ binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibil dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } gradle-secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "gradleSecrets"} kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } @@ -146,12 +144,16 @@ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = " gmazzo-test-aggregation = { id = "io.github.gmazzo.test.aggregation.results", version.ref = "gmazzo" } # Plugins defined by the project build-logic -arcgismaps-android-application = { id = "arcgismaps.android.application", version = "unspecified" } -arcgismaps-android-application-compose = { id = "arcgismaps.android.application.compose", version = "unspecified" } -arcgismaps-android-library-compose = { id = "arcgismaps.android.library.compose", version = "unspecified" } -arcgismaps-android-library = { id = "arcgismaps.android.library", version = "unspecified" } -arcgismaps-kotlin-microapp = { id = "arcgismaps.kotlin.microapp", version = "unspecified" } -arcgismaps-kotlin-toolkit = { id = "arcgismaps.kotlin.toolkit", version = "unspecified" } +arcgismaps-android-application = { id = "arcgismaps.android.application" } +arcgismaps-android-application-compose = { id = "arcgismaps.android.application.compose" } +arcgismaps-android-library-compose = { id = "arcgismaps.android.library.compose" } +arcgismaps-android-library = { id = "arcgismaps.android.library" } +arcgismaps-kotlin-root-convention = { id = "arcgismaps.kotlin.root.convention" } +arcgismaps-kotlin-kdoc-convention = { id = "arcgismaps.kotlin.kdoc.convention" } +arcgismaps-kotlin-bom-convention = { id = "arcgismaps.kotlin.bom.convention" } +arcgismaps-kotlin-sdk = { id = "arcgismaps.kotlin.sdk"} +arcgismaps-kotlin-microapp = { id = "arcgismaps.kotlin.microapp" } +arcgismaps-kotlin-toolkit = { id = "arcgismaps.kotlin.toolkit" } [bundles] camerax = [ diff --git a/kdoc/build.gradle.kts b/kdoc/build.gradle.kts index 13f34fc19..b6ff85306 100644 --- a/kdoc/build.gradle.kts +++ b/kdoc/build.gradle.kts @@ -17,80 +17,14 @@ */ plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") - alias(libs.plugins.dokka) apply true -} - -val versionNumber: String by project - -// make this project get evaluated after all the other projects -// so that we can be sure the logic to determine released components -// below works -rootProject.subprojects.filter { - it.name != project.name && it.name != "bom" -}.forEach { - evaluationDependsOn(":${it.name}") -} - -// only run kdoc on components which are released. Only modules that apply -// the `artifact-deploy` plugin are released. -// TODO: flag released modules directly. -val releasedModules = project.rootProject.subprojects.filter { - it.plugins.findPlugin("artifact-deploy") != null -} - -// determine the released toolkit components -val releasedSourceSetPaths = releasedModules.map { subproject -> - // add all the intended library projects as sourceSets below - File(rootDir, "toolkit/${subproject.name}/src/main/java").canonicalPath -} - -tasks { - //./gradlew :kdoc:dokkaHtml - // doc output will be under `documentation/build/dokka/html`. - dokkaHtml { - pluginConfiguration { - version = versionNumber - } - - moduleName.set("arcgis-maps-kotlin-toolkit") - dokkaSourceSets { - named("main") { - sourceRoots.from(releasedSourceSetPaths) - } - - configureEach { - platform.set(org.jetbrains.dokka.Platform.jvm) - perPackageOption { - matchingRegex.set(".*internal.*") - suppress.set(true) - } - - perPackageOption { - reportUndocumented.set(true) - } - } - } - } + alias(libs.plugins.arcgismaps.kotlin.kdoc.convention) } android { namespace = "com.arcgismaps.toolkit.doc" compileSdk = libs.versions.compileSdk.get().toInt() - defaultConfig { minSdk = libs.versions.minSdk.get().toInt() consumerProguardFiles("consumer-rules.pro") } } - -dependencies { - // Puts the version in the KDoc - dokkaPlugin(libs.dokka.versioning) - // put exposed dependencies in dokka's classpath - implementation(arcgis.mapsSdk) - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.bundles.composeCore) -} - diff --git a/microapps/ArFlyoverApp/app/build.gradle.kts b/microapps/ArFlyoverApp/app/build.gradle.kts index 226721397..fca9ddff7 100644 --- a/microapps/ArFlyoverApp/app/build.gradle.kts +++ b/microapps/ArFlyoverApp/app/build.gradle.kts @@ -17,79 +17,20 @@ */ plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") - id("org.jetbrains.kotlin.plugin.compose") - id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") -} - -secrets { - // this file doesn't contain secrets, it just provides defaults which can be committed into git. - defaultPropertiesFileName = "secrets.defaults.properties" + alias(libs.plugins.arcgismaps.kotlin.microapp) } android { namespace = "com.arcgismaps.toolkit.arflyoverapp" - compileSdk = libs.versions.compileSdk.get().toInt() - + defaultConfig { - applicationId ="com.arcgismaps.toolkit.arflyoverapp" - minSdk = libs.versions.minSdk.get().toInt() - targetSdk = libs.versions.compileSdk.get().toInt() - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner ="androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary = true - } - } - - buildTypes { - release { - isMinifyEnabled = false - //proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"),("proguard-rules.pro" - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } - - buildFeatures { - compose = true - buildConfig = true - } - - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } - - // Avoids an empty test report showing up in the CI integration test report. - // Remove this if tests will be added. - tasks.withType { - enabled = false + applicationId = "com.arcgismaps.toolkit.arflyoverapp" } } dependencies { - implementation(project(":geoview-compose")) - implementation(project(":microapps-lib")) + // Module-specific dependencies go here implementation(project(":ar")) + implementation(project(":geoview-compose")) implementation(libs.arcore) - implementation(arcgis.mapsSdk) - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.bundles.composeCore) - implementation(libs.bundles.core) - implementation(libs.androidx.activity.compose) - implementation(libs.androidx.lifecycle.viewmodel.compose) - testImplementation(libs.bundles.unitTest) - androidTestImplementation(platform(libs.androidx.compose.bom)) - androidTestImplementation(libs.bundles.composeTest) - debugImplementation(libs.bundles.debug) } diff --git a/microapps/ArFlyoverApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/microapps/ArFlyoverApp/app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from microapps/ArFlyoverApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to microapps/ArFlyoverApp/app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/microapps/ArTabletopApp/app/build.gradle.kts b/microapps/ArTabletopApp/app/build.gradle.kts index 48015402b..a2a18edc0 100644 --- a/microapps/ArTabletopApp/app/build.gradle.kts +++ b/microapps/ArTabletopApp/app/build.gradle.kts @@ -17,78 +17,20 @@ */ plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") - id("org.jetbrains.kotlin.plugin.compose") - id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") -} - -secrets { - // this file doesn't contain secrets, it just provides defaults which can be committed into git. - defaultPropertiesFileName = "secrets.defaults.properties" + alias(libs.plugins.arcgismaps.kotlin.microapp) } android { namespace = "com.arcgismaps.toolkit.artabletopapp" - compileSdk = libs.versions.compileSdk.get().toInt() - + defaultConfig { - applicationId ="com.arcgismaps.toolkit.artabletopapp" - minSdk = libs.versions.minSdk.get().toInt() - targetSdk = libs.versions.compileSdk.get().toInt() - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner ="androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary = true - } - } - - buildTypes { - release { - isMinifyEnabled = false - //proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"),("proguard-rules.pro" - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } - buildFeatures { - compose = true - buildConfig = true - } - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } - - // Avoids an empty test report showing up in the CI integration test report. - // Remove this if tests will be added. - tasks.withType { - enabled = false + applicationId = "com.arcgismaps.toolkit.artabletopapp" } } dependencies { - implementation(project(":geoview-compose")) - implementation(project(":microapps-lib")) + // Module-specific dependencies go here implementation(project(":ar")) + implementation(project(":geoview-compose")) implementation(libs.arcore) - implementation(arcgis.mapsSdk) - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.bundles.composeCore) - implementation(libs.bundles.core) - implementation(libs.androidx.activity.compose) - implementation(libs.androidx.lifecycle.viewmodel.compose) - implementation(libs.androidx.lifecycle.runtime.compose) - testImplementation(libs.bundles.unitTest) - androidTestImplementation(platform(libs.androidx.compose.bom)) - androidTestImplementation(libs.bundles.composeTest) - debugImplementation(libs.bundles.debug) } diff --git a/microapps/ArTabletopApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/microapps/ArTabletopApp/app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from microapps/ArTabletopApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to microapps/ArTabletopApp/app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/microapps/ArWorldScaleApp/app/build.gradle.kts b/microapps/ArWorldScaleApp/app/build.gradle.kts index d9875206d..e9ab6c9d8 100644 --- a/microapps/ArWorldScaleApp/app/build.gradle.kts +++ b/microapps/ArWorldScaleApp/app/build.gradle.kts @@ -17,79 +17,19 @@ */ plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") - id("org.jetbrains.kotlin.plugin.compose") - id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") -} - -secrets { - // this file doesn't contain secrets, it just provides defaults which can be committed into git. - defaultPropertiesFileName = "secrets.defaults.properties" + alias(libs.plugins.arcgismaps.kotlin.microapp) } android { namespace = "com.arcgismaps.toolkit.arworldscaleapp" - compileSdk = libs.versions.compileSdk.get().toInt() - + defaultConfig { - applicationId ="com.arcgismaps.toolkit.arworldscaleapp" - minSdk = libs.versions.minSdk.get().toInt() - targetSdk = libs.versions.compileSdk.get().toInt() - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner ="androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary = true - } - } - - buildTypes { - release { - isMinifyEnabled = false - //proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"),("proguard-rules.pro" - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } - - buildFeatures { - compose = true - buildConfig = true - } - - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } - - // Avoids an empty test report showing up in the CI integration test report. - // Remove this if tests will be added. - tasks.withType { - enabled = false + applicationId = "com.arcgismaps.toolkit.arworldscaleapp" } } dependencies { - implementation(project(":geoview-compose")) - implementation(project(":microapps-lib")) + // Module-specific dependencies go here implementation(project(":ar")) implementation(libs.arcore) - implementation(arcgis.mapsSdk) - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.bundles.composeCore) - implementation(libs.bundles.core) - implementation(libs.androidx.activity.compose) - implementation(libs.androidx.lifecycle.viewmodel.compose) - testImplementation(libs.bundles.unitTest) - androidTestImplementation(platform(libs.androidx.compose.bom)) - androidTestImplementation(libs.bundles.composeTest) - debugImplementation(libs.bundles.debug) } diff --git a/microapps/ArWorldScaleApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/microapps/ArWorldScaleApp/app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from microapps/ArWorldScaleApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to microapps/ArWorldScaleApp/app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/microapps/AuthenticationApp/app/build.gradle.kts b/microapps/AuthenticationApp/app/build.gradle.kts index 3f82e53a9..1545f60a8 100644 --- a/microapps/AuthenticationApp/app/build.gradle.kts +++ b/microapps/AuthenticationApp/app/build.gradle.kts @@ -17,77 +17,18 @@ */ plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") - id("org.jetbrains.kotlin.plugin.compose") - id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") -} - -secrets { - defaultPropertiesFileName = "secrets.defaults.properties" + alias(libs.plugins.arcgismaps.kotlin.microapp) } android { namespace = "com.arcgismaps.toolkit.authenticationapp" - compileSdk = libs.versions.compileSdk.get().toInt() - + defaultConfig { - applicationId ="com.arcgismaps.toolkit.authenticationapp" - minSdk = libs.versions.minSdk.get().toInt() - targetSdk = libs.versions.compileSdk.get().toInt() - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner ="androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary = true - } - } - - buildTypes { - release { - isMinifyEnabled = false - //proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"),("proguard-rules.pro" - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - // context receivers are used by the MapInterface for gesture events - freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers" - } - buildFeatures { - compose = true - buildConfig = true - } - - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } - - // Avoids an empty test report showing up in the CI integration test report. - // Remove this if tests will be added. - tasks.withType { - enabled = false + applicationId = "com.arcgismaps.toolkit.authenticationapp" } } dependencies { + // Module-specific dependencies go here implementation(project(":authentication")) - implementation(project(":microapps-lib")) - implementation(arcgis.mapsSdk) - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.bundles.composeCore) - implementation(libs.bundles.core) - implementation(libs.androidx.activity.compose) - implementation(libs.androidx.lifecycle.viewmodel.compose) - testImplementation(libs.bundles.unitTest) - androidTestImplementation(platform(libs.androidx.compose.bom)) - androidTestImplementation(libs.bundles.composeTest) - debugImplementation(libs.bundles.debug) } diff --git a/microapps/AuthenticationApp/app/src/main/java/com/arcgismaps/toolkit/authenticationapp/MainActivity.kt b/microapps/AuthenticationApp/app/src/main/java/com/arcgismaps/toolkit/authenticationapp/MainActivity.kt index 04b4c2e75..e437ce23f 100644 --- a/microapps/AuthenticationApp/app/src/main/java/com/arcgismaps/toolkit/authenticationapp/MainActivity.kt +++ b/microapps/AuthenticationApp/app/src/main/java/com/arcgismaps/toolkit/authenticationapp/MainActivity.kt @@ -45,7 +45,6 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -105,7 +104,6 @@ private fun AuthenticationApp(authenticationAppViewModel: AuthenticationAppViewM * @param onLoadPortal called when the [url] should be loaded * @since 200.2.0 */ -@OptIn(ExperimentalComposeUiApi::class) @Composable private fun PortalDetails( url: String, diff --git a/microapps/AuthenticationApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/microapps/AuthenticationApp/app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from microapps/AuthenticationApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to microapps/AuthenticationApp/app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/microapps/BasemapGalleryApp/app/build.gradle.kts b/microapps/BasemapGalleryApp/app/build.gradle.kts index eecd7bc4c..0ad9719fe 100644 --- a/microapps/BasemapGalleryApp/app/build.gradle.kts +++ b/microapps/BasemapGalleryApp/app/build.gradle.kts @@ -17,78 +17,19 @@ */ plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") - id("org.jetbrains.kotlin.plugin.compose") - id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") -} - -secrets { - // this file doesn't contain secrets, it just provides defaults which can be committed into git. - defaultPropertiesFileName = "secrets.defaults.properties" + alias(libs.plugins.arcgismaps.kotlin.microapp) } android { namespace = "com.arcgismaps.toolkit.basemapgalleryapp" - compileSdk = libs.versions.compileSdk.get().toInt() - + defaultConfig { - applicationId ="com.arcgismaps.toolkit.basemapgalleryapp" - minSdk = libs.versions.minSdk.get().toInt() - targetSdk = libs.versions.compileSdk.get().toInt() - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner ="androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary = true - } - } - - buildTypes { - release { - isMinifyEnabled = false - //proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"),("proguard-rules.pro" - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } - - buildFeatures { - compose = true - buildConfig = true - } - - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } - - // Avoids an empty test report showing up in the CI integration test report. - // Remove this if tests will be added. - tasks.withType { - enabled = false + applicationId = "com.arcgismaps.toolkit.basemapgalleryapp" } } dependencies { + // Module-specific dependencies go here implementation(project(":basemapgallery")) implementation(project(":geoview-compose")) - implementation(project(":microapps-lib")) - implementation(arcgis.mapsSdk) - implementation(platform(libs.androidx.compose.bom)) - implementation(libs.bundles.composeCore) - implementation(libs.bundles.core) - implementation(libs.androidx.activity.compose) - implementation(libs.androidx.lifecycle.viewmodel.compose) - testImplementation(libs.bundles.unitTest) - androidTestImplementation(platform(libs.androidx.compose.bom)) - androidTestImplementation(libs.bundles.composeTest) - debugImplementation(libs.bundles.debug) } diff --git a/microapps/BasemapGalleryApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/microapps/BasemapGalleryApp/app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from microapps/BasemapGalleryApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to microapps/BasemapGalleryApp/app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/microapps/MapViewCalloutApp/.gitignore b/microapps/CalloutApp/.gitignore similarity index 100% rename from microapps/MapViewCalloutApp/.gitignore rename to microapps/CalloutApp/.gitignore diff --git a/microapps/CalloutApp/README.md b/microapps/CalloutApp/README.md new file mode 100644 index 000000000..4cab21120 --- /dev/null +++ b/microapps/CalloutApp/README.md @@ -0,0 +1,13 @@ +# Callout Micro-app + +This micro-app demonstrates the use of `Callout` with a composable `MapView` or `SceneView`. `Callout` is a composable function which renders an empty composable Box placed at a given point or GeoElement on a Map. The content of the composable Box is customizable, while the container of the Box is a stylable rectangular shape with a leader line positioned at the point of the tap or GeoElement passed into the `Callout` composable functions. The default `LeaderPosition` is set to `Automatic` which dynamically positions the leader to keep the callout as much as possible within the bounds of the `GeoView`. + +## Usage + +The application starts with a choice of two screens +* A `GeoView` that displays a `Callout` at the location where the user taps on the screen. +* A `MapView` with `Callout`s placed on GeoElements tapped by the user. +* For more information on the composable `Callout` component and how it works, see the relevant section in the composable GeoView [Readme](../../toolkit/geoview-compose#display-a-callout). + +![Callout-geoelement](https://github.com/user-attachments/assets/d6fd278a-c773-45f3-9ecd-a76852b71192) + diff --git a/microapps/MapViewCalloutApp/app/.gitignore b/microapps/CalloutApp/app/.gitignore similarity index 100% rename from microapps/MapViewCalloutApp/app/.gitignore rename to microapps/CalloutApp/app/.gitignore diff --git a/microapps/CalloutApp/app/build.gradle.kts b/microapps/CalloutApp/app/build.gradle.kts new file mode 100644 index 000000000..a7585beb2 --- /dev/null +++ b/microapps/CalloutApp/app/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * + * Copyright 2024 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +plugins { + alias(libs.plugins.arcgismaps.kotlin.microapp) +} + +android { + namespace = "com.arcgismaps.toolkit.calloutapp" + + defaultConfig { + applicationId = "com.arcgismaps.toolkit.calloutapp" + } +} + +dependencies { + // Module-specific dependencies go here + implementation(project(":geoview-compose")) + implementation(libs.androidx.compose.navigation) +} diff --git a/microapps/MapViewCalloutApp/app/proguard-rules.pro b/microapps/CalloutApp/app/proguard-rules.pro similarity index 100% rename from microapps/MapViewCalloutApp/app/proguard-rules.pro rename to microapps/CalloutApp/app/proguard-rules.pro diff --git a/microapps/MapViewCalloutApp/app/src/main/AndroidManifest.xml b/microapps/CalloutApp/app/src/main/AndroidManifest.xml similarity index 93% rename from microapps/MapViewCalloutApp/app/src/main/AndroidManifest.xml rename to microapps/CalloutApp/app/src/main/AndroidManifest.xml index 940864f19..333472ce0 100644 --- a/microapps/MapViewCalloutApp/app/src/main/AndroidManifest.xml +++ b/microapps/CalloutApp/app/src/main/AndroidManifest.xml @@ -30,12 +30,12 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.MapViewCalloutApp" + android:theme="@style/Theme.CalloutApp" tools:targetApi="31"> + android:theme="@style/Theme.CalloutApp"> diff --git a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/MainActivity.kt b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/MainActivity.kt similarity index 87% rename from microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/MainActivity.kt rename to microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/MainActivity.kt index 95d6196e3..af6c6c37f 100644 --- a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/MainActivity.kt +++ b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/MainActivity.kt @@ -16,7 +16,7 @@ * */ -package com.arcgismaps.toolkit.mapviewcalloutapp +package com.arcgismaps.toolkit.calloutapp import android.os.Bundle import androidx.activity.ComponentActivity @@ -24,7 +24,7 @@ import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import com.arcgismaps.ApiKey import com.arcgismaps.ArcGISEnvironment -import com.arcgismaps.toolkit.mapviewcalloutapp.screens.MainScreen +import com.arcgismaps.toolkit.calloutapp.screens.MainScreen import com.esri.microappslib.theme.MicroAppTheme class MainActivity : ComponentActivity() { @@ -33,13 +33,13 @@ class MainActivity : ComponentActivity() { ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.API_KEY) setContent { MicroAppTheme { - MapViewCalloutApp() + CalloutApp() } } } } @Composable -fun MapViewCalloutApp() { +fun CalloutApp() { MainScreen() } diff --git a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/FeatureScreen.kt b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/FeatureScreen.kt similarity index 94% rename from microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/FeatureScreen.kt rename to microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/FeatureScreen.kt index f36b2c28e..aa1d11f65 100644 --- a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/FeatureScreen.kt +++ b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/FeatureScreen.kt @@ -16,7 +16,7 @@ * */ -package com.arcgismaps.toolkit.mapviewcalloutapp.screens +package com.arcgismaps.toolkit.calloutapp.screens import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -59,6 +59,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.arcgismaps.toolkit.geoviewcompose.LeaderPosition import com.arcgismaps.toolkit.geoviewcompose.MapView /** @@ -66,7 +67,13 @@ import com.arcgismaps.toolkit.geoviewcompose.MapView */ @OptIn(ExperimentalMaterial3Api::class) @Composable -fun FeatureScreen(viewModel: MapViewModel) { +fun FeatureScreen( + viewModel: GeoViewModel, + screenTitle: String, + canNavigateBack: Boolean, + navigateUp: () -> Unit +) { + val tapLocationGraphicsOverlay = viewModel.tapLocationGraphicsOverlay.collectAsState().value val selectedGeoElement = viewModel.selectedGeoElement.collectAsState().value val selectedLayerName = viewModel.selectedLayerName.collectAsState().value var calloutVisibility by rememberSaveable { mutableStateOf(true) } @@ -75,6 +82,7 @@ fun FeatureScreen(viewModel: MapViewModel) { var showBottomSheet by remember { mutableStateOf(false) } Scaffold( + topBar = { CalloutAppBar(screenTitle, canNavigateBack, navigateUp) }, floatingActionButton = { Box( Modifier @@ -97,7 +105,7 @@ fun FeatureScreen(viewModel: MapViewModel) { arcGISMap = viewModel.arcGISMapWithFeatureLayer, mapViewProxy = viewModel.mapViewProxy, insets = PaddingValues(horizontal = 12.dp), - graphicsOverlays = remember { listOf(viewModel.tapLocationGraphicsOverlay) }, + graphicsOverlays = remember { listOf(tapLocationGraphicsOverlay) }, onSingleTapConfirmed = { singleTapConfirmedEvent -> viewModel.apply { // clears the tapped location and graphic @@ -116,6 +124,7 @@ fun FeatureScreen(viewModel: MapViewModel) { .height(200.dp) .widthIn(max = 300.dp), geoElement = selectedGeoElement, + leaderPosition = LeaderPosition.Automatic, tapLocation = viewModel.tapLocation.value, ) { CalloutContent( diff --git a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/MapViewModel.kt b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/GeoViewModel.kt similarity index 67% rename from microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/MapViewModel.kt rename to microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/GeoViewModel.kt index d1b8d8e58..8fa6ec209 100644 --- a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/MapViewModel.kt +++ b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/GeoViewModel.kt @@ -16,7 +16,7 @@ * */ -package com.arcgismaps.toolkit.mapviewcalloutapp.screens +package com.arcgismaps.toolkit.calloutapp.screens import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.dp @@ -25,6 +25,7 @@ import androidx.lifecycle.viewModelScope import com.arcgismaps.Color import com.arcgismaps.geometry.Point import com.arcgismaps.mapping.ArcGISMap +import com.arcgismaps.mapping.ArcGISScene import com.arcgismaps.mapping.BasemapStyle import com.arcgismaps.mapping.GeoElement import com.arcgismaps.mapping.Viewpoint @@ -34,15 +35,17 @@ import com.arcgismaps.mapping.view.Graphic import com.arcgismaps.mapping.view.GraphicsOverlay import com.arcgismaps.mapping.view.SingleTapConfirmedEvent import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy +import com.arcgismaps.toolkit.geoviewcompose.SceneViewProxy import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -class MapViewModel : ViewModel() { +class GeoViewModel : ViewModel() { val mapViewProxy = MapViewProxy() + val sceneViewProxy = SceneViewProxy() val arcGISMap = ArcGISMap(BasemapStyle.ArcGISTopographic).apply { initialViewpoint = Viewpoint( @@ -52,6 +55,14 @@ class MapViewModel : ViewModel() { ) } + val arcGISScene = ArcGISScene(BasemapStyle.ArcGISTopographic).apply { + initialViewpoint = Viewpoint( + latitude = 39.8, + longitude = -98.6, + scale = 10e7 + ) + } + val arcGISMapWithFeatureLayer = ArcGISMap( uri = "https://www.arcgis.com/home/item.html?id=16f1b8ba37b44dc3884afc8d5f454dd2" ).apply { @@ -61,8 +72,12 @@ class MapViewModel : ViewModel() { ) } - private val _mapPoint = MutableStateFlow(null) - val mapPoint: StateFlow = _mapPoint.asStateFlow() + + private val _geoViewType = MutableStateFlow(GeoViewType.SceneViewType) + val geoViewType: StateFlow = _geoViewType.asStateFlow() + + private val _point = MutableStateFlow(null) + val point: StateFlow = _point.asStateFlow() private val _selectedGeoElement = MutableStateFlow(null) val selectedGeoElement: StateFlow = _selectedGeoElement.asStateFlow() @@ -76,34 +91,47 @@ class MapViewModel : ViewModel() { private val _offset = MutableStateFlow(Offset.Zero) val offset: StateFlow = _offset - val tapLocationGraphicsOverlay: GraphicsOverlay = GraphicsOverlay() + private val _tapLocationGraphicsOverlay = MutableStateFlow(GraphicsOverlay()) + val tapLocationGraphicsOverlay: StateFlow = _tapLocationGraphicsOverlay.asStateFlow() private var currentIdentifyJob: Job? = null - fun clearMapPoint() { - _mapPoint.value = null - tapLocationGraphicsOverlay.graphics.clear() + fun clearPoint() { + _point.value = null + _tapLocationGraphicsOverlay.value.graphics.clear() + _tapLocationGraphicsOverlay.value = GraphicsOverlay() } fun setOffset(offset: Offset) { _offset.value = offset } - fun setMapPoint(singleTapConfirmedEvent: SingleTapConfirmedEvent) { - _mapPoint.value = singleTapConfirmedEvent.mapPoint + fun setPoint(singleTapConfirmedEvent: SingleTapConfirmedEvent) { + if (_geoViewType.value == GeoViewType.MapViewType) + _point.value = singleTapConfirmedEvent.mapPoint + else + _point.value = sceneViewProxy.screenToBaseSurface(singleTapConfirmedEvent.screenCoordinate) - tapLocationGraphicsOverlay.graphics.clear() - tapLocationGraphicsOverlay.graphics.add( + _tapLocationGraphicsOverlay.value.graphics.clear() + _tapLocationGraphicsOverlay.value.graphics.add( Graphic( - geometry = _mapPoint.value, + geometry = _point.value, symbol = SimpleMarkerSymbol(SimpleMarkerSymbolStyle.Cross, Color.red, 12.0f) ) ) } + fun toggleGeoView() { + clearPoint() + _geoViewType.value = if (_geoViewType.value == GeoViewType.MapViewType) + GeoViewType.SceneViewType + else + GeoViewType.MapViewType + } + fun clearTapLocationAndGraphic() { _tapLocation.value = null - tapLocationGraphicsOverlay.graphics.clear() + _tapLocationGraphicsOverlay.value.graphics.clear() } fun clearSelectedGeoElement() { @@ -113,8 +141,8 @@ class MapViewModel : ViewModel() { fun setTapLocation(tapLocation: Point?, nullTapLocation: Boolean) { _tapLocation.value = if (nullTapLocation) null else tapLocation - tapLocationGraphicsOverlay.graphics.clear() - tapLocationGraphicsOverlay.graphics.add( + _tapLocationGraphicsOverlay.value.graphics.clear() + _tapLocationGraphicsOverlay.value.graphics.add( Graphic( geometry = tapLocation, symbol = SimpleMarkerSymbol(SimpleMarkerSymbolStyle.Cross, Color.red, 12.0f) @@ -146,3 +174,7 @@ class MapViewModel : ViewModel() { } } } +sealed class GeoViewType { + object MapViewType : GeoViewType() + object SceneViewType : GeoViewType() +} diff --git a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/MainScreen.kt b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/MainScreen.kt similarity index 83% rename from microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/MainScreen.kt rename to microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/MainScreen.kt index cc38a84d7..3e6730eac 100644 --- a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/MainScreen.kt +++ b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/MainScreen.kt @@ -16,7 +16,7 @@ * */ -package com.arcgismaps.toolkit.mapviewcalloutapp.screens +package com.arcgismaps.toolkit.calloutapp.screens import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -73,16 +73,23 @@ fun MainScreen() { CalloutAppNavHost( navController = navController, - calloutScreenNames = calloutAppScreens, - currentScreen = currentScreen + calloutScreenNames = calloutAppScreens ) { composable(route = calloutAppScreens[0]) { - val tapLocationViewModel: MapViewModel = viewModel() - TapLocationScreen(tapLocationViewModel) + val tapLocationViewModel: GeoViewModel = viewModel() + TapLocationScreen( + viewModel = tapLocationViewModel, + screenTitle = currentScreen, + canNavigateBack = navController.previousBackStackEntry != null, + navigateUp = { navController.navigateUp() }) } composable(route = calloutAppScreens[1]) { - val featureViewModel: MapViewModel = viewModel() - FeatureScreen(featureViewModel) + val featureViewModel: GeoViewModel = viewModel() + FeatureScreen( + viewModel = featureViewModel, + screenTitle = currentScreen, + canNavigateBack = navController.previousBackStackEntry != null, + navigateUp = { navController.navigateUp() }) } } } @@ -92,33 +99,22 @@ fun MainScreen() { fun CalloutAppNavHost( navController: NavHostController, calloutScreenNames: MutableList, - currentScreen: String, builder: NavGraphBuilder.() -> Unit ) { - Scaffold( - topBar = { - CalloutAppBar( - currentScreen = currentScreen, - canNavigateBack = navController.previousBackStackEntry != null, - navigateUp = { navController.navigateUp() } + NavHost( + modifier = Modifier, + navController = navController, + startDestination = "Callout App", + ) { + composable(route = "Callout App") { + NavScreenSwitcher( + calloutScreenNames = calloutScreenNames, + onScreenSelected = { selectedScreen -> + navController.navigate(selectedScreen) + } ) } - ) { innerPadding -> - NavHost( - modifier = Modifier.padding(innerPadding), - navController = navController, - startDestination = "Callout App", - ) { - composable(route = "Callout App") { - NavScreenSwitcher( - calloutScreenNames = calloutScreenNames, - onScreenSelected = { selectedScreen -> - navController.navigate(selectedScreen) - } - ) - } - builder.invoke(this) - } + builder.invoke(this) } } diff --git a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/TapLocationScreen.kt b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/TapLocationScreen.kt similarity index 58% rename from microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/TapLocationScreen.kt rename to microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/TapLocationScreen.kt index 2f19b3283..3ba8194b8 100644 --- a/microapps/MapViewCalloutApp/app/src/main/java/com/arcgismaps/toolkit/mapviewcalloutapp/screens/TapLocationScreen.kt +++ b/microapps/CalloutApp/app/src/main/java/com/arcgismaps/toolkit/calloutapp/screens/TapLocationScreen.kt @@ -16,7 +16,7 @@ * */ -package com.arcgismaps.toolkit.mapviewcalloutapp.screens +package com.arcgismaps.toolkit.calloutapp.screens import android.widget.TextView import androidx.compose.animation.AnimatedVisibility @@ -47,6 +47,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults +import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -68,7 +71,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.text.HtmlCompat import com.arcgismaps.geometry.Point +import com.arcgismaps.toolkit.geoviewcompose.GeoViewScope +import com.arcgismaps.toolkit.geoviewcompose.LeaderPosition import com.arcgismaps.toolkit.geoviewcompose.MapView +import com.arcgismaps.toolkit.geoviewcompose.SceneView import kotlin.math.roundToInt /** @@ -76,21 +82,27 @@ import kotlin.math.roundToInt */ @OptIn(ExperimentalMaterial3Api::class) @Composable -fun TapLocationScreen(viewModel: MapViewModel) { - - val mapPoint = viewModel.mapPoint.collectAsState().value +fun TapLocationScreen( + viewModel: GeoViewModel, + screenTitle: String, + canNavigateBack: Boolean, + navigateUp: () -> Unit +) { + val point = viewModel.point.collectAsState().value val offset = viewModel.offset.collectAsState().value - + val tapLocationGraphicsOverlay = viewModel.tapLocationGraphicsOverlay.collectAsState().value + val geoViewType = viewModel.geoViewType.collectAsState().value var rotateOffsetWithGeoView by rememberSaveable { mutableStateOf(false) } var calloutVisibility by rememberSaveable { mutableStateOf(true) } var showBottomSheet by remember { mutableStateOf(false) } val modalBottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) // animate to a visible transition state val calloutVisibleState = remember { MutableTransitionState(false) }.apply { - targetState = mapPoint != null && calloutVisibility + targetState = point != null && calloutVisibility } Scaffold( + topBar = { CalloutAppBar(screenTitle, canNavigateBack, navigateUp) }, floatingActionButton = { Box( Modifier @@ -106,44 +118,43 @@ fun TapLocationScreen(viewModel: MapViewModel) { } } ) { contentPadding -> - MapView( - modifier = Modifier - .fillMaxSize() - .padding(contentPadding), - arcGISMap = viewModel.arcGISMap, - graphicsOverlays = remember { listOf(viewModel.tapLocationGraphicsOverlay) }, - onSingleTapConfirmed = viewModel::setMapPoint, - content = { - val lastMapPoint = remember { Ref() } - lastMapPoint.value = mapPoint ?: lastMapPoint.value - - AnimatedVisibility( - calloutVisibleState, - enter = fadeIn(), - exit = fadeOut() - ) { - lastMapPoint.value?.let { - Callout( - modifier = Modifier.wrapContentSize(), - location = it, - rotateOffsetWithGeoView = rotateOffsetWithGeoView, - offset = offset - ) { - Column(Modifier.padding(4.dp)) { - HtmlText( - html = "Tapped location:
" + - "x = ${it.x.roundToInt()}
" + - "y = ${it.y.roundToInt()}
" + - "wkid = ${it.spatialReference?.wkid}", - htmlFlag = HtmlCompat.FROM_HTML_MODE_COMPACT, - textColor = MaterialTheme.colorScheme.onBackground, - ) - } - } - } + if (geoViewType == GeoViewType.MapViewType) { + MapView( + modifier = Modifier + .fillMaxSize() + .padding(contentPadding), + arcGISMap = viewModel.arcGISMap, + mapViewProxy = viewModel.mapViewProxy, + graphicsOverlays = listOf(tapLocationGraphicsOverlay), + onSingleTapConfirmed = viewModel::setPoint, + content = { + GeoViewScopeContent( + mapPoint = point, + calloutVisibleState = calloutVisibleState, + rotateOffsetWithGeoView = rotateOffsetWithGeoView, + offset = offset + ) } - } - ) + ) + } else { + SceneView( + modifier = Modifier + .fillMaxSize() + .padding(contentPadding), + arcGISScene = viewModel.arcGISScene, + sceneViewProxy = viewModel.sceneViewProxy, + graphicsOverlays = listOf(tapLocationGraphicsOverlay), + onSingleTapConfirmed = viewModel::setPoint, + content = { + GeoViewScopeContent( + mapPoint = point, + calloutVisibleState = calloutVisibleState, + rotateOffsetWithGeoView = rotateOffsetWithGeoView, + offset = offset + ) + } + ) + } if (showBottomSheet) { ModalBottomSheet( @@ -153,13 +164,15 @@ fun TapLocationScreen(viewModel: MapViewModel) { ) { Box(Modifier.navigationBarsPadding()) { CalloutOptions( + isGeoViewMapView = geoViewType == GeoViewType.MapViewType, calloutVisibility = calloutVisibility, isCalloutRotationEnabled = rotateOffsetWithGeoView, offset = offset, - mapPoint = mapPoint, + mapPoint = point, onOffsetChange = { viewModel.setOffset(it) }, + onGeoViewToggled = { viewModel.toggleGeoView() }, onVisibilityToggled = { calloutVisibility = !calloutVisibility }, - onClearMapPointRequest = { viewModel.clearMapPoint() }, + onClearMapPointRequest = { viewModel.clearPoint() }, onCalloutOffsetRotationToggled = { rotateOffsetWithGeoView = !rotateOffsetWithGeoView } @@ -170,6 +183,51 @@ fun TapLocationScreen(viewModel: MapViewModel) { } } + +/** + * Displays the callout at the tapped location on the map or scene. + * + * This function is responsible for managing callout at the coordinates of the [mapPoint]. + * It uses animated visibility to show or hide the callout based on the state provided. + */ +@Composable +fun GeoViewScope.GeoViewScopeContent( + mapPoint: Point?, + calloutVisibleState: MutableTransitionState, + rotateOffsetWithGeoView: Boolean, + offset: Offset +) { + val lastMapPoint = remember { Ref() } + lastMapPoint.value = mapPoint ?: lastMapPoint.value + + AnimatedVisibility( + calloutVisibleState, + enter = fadeIn(), + exit = fadeOut() + ) { + lastMapPoint.value?.let { + Callout( + modifier = Modifier.wrapContentSize(), + location = it, + leaderPosition = LeaderPosition.Automatic, + rotateOffsetWithGeoView = rotateOffsetWithGeoView, + offset = offset + ) { + Column(Modifier.padding(4.dp)) { + HtmlText( + html = "Tapped location:
" + + "x = ${it.x.roundToInt()}
" + + "y = ${it.y.roundToInt()}
" + + "wkid = ${it.spatialReference?.wkid}", + htmlFlag = HtmlCompat.FROM_HTML_MODE_COMPACT, + textColor = MaterialTheme.colorScheme.onBackground, + ) + } + } + } + } +} + /** * AndroidView wrapper for the view-based [TextView] which is able to display a styled spannable [html]. * Currently, Compose does not provide a tool to buildAnnotatedString for HTML styled spannable text. @@ -191,31 +249,68 @@ fun HtmlText(modifier: Modifier = Modifier, html: String, htmlFlag: Int, textCol */ @Composable fun CalloutOptions( + isGeoViewMapView: Boolean, calloutVisibility: Boolean, isCalloutRotationEnabled: Boolean, offset: Offset, mapPoint: Point?, + onGeoViewToggled: () -> Unit, onVisibilityToggled: () -> Unit, onOffsetChange: (Offset) -> Unit, onCalloutOffsetRotationToggled: () -> Unit, onClearMapPointRequest: () -> Unit, ) { Column(Modifier.padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally) { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Row( + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = "Select GeoView") + SingleChoiceSegmentedButtonRow { + SegmentedButton( + onClick = onGeoViewToggled, + selected = isGeoViewMapView, + label = { Text("MapView") }, + shape = SegmentedButtonDefaults.itemShape( + index = 0, + count = 2 + ) + ) + SegmentedButton( + onClick = onGeoViewToggled, + selected = !isGeoViewMapView, + label = { Text("SceneView") }, + shape = SegmentedButtonDefaults.itemShape( + index = 1, + count = 2 + ) + ) + } + } + Row( + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Text(text = "Show Callout") Checkbox( checked = calloutVisibility, onCheckedChange = { onVisibilityToggled() } ) } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Row( + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Text(text = "Rotate offset") Checkbox( checked = isCalloutRotationEnabled, onCheckedChange = { onCalloutOffsetRotationToggled() } ) } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround) { + Row( + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround, + verticalAlignment = Alignment.CenterVertically + ) { OutlinedTextField( modifier = Modifier.weight(1f), value = offset.x.toString(), diff --git a/microapps/MapViewCalloutApp/app/src/main/res/drawable/ic_launcher_background.xml b/microapps/CalloutApp/app/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/drawable/ic_launcher_background.xml rename to microapps/CalloutApp/app/src/main/res/drawable/ic_launcher_background.xml diff --git a/microapps/MapViewCalloutApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/microapps/CalloutApp/app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to microapps/CalloutApp/app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/microapps/CalloutApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/microapps/CalloutApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/microapps/CalloutApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/microapps/CalloutApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/microapps/CalloutApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/microapps/CalloutApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/microapps/CalloutApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/microapps/CalloutApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/microapps/CalloutApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/microapps/CalloutApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp rename to microapps/CalloutApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/values/colors.xml b/microapps/CalloutApp/app/src/main/res/values/colors.xml similarity index 100% rename from microapps/MapViewCalloutApp/app/src/main/res/values/colors.xml rename to microapps/CalloutApp/app/src/main/res/values/colors.xml diff --git a/microapps/MapViewCalloutApp/app/src/main/res/values/strings.xml b/microapps/CalloutApp/app/src/main/res/values/strings.xml similarity index 92% rename from microapps/MapViewCalloutApp/app/src/main/res/values/strings.xml rename to microapps/CalloutApp/app/src/main/res/values/strings.xml index 70a05bfd1..1c1d8c5a2 100644 --- a/microapps/MapViewCalloutApp/app/src/main/res/values/strings.xml +++ b/microapps/CalloutApp/app/src/main/res/values/strings.xml @@ -17,5 +17,5 @@ --> - MapViewCalloutApp + CalloutApp diff --git a/microapps/MapViewCalloutApp/app/src/main/res/values/themes.xml b/microapps/CalloutApp/app/src/main/res/values/themes.xml similarity index 87% rename from microapps/MapViewCalloutApp/app/src/main/res/values/themes.xml rename to microapps/CalloutApp/app/src/main/res/values/themes.xml index 456bece1e..7eebad38e 100644 --- a/microapps/MapViewCalloutApp/app/src/main/res/values/themes.xml +++ b/microapps/CalloutApp/app/src/main/res/values/themes.xml @@ -19,5 +19,5 @@ -