Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b6a6c8f
feat: create repo-root catalog, basic Compose map sample, and establi…
dkhawk Apr 3, 2026
43e4f03
feat: initialize ComposeDemos module and reorganize catalog
dkhawk Apr 23, 2026
00652cd
feat: add Polylines sample data, tests, and screenshot
dkhawk Apr 23, 2026
c71f07a
docs: use HTML img tags for catalog thumbnails
dkhawk Apr 23, 2026
70a5638
feat: implement Popovers sample and visual test
dkhawk Apr 23, 2026
c8b6286
feat: apply immersive mode and update catalog thumbnails
dkhawk Apr 23, 2026
9a5ab2e
feat: implement Polygons, Models, and Markers samples
dkhawk Apr 23, 2026
ab7f9fe
fix: correct spelling of Visualization to American English
dkhawk Apr 23, 2026
c9eed08
feat: implement Camera Restrictions sample and visual test
dkhawk Apr 23, 2026
f8e55c8
style: run spotlessApply to format code
dkhawk Apr 23, 2026
bd0cdb9
style: clean up fully qualified names and format code
dkhawk Apr 23, 2026
72765dc
feat: implement missing compose samples and fix library bugs
dkhawk Apr 23, 2026
5cd4041
chore: add default API key placeholders to local.defaults.properties
dkhawk Apr 23, 2026
c570c08
feat: implement Place Details sample with custom theme and fragment i…
dkhawk Apr 23, 2026
7263871
feat: update initial camera for Place Details sample
dkhawk Apr 23, 2026
44e0e60
feat: make Flatirons the first item in Place Details sample
dkhawk Apr 23, 2026
5fb5ebe
chore: remove old screenshot
dkhawk Apr 23, 2026
214ce81
feat: add correct place details screenshot for Flatirons
dkhawk Apr 23, 2026
6b04da0
chore: remove old screenshot again
dkhawk Apr 23, 2026
a1238f4
feat: verify Place Details with strict prompt and longer delay
dkhawk Apr 23, 2026
36bae9c
feat: add swipe gesture to Place Details visual test
dkhawk Apr 23, 2026
a3cad79
feat: implement Place Details sample with custom theme and clean MVVM…
dkhawk Apr 23, 2026
6b4f9cc
docs: add description column to Compose catalog README
dkhawk Apr 24, 2026
39f6076
fix: resolve build failures in CI and spotless
kikoso Apr 24, 2026
e1b6cc2
fix: add manifest placeholder for MAPS3D_API_KEY in all sample apps
kikoso Apr 24, 2026
fbd6d36
fix: make secrets.properties optional in CI to avoid FileNotFoundExce…
kikoso Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ jobs:
distribution: 'adopt'
java-version: '21'
- name: Build advanced
run: cd Maps3DSamples/ApiDemos && ./gradlew buildDebugPreBundle
run: cd Maps3DSamples/advanced && ./gradlew buildDebugPreBundle
20 changes: 20 additions & 0 deletions Maps3DSamples/ApiDemos/java-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ☕ Java Samples Catalog

This directory contains the Java samples using traditional Android Views for the Android Maps 3D SDK.

## 📊 Sample Status

| Feature | Status | Source Code |
| :--- | :--- | :--- |
| **Hello Map** | ✅ Done | [HelloMapActivity.java](src/main/java/com/example/maps3djava/hellomap/HelloMapActivity.java) |
| **Polylines** | ✅ Done | [PolylinesActivity.java](src/main/java/com/example/maps3djava/polylines/PolylinesActivity.java) |
| **Map Interactions** | ✅ Done | [MapInteractionsActivity.java](src/main/java/com/example/maps3djava/mapinteractions/MapInteractionsActivity.java) |
| **Popovers** | ✅ Done | [PopoversActivity.java](src/main/java/com/example/maps3djava/popovers/PopoversActivity.java) |
| **Camera Controls** | ✅ Done | [CameraControlsActivity.java](src/main/java/com/example/maps3djava/cameracontrols/CameraControlsActivity.java) |
| **Polygons** | ✅ Done | [PolygonsActivity.java](src/main/java/com/example/maps3djava/polygons/PolygonsActivity.java) |
| **Models** | ✅ Done | [ModelsActivity.java](src/main/java/com/example/maps3djava/models/ModelsActivity.java) |
| **Markers** | ✅ Done | [MarkersActivity.java](src/main/java/com/example/maps3djava/markers/MarkersActivity.java) |

---
> [!NOTE]
> These samples are view-based and serve as a reference for Java developers.
12 changes: 6 additions & 6 deletions Maps3DSamples/ApiDemos/java-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ android {
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
}

