Loading cardinal-android/app/src/main/java/earth/maps/cardinal/ui/directions/DirectionsScreen.kt +472 −308 Original line number Diff line number Diff line Loading @@ -82,6 +82,7 @@ import earth.maps.cardinal.ui.core.Screen import earth.maps.cardinal.ui.place.SearchResults import earth.maps.cardinal.ui.saved.QuickSuggestions import io.github.dellisd.spatialk.geojson.BoundingBox import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import uniffi.ferrostar.Route Loading Loading @@ -153,11 +154,53 @@ fun DirectionsScreen( modifier = Modifier .fillMaxSize() .padding(horizontal = dimensionResource(dimen.padding)) ) { if (!isAnyFieldFocused) { DirectionsScreenFullUI( viewModel = viewModel, onPeekHeightChange = onPeekHeightChange, onBack = onBack, onFullExpansionRequired = onFullExpansionRequired, navController = navController, onFieldFocusStateChange = { fieldFocusState = it }, fieldFocusState = fieldFocusState, routeState = routeState, availableProfiles = availableProfiles, appPreferences = appPreferences, hasNotificationPermission = hasNotificationPermission, onRequestNotificationPermission = onRequestNotificationPermission ) } else { DirectionsScreenFocusedField( viewModel = viewModel, fieldFocusState = fieldFocusState, savedPlaces = savedPlaces, hasLocationPermission = hasLocationPermission, onRequestLocationPermission = onRequestLocationPermission, pendingLocationRequest = pendingLocationRequest, coroutineScope = coroutineScope ) } } } @Composable private fun DirectionsScreenFullUI( viewModel: DirectionsViewModel, onPeekHeightChange: (Dp) -> Unit, onBack: () -> Unit, onFullExpansionRequired: () -> Job, navController: NavController, onFieldFocusStateChange: (FieldFocusState) -> Unit, fieldFocusState: FieldFocusState, routeState: RouteState, availableProfiles: List<RoutingProfile>, appPreferences: AppPreferenceRepository, hasNotificationPermission: Boolean, onRequestNotificationPermission: () -> Unit ) { val density = androidx.compose.ui.platform.LocalDensity.current // Conditionally show UI based on field focus if (!isAnyFieldFocused) { // Show full UI when no field is focused Column( modifier = Modifier Loading Loading @@ -194,20 +237,10 @@ fun DirectionsScreen( PlaceField( label = stringResource(string.from), place = viewModel.fromPlace, onCleared = { viewModel.updateFromPlace(null) }, onTextChange = { viewModel.updateSearchQuery(it) }, onCleared = { viewModel.updateFromPlace(null) }, onTextChange = { viewModel.updateSearchQuery(it) }, onTextFieldFocusChange = { fieldFocusState = if (it) { onFullExpansionRequired() FieldFocusState.FROM } else { FieldFocusState.NONE } onFieldFocusStateChange(if (it) FieldFocusState.FROM else FieldFocusState.NONE) }, isFocused = fieldFocusState == FieldFocusState.FROM, showRecalculateButton = viewModel.fromPlace != null && viewModel.toPlace != null, Loading @@ -221,19 +254,10 @@ fun DirectionsScreen( PlaceField( label = stringResource(string.to), place = viewModel.toPlace, onCleared = { viewModel.updateToPlace(null) }, onTextChange = { viewModel.updateSearchQuery(it) }, onCleared = { viewModel.updateToPlace(null) }, onTextChange = { viewModel.updateSearchQuery(it) }, onTextFieldFocusChange = { fieldFocusState = if (it) { onFullExpansionRequired() FieldFocusState.TO } else { FieldFocusState.NONE } onFieldFocusStateChange(if (it) FieldFocusState.TO else FieldFocusState.NONE) }, isFocused = fieldFocusState == FieldFocusState.TO, showFlipButton = viewModel.fromPlace != null && viewModel.toPlace != null, Loading Loading @@ -279,8 +303,47 @@ fun DirectionsScreen( } // Route results if (viewModel.selectedRoutingMode == RoutingMode.PUBLIC_TRANSPORT) { DirectionsRouteResults( viewModel = viewModel, routeState = routeState, navController = navController, appPreferences = appPreferences, hasNotificationPermission = hasNotificationPermission, onRequestNotificationPermission = onRequestNotificationPermission ) } @Composable private fun DirectionsRouteResults( viewModel: DirectionsViewModel, routeState: RouteState, navController: NavController, appPreferences: AppPreferenceRepository, hasNotificationPermission: Boolean, onRequestNotificationPermission: () -> Unit ) { val planState = viewModel.planState if (viewModel.selectedRoutingMode == RoutingMode.PUBLIC_TRANSPORT) { TransitRouteResults(planState = planState, navController = navController, viewModel = viewModel, appPreferences = appPreferences) } else { NonTransitRouteResults( routeState = routeState, viewModel = viewModel, navController = navController, appPreferences = appPreferences, hasNotificationPermission = hasNotificationPermission, onRequestNotificationPermission = onRequestNotificationPermission ) } } @Composable private fun TransitRouteResults( planState: TransitPlanState, navController: NavController, viewModel: DirectionsViewModel, appPreferences: AppPreferenceRepository ) { when { planState.isLoading -> { Text( Loading Loading @@ -321,7 +384,17 @@ fun DirectionsScreen( ) } } } else { } @Composable private fun NonTransitRouteResults( routeState: RouteState, viewModel: DirectionsViewModel, navController: NavController, appPreferences: AppPreferenceRepository, hasNotificationPermission: Boolean, onRequestNotificationPermission: () -> Unit ) { when { routeState.isLoading -> { Text( Loading Loading @@ -367,28 +440,31 @@ fun DirectionsScreen( } } } } else { @Composable private fun DirectionsScreenFocusedField( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, savedPlaces: List<Place>, hasLocationPermission: Boolean, onRequestLocationPermission: () -> Unit, pendingLocationRequest: FieldFocusState?, coroutineScope: CoroutineScope ) { // Show only the focused field and search results when a field is focused val currentFocusState = fieldFocusState PlaceField( label = if (currentFocusState == FieldFocusState.FROM) "From" else "To", place = if (currentFocusState == FieldFocusState.FROM) viewModel.fromPlace else viewModel.toPlace, label = if (fieldFocusState == FieldFocusState.FROM) "From" else "To", place = if (fieldFocusState == FieldFocusState.FROM) viewModel.fromPlace else viewModel.toPlace, onCleared = { if (currentFocusState == FieldFocusState.FROM) { if (fieldFocusState == FieldFocusState.FROM) { viewModel.updateFromPlace(null) } else { viewModel.updateToPlace(null) } }, onTextChange = { viewModel.updateSearchQuery(it) }, onTextChange = { viewModel.updateSearchQuery(it) }, onTextFieldFocusChange = { isFocused -> fieldFocusState = if (isFocused) { currentFocusState } else { FieldFocusState.NONE } }, isFocused = true, modifier = Modifier Loading @@ -397,73 +473,133 @@ fun DirectionsScreen( ) // Show search results or quick suggestions based on search query if (viewModel.isSearching) { FocusedFieldContent( viewModel = viewModel, fieldFocusState = fieldFocusState, savedPlaces = savedPlaces, hasLocationPermission = hasLocationPermission, onRequestLocationPermission = onRequestLocationPermission, pendingLocationRequest = pendingLocationRequest, coroutineScope = coroutineScope ) } @Composable private fun FocusedFieldContent( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, savedPlaces: List<Place>, hasLocationPermission: Boolean, onRequestLocationPermission: () -> Unit, pendingLocationRequest: FieldFocusState?, coroutineScope: CoroutineScope ) { when { viewModel.isSearching -> { SearchingIndicator() } viewModel.searchQuery.isEmpty() -> { QuickSuggestionsContent( viewModel = viewModel, fieldFocusState = fieldFocusState, savedPlaces = savedPlaces, hasLocationPermission = hasLocationPermission, onRequestLocationPermission = onRequestLocationPermission, pendingLocationRequest = pendingLocationRequest, coroutineScope = coroutineScope ) } else -> { SearchResultsContent( viewModel = viewModel, fieldFocusState = fieldFocusState ) } } } @Composable private fun SearchingIndicator() { Text( text = "Searching...", modifier = Modifier .fillMaxWidth() .padding(dimensionResource(dimen.padding)) ) } else if (viewModel.searchQuery.isEmpty()) { // Show quick suggestions when no search query QuickSuggestions( onMyLocationSelected = { // Check permissions before attempting to get location if (hasLocationPermission) { // Launch coroutine to get current location coroutineScope.launch { val myLocationPlace = viewModel.getCurrentLocationAsPlace() myLocationPlace?.let { place -> // Update the appropriate place based on which field is focused if (fieldFocusState == FieldFocusState.FROM) { viewModel.updateFromPlace(place) } else { viewModel.updateToPlace(place) } // Clear focus state after selection fieldFocusState = FieldFocusState.NONE } } } else { // Set pending request for auto-retry after permission grant pendingLocationRequest = fieldFocusState // Request location permission onRequestLocationPermission() } }, @Composable private fun QuickSuggestionsContent( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, savedPlaces: List<Place>, hasLocationPermission: Boolean, onRequestLocationPermission: () -> Unit, pendingLocationRequest: FieldFocusState?, coroutineScope: CoroutineScope ) { QuickSuggestions( onMyLocationSelected = handleMyLocationSelected( viewModel = viewModel, fieldFocusState = fieldFocusState, hasLocationPermission = hasLocationPermission, onRequestLocationPermission = onRequestLocationPermission, coroutineScope = coroutineScope ), savedPlaces = savedPlaces, onSavedPlaceSelected = { place -> // Update the appropriate place based on which field is focused if (fieldFocusState == FieldFocusState.FROM) { viewModel.updateFromPlace(place) } else { viewModel.updateToPlace(place) } // Clear focus state after selection fieldFocusState = FieldFocusState.NONE updatePlaceForField(viewModel, fieldFocusState, place) }, isGettingLocation = viewModel.isGettingLocation, modifier = Modifier.fillMaxWidth() ) } else { // Show search results when there's a query } @Composable private fun SearchResultsContent( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState ) { SearchResults( viewModel = hiltViewModel(), geocodeResults = deduplicateSearchResults(viewModel.geocodeResults.value), onPlaceSelected = { place -> // Update the appropriate place based on which field is focused updatePlaceForField(viewModel, fieldFocusState, place) }, modifier = Modifier.fillMaxWidth() ) } private fun updatePlaceForField( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, place: Place ) { if (fieldFocusState == FieldFocusState.FROM) { viewModel.updateFromPlace(place) } else { viewModel.updateToPlace(place) } // Clear focus state after selection fieldFocusState = FieldFocusState.NONE }, modifier = Modifier.fillMaxWidth() ) } private fun handleMyLocationSelected( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, hasLocationPermission: Boolean, onRequestLocationPermission: () -> Unit, coroutineScope: CoroutineScope ): () -> Unit = { if (hasLocationPermission) { coroutineScope.launch { val myLocationPlace = viewModel.getCurrentLocationAsPlace() myLocationPlace?.let { place -> updatePlaceForField(viewModel, fieldFocusState, place) } } } else { onRequestLocationPermission() } } Loading Loading @@ -836,19 +972,47 @@ private fun FerrostarRouteResults( } } // Profile selection dialog if (showProfileDialog) { // Dialogs ProfileSelectionDialog( showDialog = showProfileDialog, onDismiss = { showProfileDialog = false }, selectedProfile = selectedProfile, availableProfiles = availableProfiles, onProfileSelected = { profile -> viewModel.selectRoutingProfile(profile) showProfileDialog = false } ) NotificationRequestDialog( showDialog = showNotificationDialog, onDismiss = { showNotificationDialog = false }, onConfirm = { showNotificationDialog = false pendingNavigation = true onRequestNotificationPermission() } ) } @Composable private fun ProfileSelectionDialog( showDialog: Boolean, onDismiss: () -> Unit, selectedProfile: RoutingProfile?, availableProfiles: List<RoutingProfile>, onProfileSelected: (RoutingProfile?) -> Unit ) { if (showDialog) { AlertDialog( onDismissRequest = { showProfileDialog = false }, onDismissRequest = onDismiss, title = { Text(stringResource(string.select_routing_profile)) }, text = { Column { // Default option TextButton( onClick = { viewModel.selectRoutingProfile(null) showProfileDialog = false }, modifier = Modifier.fillMaxWidth() onClick = { onProfileSelected(null) }, modifier = Modifier.fillMaxWidth() ) { Text( text = stringResource(string.default_profile), Loading @@ -859,10 +1023,8 @@ private fun FerrostarRouteResults( // Custom profiles availableProfiles.forEach { profile -> TextButton( onClick = { viewModel.selectRoutingProfile(profile) showProfileDialog = false }, modifier = Modifier.fillMaxWidth() onClick = { onProfileSelected(profile) }, modifier = Modifier.fillMaxWidth() ) { Text( text = profile.name, Loading @@ -873,29 +1035,31 @@ private fun FerrostarRouteResults( } }, confirmButton = { TextButton(onClick = { showProfileDialog = false }) { TextButton(onClick = onDismiss) { Text(stringResource(string.cancel_change_routing_profile)) } }) } } // Notification permission dialog if (showNotificationDialog) { @Composable private fun NotificationRequestDialog( showDialog: Boolean, onDismiss: () -> Unit, onConfirm: () -> Unit ) { if (showDialog) { AlertDialog( onDismissRequest = { showNotificationDialog = false }, onDismissRequest = onDismiss, title = { Text(stringResource(string.notification_ask_title)) }, text = { Text(stringResource(string.notification_ask_body)) }, confirmButton = { TextButton(onClick = { showNotificationDialog = false pendingNavigation = true onRequestNotificationPermission() }) { TextButton(onClick = onConfirm) { Text(stringResource(string.got_it)) } }, dismissButton = { TextButton(onClick = { showNotificationDialog = false }) { TextButton(onClick = onDismiss) { Text(stringResource(string.cancel)) } }) Loading Loading
cardinal-android/app/src/main/java/earth/maps/cardinal/ui/directions/DirectionsScreen.kt +472 −308 Original line number Diff line number Diff line Loading @@ -82,6 +82,7 @@ import earth.maps.cardinal.ui.core.Screen import earth.maps.cardinal.ui.place.SearchResults import earth.maps.cardinal.ui.saved.QuickSuggestions import io.github.dellisd.spatialk.geojson.BoundingBox import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import uniffi.ferrostar.Route Loading Loading @@ -153,11 +154,53 @@ fun DirectionsScreen( modifier = Modifier .fillMaxSize() .padding(horizontal = dimensionResource(dimen.padding)) ) { if (!isAnyFieldFocused) { DirectionsScreenFullUI( viewModel = viewModel, onPeekHeightChange = onPeekHeightChange, onBack = onBack, onFullExpansionRequired = onFullExpansionRequired, navController = navController, onFieldFocusStateChange = { fieldFocusState = it }, fieldFocusState = fieldFocusState, routeState = routeState, availableProfiles = availableProfiles, appPreferences = appPreferences, hasNotificationPermission = hasNotificationPermission, onRequestNotificationPermission = onRequestNotificationPermission ) } else { DirectionsScreenFocusedField( viewModel = viewModel, fieldFocusState = fieldFocusState, savedPlaces = savedPlaces, hasLocationPermission = hasLocationPermission, onRequestLocationPermission = onRequestLocationPermission, pendingLocationRequest = pendingLocationRequest, coroutineScope = coroutineScope ) } } } @Composable private fun DirectionsScreenFullUI( viewModel: DirectionsViewModel, onPeekHeightChange: (Dp) -> Unit, onBack: () -> Unit, onFullExpansionRequired: () -> Job, navController: NavController, onFieldFocusStateChange: (FieldFocusState) -> Unit, fieldFocusState: FieldFocusState, routeState: RouteState, availableProfiles: List<RoutingProfile>, appPreferences: AppPreferenceRepository, hasNotificationPermission: Boolean, onRequestNotificationPermission: () -> Unit ) { val density = androidx.compose.ui.platform.LocalDensity.current // Conditionally show UI based on field focus if (!isAnyFieldFocused) { // Show full UI when no field is focused Column( modifier = Modifier Loading Loading @@ -194,20 +237,10 @@ fun DirectionsScreen( PlaceField( label = stringResource(string.from), place = viewModel.fromPlace, onCleared = { viewModel.updateFromPlace(null) }, onTextChange = { viewModel.updateSearchQuery(it) }, onCleared = { viewModel.updateFromPlace(null) }, onTextChange = { viewModel.updateSearchQuery(it) }, onTextFieldFocusChange = { fieldFocusState = if (it) { onFullExpansionRequired() FieldFocusState.FROM } else { FieldFocusState.NONE } onFieldFocusStateChange(if (it) FieldFocusState.FROM else FieldFocusState.NONE) }, isFocused = fieldFocusState == FieldFocusState.FROM, showRecalculateButton = viewModel.fromPlace != null && viewModel.toPlace != null, Loading @@ -221,19 +254,10 @@ fun DirectionsScreen( PlaceField( label = stringResource(string.to), place = viewModel.toPlace, onCleared = { viewModel.updateToPlace(null) }, onTextChange = { viewModel.updateSearchQuery(it) }, onCleared = { viewModel.updateToPlace(null) }, onTextChange = { viewModel.updateSearchQuery(it) }, onTextFieldFocusChange = { fieldFocusState = if (it) { onFullExpansionRequired() FieldFocusState.TO } else { FieldFocusState.NONE } onFieldFocusStateChange(if (it) FieldFocusState.TO else FieldFocusState.NONE) }, isFocused = fieldFocusState == FieldFocusState.TO, showFlipButton = viewModel.fromPlace != null && viewModel.toPlace != null, Loading Loading @@ -279,8 +303,47 @@ fun DirectionsScreen( } // Route results if (viewModel.selectedRoutingMode == RoutingMode.PUBLIC_TRANSPORT) { DirectionsRouteResults( viewModel = viewModel, routeState = routeState, navController = navController, appPreferences = appPreferences, hasNotificationPermission = hasNotificationPermission, onRequestNotificationPermission = onRequestNotificationPermission ) } @Composable private fun DirectionsRouteResults( viewModel: DirectionsViewModel, routeState: RouteState, navController: NavController, appPreferences: AppPreferenceRepository, hasNotificationPermission: Boolean, onRequestNotificationPermission: () -> Unit ) { val planState = viewModel.planState if (viewModel.selectedRoutingMode == RoutingMode.PUBLIC_TRANSPORT) { TransitRouteResults(planState = planState, navController = navController, viewModel = viewModel, appPreferences = appPreferences) } else { NonTransitRouteResults( routeState = routeState, viewModel = viewModel, navController = navController, appPreferences = appPreferences, hasNotificationPermission = hasNotificationPermission, onRequestNotificationPermission = onRequestNotificationPermission ) } } @Composable private fun TransitRouteResults( planState: TransitPlanState, navController: NavController, viewModel: DirectionsViewModel, appPreferences: AppPreferenceRepository ) { when { planState.isLoading -> { Text( Loading Loading @@ -321,7 +384,17 @@ fun DirectionsScreen( ) } } } else { } @Composable private fun NonTransitRouteResults( routeState: RouteState, viewModel: DirectionsViewModel, navController: NavController, appPreferences: AppPreferenceRepository, hasNotificationPermission: Boolean, onRequestNotificationPermission: () -> Unit ) { when { routeState.isLoading -> { Text( Loading Loading @@ -367,28 +440,31 @@ fun DirectionsScreen( } } } } else { @Composable private fun DirectionsScreenFocusedField( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, savedPlaces: List<Place>, hasLocationPermission: Boolean, onRequestLocationPermission: () -> Unit, pendingLocationRequest: FieldFocusState?, coroutineScope: CoroutineScope ) { // Show only the focused field and search results when a field is focused val currentFocusState = fieldFocusState PlaceField( label = if (currentFocusState == FieldFocusState.FROM) "From" else "To", place = if (currentFocusState == FieldFocusState.FROM) viewModel.fromPlace else viewModel.toPlace, label = if (fieldFocusState == FieldFocusState.FROM) "From" else "To", place = if (fieldFocusState == FieldFocusState.FROM) viewModel.fromPlace else viewModel.toPlace, onCleared = { if (currentFocusState == FieldFocusState.FROM) { if (fieldFocusState == FieldFocusState.FROM) { viewModel.updateFromPlace(null) } else { viewModel.updateToPlace(null) } }, onTextChange = { viewModel.updateSearchQuery(it) }, onTextChange = { viewModel.updateSearchQuery(it) }, onTextFieldFocusChange = { isFocused -> fieldFocusState = if (isFocused) { currentFocusState } else { FieldFocusState.NONE } }, isFocused = true, modifier = Modifier Loading @@ -397,73 +473,133 @@ fun DirectionsScreen( ) // Show search results or quick suggestions based on search query if (viewModel.isSearching) { FocusedFieldContent( viewModel = viewModel, fieldFocusState = fieldFocusState, savedPlaces = savedPlaces, hasLocationPermission = hasLocationPermission, onRequestLocationPermission = onRequestLocationPermission, pendingLocationRequest = pendingLocationRequest, coroutineScope = coroutineScope ) } @Composable private fun FocusedFieldContent( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, savedPlaces: List<Place>, hasLocationPermission: Boolean, onRequestLocationPermission: () -> Unit, pendingLocationRequest: FieldFocusState?, coroutineScope: CoroutineScope ) { when { viewModel.isSearching -> { SearchingIndicator() } viewModel.searchQuery.isEmpty() -> { QuickSuggestionsContent( viewModel = viewModel, fieldFocusState = fieldFocusState, savedPlaces = savedPlaces, hasLocationPermission = hasLocationPermission, onRequestLocationPermission = onRequestLocationPermission, pendingLocationRequest = pendingLocationRequest, coroutineScope = coroutineScope ) } else -> { SearchResultsContent( viewModel = viewModel, fieldFocusState = fieldFocusState ) } } } @Composable private fun SearchingIndicator() { Text( text = "Searching...", modifier = Modifier .fillMaxWidth() .padding(dimensionResource(dimen.padding)) ) } else if (viewModel.searchQuery.isEmpty()) { // Show quick suggestions when no search query QuickSuggestions( onMyLocationSelected = { // Check permissions before attempting to get location if (hasLocationPermission) { // Launch coroutine to get current location coroutineScope.launch { val myLocationPlace = viewModel.getCurrentLocationAsPlace() myLocationPlace?.let { place -> // Update the appropriate place based on which field is focused if (fieldFocusState == FieldFocusState.FROM) { viewModel.updateFromPlace(place) } else { viewModel.updateToPlace(place) } // Clear focus state after selection fieldFocusState = FieldFocusState.NONE } } } else { // Set pending request for auto-retry after permission grant pendingLocationRequest = fieldFocusState // Request location permission onRequestLocationPermission() } }, @Composable private fun QuickSuggestionsContent( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, savedPlaces: List<Place>, hasLocationPermission: Boolean, onRequestLocationPermission: () -> Unit, pendingLocationRequest: FieldFocusState?, coroutineScope: CoroutineScope ) { QuickSuggestions( onMyLocationSelected = handleMyLocationSelected( viewModel = viewModel, fieldFocusState = fieldFocusState, hasLocationPermission = hasLocationPermission, onRequestLocationPermission = onRequestLocationPermission, coroutineScope = coroutineScope ), savedPlaces = savedPlaces, onSavedPlaceSelected = { place -> // Update the appropriate place based on which field is focused if (fieldFocusState == FieldFocusState.FROM) { viewModel.updateFromPlace(place) } else { viewModel.updateToPlace(place) } // Clear focus state after selection fieldFocusState = FieldFocusState.NONE updatePlaceForField(viewModel, fieldFocusState, place) }, isGettingLocation = viewModel.isGettingLocation, modifier = Modifier.fillMaxWidth() ) } else { // Show search results when there's a query } @Composable private fun SearchResultsContent( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState ) { SearchResults( viewModel = hiltViewModel(), geocodeResults = deduplicateSearchResults(viewModel.geocodeResults.value), onPlaceSelected = { place -> // Update the appropriate place based on which field is focused updatePlaceForField(viewModel, fieldFocusState, place) }, modifier = Modifier.fillMaxWidth() ) } private fun updatePlaceForField( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, place: Place ) { if (fieldFocusState == FieldFocusState.FROM) { viewModel.updateFromPlace(place) } else { viewModel.updateToPlace(place) } // Clear focus state after selection fieldFocusState = FieldFocusState.NONE }, modifier = Modifier.fillMaxWidth() ) } private fun handleMyLocationSelected( viewModel: DirectionsViewModel, fieldFocusState: FieldFocusState, hasLocationPermission: Boolean, onRequestLocationPermission: () -> Unit, coroutineScope: CoroutineScope ): () -> Unit = { if (hasLocationPermission) { coroutineScope.launch { val myLocationPlace = viewModel.getCurrentLocationAsPlace() myLocationPlace?.let { place -> updatePlaceForField(viewModel, fieldFocusState, place) } } } else { onRequestLocationPermission() } } Loading Loading @@ -836,19 +972,47 @@ private fun FerrostarRouteResults( } } // Profile selection dialog if (showProfileDialog) { // Dialogs ProfileSelectionDialog( showDialog = showProfileDialog, onDismiss = { showProfileDialog = false }, selectedProfile = selectedProfile, availableProfiles = availableProfiles, onProfileSelected = { profile -> viewModel.selectRoutingProfile(profile) showProfileDialog = false } ) NotificationRequestDialog( showDialog = showNotificationDialog, onDismiss = { showNotificationDialog = false }, onConfirm = { showNotificationDialog = false pendingNavigation = true onRequestNotificationPermission() } ) } @Composable private fun ProfileSelectionDialog( showDialog: Boolean, onDismiss: () -> Unit, selectedProfile: RoutingProfile?, availableProfiles: List<RoutingProfile>, onProfileSelected: (RoutingProfile?) -> Unit ) { if (showDialog) { AlertDialog( onDismissRequest = { showProfileDialog = false }, onDismissRequest = onDismiss, title = { Text(stringResource(string.select_routing_profile)) }, text = { Column { // Default option TextButton( onClick = { viewModel.selectRoutingProfile(null) showProfileDialog = false }, modifier = Modifier.fillMaxWidth() onClick = { onProfileSelected(null) }, modifier = Modifier.fillMaxWidth() ) { Text( text = stringResource(string.default_profile), Loading @@ -859,10 +1023,8 @@ private fun FerrostarRouteResults( // Custom profiles availableProfiles.forEach { profile -> TextButton( onClick = { viewModel.selectRoutingProfile(profile) showProfileDialog = false }, modifier = Modifier.fillMaxWidth() onClick = { onProfileSelected(profile) }, modifier = Modifier.fillMaxWidth() ) { Text( text = profile.name, Loading @@ -873,29 +1035,31 @@ private fun FerrostarRouteResults( } }, confirmButton = { TextButton(onClick = { showProfileDialog = false }) { TextButton(onClick = onDismiss) { Text(stringResource(string.cancel_change_routing_profile)) } }) } } // Notification permission dialog if (showNotificationDialog) { @Composable private fun NotificationRequestDialog( showDialog: Boolean, onDismiss: () -> Unit, onConfirm: () -> Unit ) { if (showDialog) { AlertDialog( onDismissRequest = { showNotificationDialog = false }, onDismissRequest = onDismiss, title = { Text(stringResource(string.notification_ask_title)) }, text = { Text(stringResource(string.notification_ask_body)) }, confirmButton = { TextButton(onClick = { showNotificationDialog = false pendingNavigation = true onRequestNotificationPermission() }) { TextButton(onClick = onConfirm) { Text(stringResource(string.got_it)) } }, dismissButton = { TextButton(onClick = { showNotificationDialog = false }) { TextButton(onClick = onDismiss) { Text(stringResource(string.cancel)) } }) Loading