diff --git a/build.gradle.kts b/build.gradle.kts index be9617cc3..14a446d49 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,13 @@ tasks.register("clean") { delete(layout.buildDirectory) } +tasks.register("installAndLaunch") { + description = "Installs and launches the demo app." + group = "install" + dependsOn(":demo:installDebug") + commandLine("adb", "shell", "am", "start", "-n", "com.google.maps.android.utils.demo/.MainActivity") +} + allprojects { group = "com.google.maps.android" // {x-release-please-start-version} diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index da64b5b92..8148ff11c 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -20,6 +20,7 @@ plugins { id("com.android.application") id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") id("kotlin-android") + alias(libs.plugins.compose.compiler) } android { @@ -48,6 +49,7 @@ android { buildFeatures { viewBinding = true + compose = true } kotlin { @@ -76,6 +78,15 @@ dependencies { testImplementation(libs.junit) testImplementation(libs.truth) + + implementation(platform(libs.compose.bom)) + implementation(libs.activity.compose) + implementation(libs.ui) + implementation(libs.ui.graphics) + implementation(libs.ui.tooling.preview) + implementation(libs.material3) + implementation(libs.material.icons.core) + debugImplementation(libs.ui.tooling) // [END_EXCLUDE] } // [END maps_android_utils_install_snippet] diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index a2724f7c3..5b9ce9fa9 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -134,6 +134,9 @@ + diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/BigClusteringDemoActivity.java b/demo/src/main/java/com/google/maps/android/utils/demo/BigClusteringDemoActivity.java index bf1ee93a6..2346247c4 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/BigClusteringDemoActivity.java +++ b/demo/src/main/java/com/google/maps/android/utils/demo/BigClusteringDemoActivity.java @@ -43,7 +43,7 @@ protected void startDemo(boolean isRestore) { try { readItems(); } catch (JSONException e) { - Toast.makeText(this, "Problem reading list of markers.", Toast.LENGTH_LONG).show(); + Toast.makeText(this, getString(R.string.error_reading_markers), Toast.LENGTH_LONG).show(); } } diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt index d1f27fbe5..fa1549a82 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt +++ b/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt @@ -64,9 +64,9 @@ class ClusterAlgorithmsDemoActivity : BaseDemoActivity() { private fun setupSpinner() { val spinner: Spinner = findViewById(R.id.algorithm_spinner) val adapter = ArrayAdapter.createFromResource( - this, R.array.clustering_algorithms, android.R.layout.simple_spinner_item + this, R.array.clustering_algorithms, R.layout.text_view_spinner_item ) - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + adapter.setDropDownViewResource(R.layout.text_view_spinner_dropdown_item) spinner.adapter = adapter spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/ClusteringDiffDemoActivity.java b/demo/src/main/java/com/google/maps/android/utils/demo/ClusteringDiffDemoActivity.java index 294f1484a..3e745c8f8 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/ClusteringDiffDemoActivity.java +++ b/demo/src/main/java/com/google/maps/android/utils/demo/ClusteringDiffDemoActivity.java @@ -103,7 +103,8 @@ private LatLng getMidpoint() { public boolean onClusterClick(Cluster cluster) { // Show a toast with some info when the cluster is clicked. String firstName = cluster.getItems().iterator().next().name; - Toast.makeText(this, cluster.getSize() + " (including " + firstName + ")", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, getString(R.string.cluster_info_fmt, cluster.getSize(), firstName), Toast.LENGTH_SHORT) + .show(); // Zoom in the cluster. Need to create LatLngBounds and including all the cluster items // inside of bounds, then animate to center of the bounds. diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/CustomAdvancedMarkerClusteringDemoActivity.java b/demo/src/main/java/com/google/maps/android/utils/demo/CustomAdvancedMarkerClusteringDemoActivity.java index e4960e397..c6f253be1 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/CustomAdvancedMarkerClusteringDemoActivity.java +++ b/demo/src/main/java/com/google/maps/android/utils/demo/CustomAdvancedMarkerClusteringDemoActivity.java @@ -58,10 +58,10 @@ public class CustomAdvancedMarkerClusteringDemoActivity extends BaseDemoActivity public void onMapsSdkInitialized(@NonNull MapsInitializer.Renderer renderer) { switch (renderer) { case LATEST: - Log.d("MapsDemo", "The latest version of the renderer is used."); + Log.d("MapsDemo", getString(R.string.renderer_latest)); break; case LEGACY: - Log.d("MapsDemo", "The legacy version of the renderer is used."); + Log.d("MapsDemo", getString(R.string.renderer_legacy)); break; default: break; @@ -100,7 +100,7 @@ private PinConfig.Builder getPinConfig(Person person) { @SuppressLint("SetTextI18n") private View addTextAsMarker(int size) { TextView textView = new TextView(getApplicationContext()); - textView.setText("I am a cluster of size " + size); + textView.setText(getString(R.string.cluster_text_fmt, size)); textView.setBackgroundColor(Color.BLACK); textView.setTextColor(Color.YELLOW); return textView; @@ -130,7 +130,8 @@ protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { public boolean onClusterClick(Cluster cluster) { // Show a toast with some info when the cluster is clicked. String firstName = cluster.getItems().iterator().next().name; - Toast.makeText(this, cluster.getSize() + " (including " + firstName + ")", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, getString(R.string.cluster_info_fmt, cluster.getSize(), firstName), Toast.LENGTH_SHORT) + .show(); // Zoom in the cluster. Need to create LatLngBounds and including all the cluster items // inside of bounds, then animate to center of the bounds. diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/DistanceDemoActivity.java b/demo/src/main/java/com/google/maps/android/utils/demo/DistanceDemoActivity.java index 8c3790828..767539630 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/DistanceDemoActivity.java +++ b/demo/src/main/java/com/google/maps/android/utils/demo/DistanceDemoActivity.java @@ -57,14 +57,14 @@ protected void startDemo(boolean isRestore) { mMarkerB = getMap().addMarker(new MarkerOptions().position(new LatLng(-33.8291, 151.248)).draggable(true)); mPolyline = getMap().addPolyline(new PolylineOptions().geodesic(true)); - Toast.makeText(this, "Drag the markers!", Toast.LENGTH_LONG).show(); + Toast.makeText(this, getString(R.string.drag_the_markers), Toast.LENGTH_LONG).show(); showDistance(); } @SuppressLint("SetTextI18n") private void showDistance() { double distance = SphericalUtil.computeDistanceBetween(mMarkerA.getPosition(), mMarkerB.getPosition()); - mTextView.setText("The markers are " + formatNumber(distance) + " apart."); + mTextView.setText(getString(R.string.distance_format, formatNumber(distance))); } private void updatePolyline() { @@ -72,13 +72,13 @@ private void updatePolyline() { } private String formatNumber(double distance) { - String unit = "m"; + String unit = getString(R.string.unit_m); if (distance < 1) { distance *= 1000; - unit = "mm"; + unit = getString(R.string.unit_mm); } else if (distance > 1000) { distance /= 1000; - unit = "km"; + unit = getString(R.string.unit_km); } return String.format("%4.3f%s", distance, unit); diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java b/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java deleted file mode 100644 index b7a6d0c7d..000000000 --- a/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2025 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.google.maps.android.utils.demo; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; - -import androidx.appcompat.app.AppCompatActivity; - -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowCompat; -import androidx.core.view.WindowInsetsCompat; - -public class MainActivity extends AppCompatActivity implements View.OnClickListener { - private ViewGroup mListView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.main); - - // This tells the system that the app will handle drawing behind the system bars. - WindowCompat.setDecorFitsSystemWindows(getWindow(), false); - - // This is the root view of my layout. - // Make sure to replace R.id.root_layout with the actual ID of your root view. - final View rootView = findViewById(android.R.id.content); - - // Add a listener to handle window insets. - ViewCompat.setOnApplyWindowInsetsListener(rootView, (view, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - - // Apply the insets as padding to the view. - // This will push the content down from behind the status bar and up from - // behind the navigation bar. - view.setPadding( - insets.left, - insets.top, - insets.right, - insets.bottom - ); - - // Return CONSUMED to signal that we've handled the insets. - return WindowInsetsCompat.CONSUMED; - }); - - mListView = findViewById(R.id.list); - - addDemo("Advanced Markers Clustering Example", CustomAdvancedMarkerClusteringDemoActivity.class); - addDemo("Cluster Algorithms", ClusterAlgorithmsDemoActivity.class); - addDemo("Clustering", ClusteringDemoActivity.class); - addDemo("Clustering: Custom Look", CustomMarkerClusteringDemoActivity.class); - addDemo("Clustering: Diff", ClusteringDiffDemoActivity.class); - addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class); - addDemo("Clustering: 20K only visible markers", VisibleClusteringDemoActivity.class); - addDemo("Clustering: ViewModel", ClusteringViewModelDemoActivity.class); - addDemo("Clustering: Force on Zoom", ZoomClusteringDemoActivity.class); - addDemo("PolyUtil.decode", PolyDecodeDemoActivity.class); - addDemo("PolyUtil.simplify", PolySimplifyDemoActivity.class); - addDemo("Polyline Progress", PolylineProgressDemoActivity.class); - addDemo("IconGenerator", IconGeneratorDemoActivity.class); - addDemo("SphericalUtil.computeDistanceBetween", DistanceDemoActivity.class); - addDemo("Generating tiles", TileProviderAndProjectionDemo.class); - addDemo("Heatmaps", HeatmapsDemoActivity.class); - addDemo("Heatmaps with Places API", HeatmapsPlacesDemoActivity.class); - addDemo("GeoJSON Layer", GeoJsonDemoActivity.class); - addDemo("KML Layer Overlay", KmlDemoActivity.class); - addDemo("Multi Layer", MultiLayerDemoActivity.class); - addDemo("AnimationUtil sample", AnimationUtilDemoActivity.class); - addDemo("Street View Demo", StreetViewDemoActivity.class); - addDemo("Street View Demo (Java)", StreetViewDemoJavaActivity.class); - } - - private void addDemo(String demoName, Class activityClass) { - Button b = new Button(this); - ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - b.setLayoutParams(layoutParams); - b.setText(demoName); - b.setTag(activityClass); - b.setOnClickListener(this); - mListView.addView(b); - } - - @Override - public void onClick(View view) { - Class activityClass = (Class) view.getTag(); - startActivity(new Intent(this, activityClass)); - } -} diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.kt new file mode 100644 index 000000000..9c48c894e --- /dev/null +++ b/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.kt @@ -0,0 +1,246 @@ +/* + * 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.google.maps.android.utils.demo + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import com.google.maps.android.utils.demo.model.Demo +import com.google.maps.android.utils.demo.model.DemoGroup + +/** + * The main entry point of the Demo App. + * + * This Activity displays a categorized list of demos using Jetpack Compose. + * It demonstrates a simple "Accordion" UI pattern where only one group can be expanded at a time, + * keeping the list clean and focused. + */ +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + MaterialTheme { + // Scaffold provides a framework for basic material design layout, + // automatically handling system bars and insets. + Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + DemoList( + groups = getDemoGroups(), + contentPadding = innerPadding, + onDemoClick = { activityClass -> + startActivity(Intent(this, activityClass)) + } + ) + } + } + } + } + + /** + * Returns the static list of demo groups. + * + * In a real production app, this might come from a ViewModel or a resource file, + * but for a self-contained demo, hardcoding the hierarchy here is simple and clear. + */ + private fun getDemoGroups(): List { + return listOf( + DemoGroup( + R.string.category_clustering, listOf( + Demo(R.string.demo_title_clustering_advanced, CustomAdvancedMarkerClusteringDemoActivity::class.java), + Demo(R.string.demo_title_clustering_algorithms, ClusterAlgorithmsDemoActivity::class.java), + Demo(R.string.demo_title_clustering_default, ClusteringDemoActivity::class.java), + Demo(R.string.demo_title_clustering_custom_look, CustomMarkerClusteringDemoActivity::class.java), + Demo(R.string.demo_title_clustering_diff, ClusteringDiffDemoActivity::class.java), + Demo(R.string.demo_title_clustering_2k, BigClusteringDemoActivity::class.java), + Demo(R.string.demo_title_clustering_20k, VisibleClusteringDemoActivity::class.java), + Demo(R.string.demo_title_clustering_viewmodel, ClusteringViewModelDemoActivity::class.java), + Demo(R.string.demo_title_clustering_force_zoom, ZoomClusteringDemoActivity::class.java) + ) + ), + DemoGroup( + R.string.category_data_layers, listOf( + Demo(R.string.demo_title_geojson, GeoJsonDemoActivity::class.java), + Demo(R.string.demo_title_kml, KmlDemoActivity::class.java), + Demo(R.string.demo_title_heatmaps, HeatmapsDemoActivity::class.java), + Demo(R.string.demo_title_heatmaps_places, HeatmapsPlacesDemoActivity::class.java), + Demo(R.string.demo_title_multi_layer, MultiLayerDemoActivity::class.java), + Demo(R.string.demo_title_transit_layer, TransitLayerDemoActivity::class.java) + ) + ), + DemoGroup( + R.string.category_geometry, listOf( + Demo(R.string.demo_title_poly_decode, PolyDecodeDemoActivity::class.java), + Demo(R.string.demo_title_poly_simplify, PolySimplifyDemoActivity::class.java), + Demo(R.string.demo_title_polyline_progress, PolylineProgressDemoActivity::class.java), + Demo(R.string.demo_title_spherical_distance, DistanceDemoActivity::class.java) + ) + ), + DemoGroup( + R.string.category_utilities, listOf( + Demo(R.string.demo_title_icon_generator, IconGeneratorDemoActivity::class.java), + Demo(R.string.demo_title_tile_provider, TileProviderAndProjectionDemo::class.java), + Demo(R.string.demo_title_animation_util, AnimationUtilDemoActivity::class.java) + ) + ), + DemoGroup( + R.string.category_street_view, listOf( + Demo(R.string.demo_title_street_view, StreetViewDemoActivity::class.java), + Demo(R.string.demo_title_street_view_java, StreetViewDemoJavaActivity::class.java) + ) + ) + ) + } +} + +/** + * Renders the list of demo groups. + * + * @param groups The list of groups to display. + * @param contentPadding Padding to apply to the content (handling system bars). + * @param onDemoClick Callback when a specific demo is clicked. + */ +@Composable +fun DemoList( + groups: List, + contentPadding: PaddingValues, + onDemoClick: (Class) -> Unit +) { + // We use rememberSaveable to preserve the expanded state across configuration changes (e.g. rotation). + // Storing the ID of the expanded group ensures only one group is open at a time (accordion style). + var expandedGroupResId by rememberSaveable { mutableStateOf(null) } + + LazyColumn(contentPadding = contentPadding) { + items(groups) { group -> + DemoGroupItem( + group = group, + isExpanded = expandedGroupResId == group.titleResId, + onHeaderClick = { + // Toggle expansion: if clicking the already open group, collapse it. Otherwise expand the new one. + expandedGroupResId = if (expandedGroupResId == group.titleResId) null else group.titleResId + }, + onDemoClick = onDemoClick + ) + HorizontalDivider( + thickness = dimensionResource(id = R.dimen.divider_thickness), + color = MaterialTheme.colorScheme.outlineVariant + ) + } + } +} + +/** + * Renders a single group of demos, including the header and the expandable content. + * + * @param group The group data. + * @param isExpanded Whether this group's content should be visible. + * @param onHeaderClick Callback to toggle expansion. + * @param onDemoClick Callback when a child demo item is clicked. + */ +@Composable +fun DemoGroupItem( + group: DemoGroup, + isExpanded: Boolean, + onHeaderClick: () -> Unit, + onDemoClick: (Class) -> Unit +) { + Column { + // Group Header + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onHeaderClick) + .background(MaterialTheme.colorScheme.surfaceVariant) // Use semantic color + .padding(dimensionResource(id = R.dimen.padding_medium)), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(group.titleResId), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.weight(1f) + ) + // Animate the arrow rotation for a polished feel + val rotation by animateFloatAsState(if (isExpanded) 180f else 0f, label = "arrowRotation") + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = if (isExpanded) stringResource(R.string.collapse) else stringResource(R.string.expand), + modifier = Modifier.rotate(rotation), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + // Expandable Content + // AnimatedVisibility provides a smooth expand/collapse animation + AnimatedVisibility(visible = isExpanded) { + Column { + group.demos.forEach { demo -> + Text( + text = stringResource(demo.titleResId), + modifier = Modifier + .fillMaxWidth() + .clickable { onDemoClick(demo.activityClass) } + .padding( + horizontal = dimensionResource(id = R.dimen.padding_large), + vertical = dimensionResource(id = R.dimen.padding_medium) + ), // Indented for hierarchy + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface + ) + HorizontalDivider( + thickness = dimensionResource(id = R.dimen.divider_thickness), + color = MaterialTheme.colorScheme.outlineVariant, + modifier = Modifier.padding(start = dimensionResource(id = R.dimen.padding_medium)) + ) + } + } + } + } +} diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/TransitLayerDemoActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/TransitLayerDemoActivity.kt new file mode 100644 index 000000000..b267f8bde --- /dev/null +++ b/demo/src/main/java/com/google/maps/android/utils/demo/TransitLayerDemoActivity.kt @@ -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.google.maps.android.utils.demo + +import android.widget.CheckBox +import android.widget.Toast +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.model.LatLng + +/** + * Demonstrates the Transit Layer feature of the Google Maps API. + * + * This activity allows users to toggle the transit layer on and off via a checkbox. + * The transit layer displays public transport lines and stations on the map. + */ +class TransitLayerDemoActivity : BaseDemoActivity() { + + override fun getLayoutId(): Int { + return R.layout.activity_transit_layer_demo + } + + override fun startDemo(isRestore: Boolean) { + if (!isRestore) { + map.moveCamera( + CameraUpdateFactory.newLatLngZoom( + LONDON, + DEFAULT_ZOOM + ) + ) + } + + val checkBox = findViewById(R.id.transit_toggle) + checkBox.isChecked = map.isTransitEnabled + checkBox.setOnCheckedChangeListener { _, isChecked -> + map.isTransitEnabled = isChecked + updateMessage() + } + + updateMessage() + } + + private fun updateMessage() { + val status = if (map.isTransitEnabled) { + getString(R.string.status_enabled) + } else { + getString(R.string.status_disabled) + } + Toast.makeText(this, getString(R.string.transit_layer_status_fmt, status), Toast.LENGTH_SHORT).show() + } + + companion object { + private val LONDON = LatLng(51.503186, -0.126446) + private const val DEFAULT_ZOOM = 10f + } +} diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/model/DemoModels.kt b/demo/src/main/java/com/google/maps/android/utils/demo/model/DemoModels.kt new file mode 100644 index 000000000..d851fa092 --- /dev/null +++ b/demo/src/main/java/com/google/maps/android/utils/demo/model/DemoModels.kt @@ -0,0 +1,42 @@ +/* + * 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.google.maps.android.utils.demo.model + + import android.app.Activity + import androidx.annotation.StringRes + + /** + * Represents a single demo activity in the list. + * + * @property titleResId The string resource ID for the demo title. + * @property activityClass The Activity class to launch when this demo is selected. + */ + data class Demo( + @StringRes val titleResId: Int, + val activityClass: Class + ) + + /** + * Represents a group of related demos (e.g., "Clustering", "Data Layers"). + * + * @property titleResId The string resource ID for the group header. + * @property demos The list of demos contained within this group. + */ + data class DemoGroup( + @StringRes val titleResId: Int, + val demos: List + ) diff --git a/demo/src/main/res/layout/activity_polyline_progress_demo.xml b/demo/src/main/res/layout/activity_polyline_progress_demo.xml index 4247ce089..49b958b8e 100644 --- a/demo/src/main/res/layout/activity_polyline_progress_demo.xml +++ b/demo/src/main/res/layout/activity_polyline_progress_demo.xml @@ -40,7 +40,7 @@ android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_centerVertical="true" - android:text="0%%" + android:text="@string/polyline_progress_0_percent" android:minWidth="40dp" android:gravity="center" /> @@ -53,19 +53,21 @@