buildTypes {
Expand Down Expand Up @@ -147,12 +148,11 @@ dependencies {
}

secrets {
// Optionally specify a different file name containing your secrets.
// The plugin defaults to "local.properties"
propertiesFileName = "secrets.properties"

// A properties file containing default secret values. This file can be
// checked in version control.
// Only set propertiesFileName if the file exists to avoid FileNotFoundException in CI
val secretsFile = rootProject.file("secrets.properties")
if (secretsFile.exists()) {
propertiesFileName = "secrets.properties"
}
defaultPropertiesFileName = "local.defaults.properties"
}

Expand Down
20 changes: 20 additions & 0 deletions Maps3DSamples/ApiDemos/kotlin-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 🧱 Kotlin + Views Samples Catalog

This directory contains the Kotlin samples using traditional Android Views for the Android Maps 3D SDK.

## 📊 Sample Status

| Feature | Status | Source Code |
| :--- | :--- | :--- |
| **Hello Map** | ✅ Done | [HelloMapActivity.kt](src/main/java/com/example/maps3dkotlin/hellomap/HelloMapActivity.kt) |
| **Polylines** | ✅ Done | [PolylinesActivity.kt](src/main/java/com/example/maps3dkotlin/polylines/PolylinesActivity.kt) |
| **Map Interactions** | ✅ Done | [MapInteractionsActivity.kt](src/main/java/com/example/maps3dkotlin/mapinteractions/MapInteractionsActivity.kt) |
| **Popovers** | ✅ Done | [PopoversActivity.kt](src/main/java/com/example/maps3dkotlin/popovers/PopoversActivity.kt) |
| **Camera Controls** | ✅ Done | [CameraControlsActivity.kt](src/main/java/com/example/maps3dkotlin/cameracontrols/CameraControlsActivity.kt) |
| **Polygons** | ✅ Done | [PolygonsActivity.kt](src/main/java/com/example/maps3dkotlin/polygons/PolygonsActivity.kt) |
| **Models** | ✅ Done | [ModelsActivity.kt](src/main/java/com/example/maps3dkotlin/models/ModelsActivity.kt) |
| **Markers** | ✅ Done | [MarkersActivity.kt](src/main/java/com/example/maps3dkotlin/markers/MarkersActivity.kt) |

---
> [!NOTE]
> These samples are view-based and serve as a reference for non-Compose applications.
12 changes: 6 additions & 6 deletions Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ android {
versionName = "1.7.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
}

buildTypes {
Expand Down Expand Up @@ -155,12 +156,11 @@ dependencies {
}

secrets {
// Optionally specify a different file name containing your secrets.
// The plugin defaults to "local.properties"
propertiesFileName = "secrets.properties"

// A properties file containing default secret values. This file can be
// checked in version control.
// Only set propertiesFileName if the file exists to avoid FileNotFoundException in CI
val secretsFile = rootProject.file("secrets.properties")
if (secretsFile.exists()) {
propertiesFileName = "secrets.properties"
}
defaultPropertiesFileName = "local.defaults.properties"
}

Expand Down
34 changes: 34 additions & 0 deletions Maps3DSamples/ComposeDemos/app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 🚀 Jetpack Compose Samples Catalog

This directory contains the Compose samples for the Android Maps 3D SDK. We use a state-driven approach and lean on the `maps3d-compose` library.

## 📊 Sample Status

| Feature | Status | Source Code | Screenshot | Description |
| :--- | :--- | :--- | :--- | :--- |
| **Basic Map** | ✅ Done | [HelloMapActivity.kt](src/main/java/com/example/composedemos/hellomap/HelloMapActivity.kt) | <img src="src/main/assets/screenshots/hello_map_screenshot.png" alt="Screenshot" width="121"/> | Displays a basic 3D map with standard satellite imagery and initial camera placement. |
| **Polylines** | ✅ Done | [PolylinesActivity.kt](src/main/java/com/example/composedemos/polylines/PolylinesActivity.kt) | <img src="src/main/assets/screenshots/polylines_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates drawing 3D polylines on the map, including custom colors and widths. |
| **Map Interactions** | 🚧 Skeleton | [MapInteractionsActivity.kt](src/main/java/com/example/composedemos/mapinteractions/MapInteractionsActivity.kt) | | Will demonstrate handling click and drag events on map objects. |
| **Popovers** | ✅ Done | [PopoversActivity.kt](src/main/java/com/example/composedemos/popovers/PopoversActivity.kt) | <img src="src/main/assets/screenshots/popovers_screenshot.png" alt="Screenshot" width="121"/> | Shows how to display interactive popover overlays at specific coordinates on the map. |
| **Camera Controls** | ✅ Done | [CameraControlsActivity.kt](src/main/java/com/example/composedemos/cameracontrols/CameraControlsActivity.kt) | <img src="src/main/assets/screenshots/camera_controls_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates manual control of the camera center, heading, tilt, and range using UI controls. |
| **Polygons** | ✅ Done | [PolygonsActivity.kt](src/main/java/com/example/composedemos/polygons/PolygonsActivity.kt) | <img src="src/main/assets/screenshots/polygons_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates drawing 3D polygons with fill colors and outlines on the map. |
| **Models** | ✅ Done | [ModelsActivity.kt](src/main/java/com/example/composedemos/models/ModelsActivity.kt) | <img src="src/main/assets/screenshots/models_screenshot.png" alt="Screenshot" width="121"/> | Shows how to load and place custom 3D models (gLTF) on the map with position, scale, and orientation. |
| **Markers** | ✅ Done | [MarkersActivity.kt](src/main/java/com/example/composedemos/markers/MarkersActivity.kt) | <img src="src/main/assets/screenshots/markers_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates adding 2D markers with custom icons and anchor points to the 3D map. |
| **Camera Restrictions** | ✅ Done | [CameraRestrictionsActivity.kt](src/main/java/com/example/composedemos/camerarestrictions/CameraRestrictionsActivity.kt) | <img src="src/main/assets/screenshots/camera_restrictions_screenshot.png" alt="Screenshot" width="121"/> | Shows how to restrict the camera range and center bounds to a specific area. |
| **Flight Simulator** | 🚧 Skeleton | [FlightSimulatorActivity.kt](src/main/java/com/example/composedemos/flightsimulator/FlightSimulatorActivity.kt) | | Will demonstrate a first-person camera view simulating flight. |
| **Routes API** | ✅ Done | [RoutesActivity.kt](src/main/java/com/example/composedemos/routes/RoutesActivity.kt) | <img src="src/main/assets/screenshots/routes_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates loading a route from file, rendering the polyline, and animating a 3D car model along the route using a flow-based engine. |
| **Path Following** | 🚧 Skeleton | [PathFollowingActivity.kt](src/main/java/com/example/composedemos/pathfollowing/PathFollowingActivity.kt) | | Will demonstrate camera following a path. |
| **Path Styling** | 🚧 Skeleton | [PathStylingActivity.kt](src/main/java/com/example/composedemos/pathstyling/PathStylingActivity.kt) | | Will demonstrate custom styling for paths. |
| **Animating Models** | 🚧 Skeleton | [AnimatingModelsActivity.kt](src/main/java/com/example/composedemos/animatingmodels/AnimatingModelsActivity.kt) | | Will demonstrate animating 3D models. |
| **Place Search** | 🚧 Skeleton | [PlaceSearchActivity.kt](src/main/java/com/example/composedemos/placesearch/PlaceSearchActivity.kt) | | Will demonstrate programmatic place search. |
| **Place Autocomplete** | 🚧 Skeleton | [PlaceAutocompleteActivity.kt](src/main/java/com/example/composedemos/placeautocomplete/PlaceAutocompleteActivity.kt) | | Will demonstrate place autocomplete widget. |
| **Place Details** | ✅ Done | [PlaceDetailsActivity.kt](src/main/java/com/example/composedemos/placedetails/PlaceDetailsActivity.kt) | <img src="src/main/assets/screenshots/place_details_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates loading place details using the modern Places UI Kit fragment (Fragment Interop) with a custom 'Boulder Nature Hippie' theme. |
| **Advanced Camera Animation** | 🚧 Skeleton | [AdvancedCameraAnimationActivity.kt](src/main/java/com/example/composedemos/advancedcameraanimation/AdvancedCameraAnimationActivity.kt) | | Will demonstrate complex camera animations. |
| **Data Visualization** | 🚧 Skeleton | [DataVisualizationActivity.kt](src/main/java/com/example/composedemos/datavisualization/DataVisualizationActivity.kt) | | Will demonstrate visualizing data on 3D map. |
| **Cloud Map Styling** | 🚧 Skeleton | [CloudStylingActivity.kt](src/main/java/com/example/composedemos/cloudstyling/CloudStylingActivity.kt) | | Will demonstrate styling map via Cloud console. |
| **Roadmap Mode** | 🚧 Skeleton | [RoadmapModeActivity.kt](src/main/java/com/example/composedemos/roadmapmode/RoadmapModeActivity.kt) | | Will demonstrate standard roadmap mode. |
| **Field Of View** | 🚧 Skeleton | [FieldOfViewActivity.kt](src/main/java/com/example/composedemos/fieldofview/FieldOfViewActivity.kt) | | Will demonstrate changing field of view. |

---
> [!NOTE]
> Status `🚧 Skeleton` means the activity exists and can be launched from the main list, but contains a TODO placeholder UI. We are actively implementing these following a TDD approach.
135 changes: 135 additions & 0 deletions Maps3DSamples/ComposeDemos/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright 2026 Google LLC
*
* 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.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.secrets.gradle.plugin)
alias(libs.plugins.spotless)
}

