Skip to content

Commit efb996f

Browse files
committed
feat(maps-app): Group demo activities on main screen
To make the main screen less cluttered and easier to navigate, the demo activities have been grouped into a list. This provides a cleaner and more organized user experience. This change also pulls in Material 3.
1 parent f2be1b2 commit efb996f

4 files changed

Lines changed: 293 additions & 157 deletions

File tree

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ kotlinxCoroutines = "1.10.2"
1616
leakcanaryAndroid = "2.14"
1717
mapsecrets = "2.0.1"
1818
mapsktx = "5.2.0"
19+
material3 = "1.3.2"
1920
org-jacoco-core = "0.8.13"
2021
screenshot = "0.0.1-alpha11"
2122
constraintlayout = "2.2.1"
@@ -29,6 +30,7 @@ androidx-compose-activity = { module = "androidx.activity:activity-compose", ver
2930
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
3031
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" }
3132
androidx-compose-material = { module = "androidx.compose.material:material" }
33+
androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
3234
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
3335
androidx-compose-ui-preview-tooling = { module = "androidx.compose.ui:ui-tooling-preview" }
3436
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }

maps-app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ dependencies {
6262
implementation(libs.androidx.compose.activity)
6363
implementation(libs.androidx.compose.foundation)
6464
implementation(libs.androidx.compose.material)
65+
implementation(libs.androidx.compose.material3)
6566
implementation(libs.kotlin)
6667
implementation(libs.kotlinx.coroutines.android)
6768
implementation(libs.androidx.compose.ui.preview.tooling)
@@ -71,7 +72,6 @@ dependencies {
7172
debugImplementation(libs.androidx.compose.ui.tooling)
7273
debugImplementation(libs.leakcanary.android)
7374

74-
7575
androidTestImplementation(platform(libs.androidx.compose.bom))
7676
androidTestImplementation(libs.androidx.test.core)
7777
androidTestImplementation(libs.androidx.test.rules)
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
2+
package com.google.maps.android.compose
3+
4+
import androidx.activity.ComponentActivity
5+
import androidx.compose.animation.AnimatedVisibility
6+
import androidx.compose.foundation.clickable
7+
import androidx.compose.foundation.layout.Column
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.lazy.LazyColumn
12+
import androidx.compose.foundation.lazy.items
13+
import androidx.compose.material.icons.Icons
14+
import androidx.compose.material.icons.filled.KeyboardArrowDown
15+
import androidx.compose.material.icons.filled.KeyboardArrowUp
16+
import androidx.compose.material3.Card
17+
import androidx.compose.material3.CardDefaults
18+
import androidx.compose.material3.Icon
19+
import androidx.compose.material3.MaterialTheme
20+
import androidx.compose.material3.Text
21+
import androidx.compose.runtime.Composable
22+
import androidx.compose.runtime.getValue
23+
import androidx.compose.runtime.mutableStateOf
24+
import androidx.compose.runtime.remember
25+
import androidx.compose.runtime.setValue
26+
import androidx.compose.ui.Alignment
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.text.font.FontWeight
29+
import androidx.compose.ui.unit.dp
30+
import com.google.maps.android.compose.markerexamples.AdvancedMarkersActivity
31+
import com.google.maps.android.compose.markerexamples.MarkerClusteringActivity
32+
import com.google.maps.android.compose.markerexamples.draggablemarkerscollectionwithpolygon.DraggableMarkersCollectionWithPolygonActivity
33+
import com.google.maps.android.compose.markerexamples.markerdragevents.MarkerDragEventsActivity
34+
import com.google.maps.android.compose.markerexamples.markerscollection.MarkersCollectionActivity
35+
import com.google.maps.android.compose.markerexamples.syncingdraggablemarkerwithdatamodel.SyncingDraggableMarkerWithDataModelActivity
36+
import com.google.maps.android.compose.markerexamples.updatingnodragmarkerwithdatamodel.UpdatingNoDragMarkerWithDataModelActivity
37+
import kotlin.reflect.KClass
38+
39+
// This file defines the data structures and composable functions used to display the list of
40+
// demo activities on the main screen of the app. The main goal is to present the demos in a
41+
// clear, organized, and easy-to-navigate way.
42+
43+
/**
44+
* A sealed class representing a group of related demo activities. Using a sealed class here
45+
* allows us to define a closed set of categories, which is ideal for a static list of demos.
46+
* This ensures that we can handle all possible categories in a type-safe way.
47+
*
48+
* @param title The title of the activity group.
49+
* @param activities The list of activities belonging to this group.
50+
*/
51+
sealed class ActivityGroup(
52+
val title: String,
53+
val activities: List<Activity>
54+
) {
55+
object MapTypes : ActivityGroup(
56+
"Map Types",
57+
listOf(
58+
Activity(
59+
"Basic Map",
60+
"A simple map showing the default configuration.",
61+
BasicMapActivity::class
62+
),
63+
Activity(
64+
"Street View",
65+
"A simple Street View.",
66+
StreetViewActivity::class
67+
),
68+
)
69+
)
70+
71+
object MapFeatures : ActivityGroup(
72+
"Map Features",
73+
listOf(
74+
Activity(
75+
"Location Tracking",
76+
"Tracking your location on the map.",
77+
LocationTrackingActivity::class
78+
),
79+
Activity(
80+
"Scale Bar",
81+
"Displaying a scale bar on the map.",
82+
ScaleBarActivity::class
83+
),
84+
Activity(
85+
"Custom Controls",
86+
"Replacing the default location button with a custom one.",
87+
CustomControlsActivity::class
88+
),
89+
Activity(
90+
"Accessibility",
91+
"Making your map more accessible.",
92+
AccessibilityActivity::class
93+
),
94+
)
95+
)
96+
97+
object Markers : ActivityGroup(
98+
"Markers",
99+
listOf(
100+
Activity(
101+
"Advanced Markers",
102+
"Adding advanced markers to your map.",
103+
AdvancedMarkersActivity::class
104+
),
105+
Activity(
106+
"Marker Clustering",
107+
"Clustering markers on your map.",
108+
MarkerClusteringActivity::class
109+
),
110+
Activity(
111+
"Marker Drag Events",
112+
"Listening to marker drag events.",
113+
MarkerDragEventsActivity::class
114+
),
115+
Activity(
116+
"Markers Collection",
117+
"Adding a collection of markers to your map.",
118+
MarkersCollectionActivity::class
119+
),
120+
Activity(
121+
"Syncing Draggable Marker With Data Model",
122+
"Keeping a draggable marker in sync with a data model.",
123+
SyncingDraggableMarkerWithDataModelActivity::class
124+
),
125+
Activity(
126+
"Updating No-Drag Marker With Data Model",
127+
"Updating a non-draggable marker with a data model.",
128+
UpdatingNoDragMarkerWithDataModelActivity::class
129+
),
130+
Activity(
131+
"Draggable Markers Collection With Polygon",
132+
"Dragging a collection of markers that form a polygon.",
133+
DraggableMarkersCollectionWithPolygonActivity::class
134+
),
135+
)
136+
)
137+
138+
object UIIntegration : ActivityGroup(
139+
"UI Integration",
140+
listOf(
141+
Activity(
142+
"Map in Column",
143+
"Displaying a map within a column.",
144+
MapInColumnActivity::class
145+
),
146+
Activity(
147+
"Maps in LazyColumn",
148+
"Displaying multiple maps in a LazyColumn.",
149+
MapsInLazyColumnActivity::class
150+
),
151+
Activity(
152+
"Fragment Demo",
153+
"Using the map compose components in a fragment.",
154+
FragmentDemoActivity::class
155+
),
156+
)
157+
)
158+
159+
object Performance : ActivityGroup(
160+
"Performance",
161+
listOf(
162+
Activity(
163+
"Recomposition",
164+
"Understanding how recomposition works with maps.",
165+
RecompositionActivity::class
166+
),
167+
)
168+
)
169+
}
170+
171+
/**
172+
* A data class representing a single demo activity. This class serves as a model for each
173+
* item in the demo list.
174+
*
175+
* @param title The title of the activity.
176+
* @param description A short description of what the demo showcases.
177+
* @param kClass The class of the activity to be launched.
178+
*/
179+
data class Activity(
180+
val title: String,
181+
val description: String,
182+
val kClass: KClass<out ComponentActivity>
183+
)
184+
185+
/**
186+
* The single source of truth for all the demo activity groups. This list is used to populate
187+
* the main screen.
188+
*/
189+
val allActivityGroups = listOf(
190+
ActivityGroup.MapTypes,
191+
ActivityGroup.MapFeatures,
192+
ActivityGroup.Markers,
193+
ActivityGroup.UIIntegration,
194+
ActivityGroup.Performance,
195+
)
196+
197+
/**
198+
* A composable function that displays a collapsible list of demo activity groups. This is the
199+
* main UI component for the main screen.
200+
*
201+
* The list is built using a `LazyColumn` for performance, ensuring that only the visible items
202+
* are rendered. Each group is represented by a clickable `Card` that expands or collapses to
203+
* reveal the activities within it. This approach keeps the UI clean and organized, especially
204+
* with a large number of demos.
205+
*
206+
* @param onActivityClick A lambda function to be invoked when a demo activity is clicked. This
207+
* allows the navigation logic to be decoupled from the UI.
208+
*/
209+
@Composable
210+
fun DemoList(
211+
onActivityClick: (KClass<out ComponentActivity>) -> Unit
212+
) {
213+
// State to keep track of the currently expanded group.
214+
var expandedGroup by remember { mutableStateOf<ActivityGroup?>(null) }
215+
216+
LazyColumn {
217+
items(allActivityGroups) { group ->
218+
Column {
219+
// The card representing the group header.
220+
Card(
221+
// Highlight the card when it's expanded.
222+
colors = if (expandedGroup == group) {
223+
CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
224+
} else {
225+
CardDefaults.cardColors()
226+
},
227+
modifier = Modifier
228+
.fillMaxWidth()
229+
.padding(8.dp)
230+
.clickable {
231+
// Toggle the expanded state of the group.
232+
expandedGroup = if (expandedGroup == group) null else group
233+
}
234+
) {
235+
Row(
236+
modifier = Modifier.padding(16.dp),
237+
verticalAlignment = Alignment.CenterVertically
238+
) {
239+
Text(
240+
text = group.title,
241+
fontWeight = FontWeight.Bold,
242+
modifier = Modifier.weight(1f)
243+
)
244+
// Show an up or down arrow to indicate the expanded/collapsed state.
245+
Icon(
246+
imageVector = if (expandedGroup == group) {
247+
Icons.Default.KeyboardArrowUp
248+
} else {
249+
Icons.Default.KeyboardArrowDown
250+
},
251+
contentDescription = null
252+
)
253+
}
254+
}
255+
256+
// Animate the visibility of the activities within the group.
257+
AnimatedVisibility(visible = expandedGroup == group) {
258+
Column(
259+
modifier = Modifier.padding(start = 16.dp, end = 16.dp)
260+
) {
261+
// Create a card for each activity in the group.
262+
group.activities.forEach { activity ->
263+
Card(
264+
modifier = Modifier
265+
.fillMaxWidth()
266+
.padding(vertical = 4.dp)
267+
.clickable { onActivityClick(activity.kClass) }
268+
) {
269+
Column(modifier = Modifier.padding(16.dp)) {
270+
Text(text = activity.title, fontWeight = FontWeight.Bold)
271+
Text(text = activity.description)
272+
}
273+
}
274+
}
275+
}
276+
}
277+
}
278+
}
279+
}
280+
}

0 commit comments

Comments
 (0)