Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion places-compose-demo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.AndroidPlacesCompose">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.google.android.libraries.places.compose.demo.ui.theme.AndroidPlacesComposeDemoTheme
import android.content.Intent
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
Expand Down Expand Up @@ -60,7 +60,7 @@ class MainActivity : ComponentActivity() {
setContent {
AndroidPlacesComposeDemoTheme {
Surface(
modifier = Modifier.fillMaxSize().systemBarsPadding(),
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
ActivityButtons()
Expand All @@ -70,11 +70,17 @@ class MainActivity : ComponentActivity() {
}
}

data class ActivityItem(
val activityClass: Class<*>,
val titleRes: Int,
val descRes: Int,
)

val activities = listOf(
AutocompleteActivity::class.java to R.string.autocomplete_button,
LandmarkSelectionActivity::class.java to R.string.landmark_selection_button,
AddressCompletionActivity::class.java to R.string.address_validation_button,
PlacesAutocompleteMinimalActivity::class.java to R.string.minimal_autocomplete_button
ActivityItem(AutocompleteActivity::class.java, R.string.autocomplete_button, R.string.autocomplete_button_desc),
ActivityItem(LandmarkSelectionActivity::class.java, R.string.landmark_selection_button, R.string.landmark_selection_button_desc),
ActivityItem(AddressCompletionActivity::class.java, R.string.address_validation_button, R.string.address_validation_button_desc),
ActivityItem(PlacesAutocompleteMinimalActivity::class.java, R.string.minimal_autocomplete_button, R.string.minimal_autocomplete_button_desc),
)

@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -98,15 +104,33 @@ fun ActivityButtons() {
Column(
Modifier
.padding(paddingValues)
.padding(top = 16.dp)
.fillMaxSize()
.padding(horizontal = 24.dp, vertical = 20.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
for ((activityClass, buttonTextResId) in activities) {
Button(onClick = { context.startActivity(Intent(context, activityClass)) }) {
Text(stringResource(buttonTextResId))
for (item in activities) {
Button(
onClick = { context.startActivity(Intent(context, item.activityClass)) },
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(item.titleRes),
style = MaterialTheme.typography.titleMedium
)
Text(
text = stringResource(item.descRes),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.85f)
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package com.google.android.libraries.places.compose.demo.data.repositories

import android.content.Context
import android.net.Uri
import android.util.Log
import com.android.volley.Request
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
Expand Down Expand Up @@ -67,26 +68,42 @@ class GeocoderRepository(
suspend fun reverseGeocode(
latLng: LatLng,
includeAddressDescriptors: Boolean = true
): ReverseGeocodingResponse {
): ReverseGeocodingResponse? {
val url = buildRequestUrl(latLng, includeAddressDescriptors)

return suspendCancellableCoroutine { cont ->
val queue = Volley.newRequestQueue(context)
val stringRequest =
StringRequest(
Request.Method.GET,
url,
{ response -> cont.resume(parseFullGeocodeResponse(response)) },
{ error -> cont.resumeWithException(RuntimeException(error.localizedMessage)) },
)
queue.add(stringRequest)
return try {
suspendCancellableCoroutine { cont ->
val queue = Volley.newRequestQueue(context)
val stringRequest =
StringRequest(
Request.Method.GET,
url,
{ response ->
try {
cont.resume(parseFullGeocodeResponse(response))
} catch (e: Exception) {
Log.e("GeocoderRepository", "Error parsing geocoder response", e)
cont.resume(null)
}
},
{ error ->
Log.e("GeocoderRepository", "Volley reverse geocode error: ${error.networkResponse?.statusCode} - ${error.localizedMessage}", error)
cont.resume(null)
},
)
queue.add(stringRequest)
}
} catch (e: Exception) {
Log.e("GeocoderRepository", "Reverse geocode exception", e)
null
}
}

private fun parseFullGeocodeResponse(response: String): ReverseGeocodingResponse {
val fullResult = gson.fromJson(response, ReverseGeocodingResponse::class.java)
// TODO: handle the status -- consider a monad pattern (Result?)

if (fullResult.status != "OK" && fullResult.status != "ZERO_RESULTS") {
Log.w("GeocoderRepository", "Geocode API warning: status=${fullResult.status}, error_message=${fullResult.errorMessage}")
}
return fullResult
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,19 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import com.google.android.gms.maps.model.LatLng
import com.google.android.libraries.places.compose.autocomplete.components.PlacesAutocompleteTextField
import com.google.android.libraries.places.compose.autocomplete.models.AutocompletePlace
Expand Down Expand Up @@ -150,6 +158,42 @@ private fun AddressEntryForm(
.padding(top = 16.dp)
.verticalScroll(rememberScrollState()),
) {
if (addressEntry.nearbyObjects.isEmpty()) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant
)
) {
Column(
modifier = Modifier.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
Text(
text = stringResource(R.string.address_descriptors_info_title),
style = MaterialTheme.typography.titleMedium
)
}
Text(
text = stringResource(R.string.address_descriptors_unavailable_message),
style = MaterialTheme.typography.bodyMedium
)
}
}
}

AddressDisplay(
address = addressEntry.displayAddress,
modifier = Modifier.fillMaxWidth(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.google.android.libraries.places.compose.demo.presentation.common
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
Expand Down Expand Up @@ -33,6 +33,10 @@ import com.google.android.libraries.places.compose.demo.presentation.landmark.Ge
import com.google.android.libraries.places.compose.demo.ui.theme.AndroidPlacesComposeDemoTheme
import androidx.compose.ui.platform.LocalConfiguration

import androidx.compose.material3.SnackbarDuration
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CommonScreen(
Expand All @@ -51,57 +55,75 @@ fun CommonScreen(
getUnitsConverter(country)
}

val coroutineScope = rememberCoroutineScope()

fun onEvent(event: CommonEvent) {
commonViewModel.onEvent(event)
}

AndroidPlacesComposeDemoTheme {
GetLocationPermission {
CompositionLocalProvider(LocalUnitsConverter provides unitsConverter) {
Scaffold(
modifier = Modifier.fillMaxSize().systemBarsPadding(),
topBar = {
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
actionIconContentColor = MaterialTheme.colorScheme.primary,
navigationIconContentColor = MaterialTheme.colorScheme.primary,
),
title = { Text(stringResource(titleId)) },
navigationIcon = {
IconButton(onClick = { onNavigateUp() }) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(R.string.back)
)
}
},
actions = {
SelectableButton(
buttonState = commonViewState.buttonStates.currentLocation,
onClick = { onEvent(CommonEvent.OnUseSystemLocation) },
iconId = R.drawable.baseline_my_location_24,
contentDescription = R.string.fill_address_from_current_location
CompositionLocalProvider(LocalUnitsConverter provides unitsConverter) {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
actionIconContentColor = MaterialTheme.colorScheme.primary,
navigationIconContentColor = MaterialTheme.colorScheme.primary,
),
title = { Text(stringResource(titleId)) },
navigationIcon = {
IconButton(onClick = { onNavigateUp() }) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(R.string.back)
)
}
},
actions = {
SelectableButton(
buttonState = commonViewState.buttonStates.currentLocation,
onClick = { onEvent(CommonEvent.OnUseSystemLocation) },
iconId = R.drawable.baseline_my_location_24,
contentDescription = R.string.fill_address_from_current_location
)

NextLocationButton(
isSelected = commonViewState.buttonStates.mockLocation == ButtonState.SELECTED
) {
onEvent(CommonEvent.OnNextMockLocation)
NextLocationButton(
isSelected = commonViewState.buttonStates.mockLocation == ButtonState.SELECTED
) {
onEvent(CommonEvent.OnNextMockLocation)
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = "Location: ${commonViewState.locationLabel ?: "Mock Location"}",
duration = SnackbarDuration.Short
)
}

SelectableButton(
buttonState = commonViewState.buttonStates.map,
iconId = R.drawable.baseline_map_24,
contentDescription = R.string.toggle_map,
onClick = { onEvent(CommonEvent.OnToggleMap) }
)
}
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { paddingValues ->

SelectableButton(
buttonState = commonViewState.buttonStates.map,
iconId = R.drawable.baseline_map_24,
contentDescription = R.string.toggle_map,
onClick = { onEvent(CommonEvent.OnToggleMap) }
)
}
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { paddingValues ->
GetLocationPermission(
modifier = Modifier.padding(paddingValues),
onFallbackToMock = {
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = "Falling back to mock locations. You can cycle preset locations via the top bar.",
duration = SnackbarDuration.Long
)
}
}
) {
content(paddingValues)
}
}
Expand Down
Loading