configure<com.diffplug.gradle.spotless.SpotlessExtension> {
kotlin {
target("**/*.kt")
ktlint().editorConfigOverride(mapOf("indent_size" to "4", "ktlint_function_naming_ignore_when_annotated_with" to "Composable"))
trimTrailingWhitespace()
endWithNewline()
}
}

android {
namespace = "com.example.composedemos"
compileSdk = libs.versions.compileSdk.get().toInt()

defaultConfig {
applicationId = "com.example.composedemos"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
vectorDrawables {
useSupportLibrary = true
}
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
buildFeatures {
compose = true
buildConfig = true
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}

dependencies {
implementation(project(":maps3d-compose"))

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)

implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)

// Maps 3D SDK
implementation(libs.play.services.maps3d)
implementation(libs.places)
implementation(libs.androidx.fragment.ktx)

// Maps Utils
implementation(libs.maps.utils.ktx)

// Material Icons Extended
implementation(libs.androidx.material.icons.extended)

// Lifecycle ViewModel Compose
implementation("androidx.lifecycle:lifecycle-viewmodel-compose")

testImplementation(libs.junit)
testImplementation(libs.robolectric)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
androidTestImplementation(libs.androidx.uiautomator)
androidTestImplementation(libs.kotlinx.serialization.json)
androidTestImplementation(project(":visual-testing"))
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}

secrets {
// Only set propertiesFileName if the file exists to avoid FileNotFoundException in CI
val secretsFile = rootProject.file("secrets.properties")
if (secretsFile.exists()) {
propertiesFileName = "secrets.properties"
}
defaultPropertiesFileName = "local.defaults.properties"
}

tasks.register<Exec>("installAndLaunch") {
description = "Installs and launches the demo app."
group = "install"
dependsOn("installDebug")
commandLine("adb", "shell", "am", "start", "-n", "com.example.composedemos/.MainActivity")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2026 Google LLC
*
* 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.
*/
package com.example.composedemos

import android.app.Instrumentation
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.google.maps.android.visualtesting.GeminiVisualTestHelper
import org.junit.Assert.assertTrue
import java.io.File

abstract class BaseVisualTest {

protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected val uiDevice = UiDevice.getInstance(instrumentation)
protected val context: Context = instrumentation.targetContext
protected val helper = GeminiVisualTestHelper()

protected val geminiApiKey: String by lazy {
val key = BuildConfig.GEMINI_API_KEY
assertTrue(
"GEMINI_API_KEY is not set in secrets.properties. Please add GEMINI_API_KEY=YOUR_API_KEY to your secrets.properties file.",
key != "YOUR_GEMINI_API_KEY",
)
key
}

protected fun captureScreenshot(filename: String = "screenshot_${System.currentTimeMillis()}.png"): Bitmap {
val screenshotFile = File(context.filesDir, filename)
val screenshotTaken = uiDevice.takeScreenshot(screenshotFile)
assertTrue("Failed to take screenshot: $filename", screenshotTaken)

val bitmap = BitmapFactory.decodeFile(screenshotFile.absolutePath)
assertTrue("Failed to decode screenshot file: $filename", bitmap != null)

android.util.Log.i("BaseVisualTest", "Screenshot saved to device: ${screenshotFile.absolutePath}")

return bitmap
}

/**
* Waits for the map to render.
* Since MapView content (tiles, markers) is rendered on a GL surface and not exposed as
* accessibility nodes, we cannot rely on UiAutomator looking for text/markers.
* We use a stable delay to ensure rendering is complete.
*/
protected fun waitForMapRendering(timeoutSeconds: Long = 30) {
val found = uiDevice.wait(Until.hasObject(By.desc("MapSteady")), timeoutSeconds * 1000)
assertTrue("Map did not become steady within $timeoutSeconds seconds", found)
}
}
Loading
Loading