mirror of
				https://github.com/AdrianKuta/android-challange-adrian-kuta.git
				synced 2025-10-31 05:43:40 +01:00 
			
		
		
		
	feat: Implement airport search and selection in StationsScreen
This commit introduces search functionality and airport selection to the StationsScreen. Users can now search for airports and select an origin airport, which will be displayed in a compact view. Key changes: - Added a search bar (`OutlinedTextField`) to `StationsScreen` to filter the list of airports. - Implemented `searchQuery` StateFlow in `StationsScreenViewModel` to hold the current search term. - Updated `originAirportsUiState` to filter airports based on the `searchQuery`. - Added `selectedOriginAirport` StateFlow in `StationsScreenViewModel` to manage the selected airport. - Introduced `AirportInfoCompact` composable to display the selected origin airport. - Implemented `onAirportSelected` and `clearSelectedAirport` functions in `StationsScreenViewModel` to handle airport selection and clearing. - Added `BackHandler` to clear the selected airport when the back button is pressed. - Made `AirportInfoItem` clickable to trigger airport selection. - Added string resource for "Search airports" placeholder. - Added Detekt configuration files for `domain/stations` and `ui/stations` modules. - Added Lint baseline files for `domain/stations` and `ui/stations` modules. - Used `key` and `animateItem` for better performance and animations in `LazyColumn`.
This commit is contained in:
		
							
								
								
									
										10
									
								
								domain/stations/config/detekt/detekt.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								domain/stations/config/detekt/detekt.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | # Deviations from defaults | ||||||
|  | formatting: | ||||||
|  |   TrailingCommaOnCallSite: | ||||||
|  |     active: true | ||||||
|  |     autoCorrect: true | ||||||
|  |     useTrailingCommaOnCallSite: true | ||||||
|  |   TrailingCommaOnDeclarationSite: | ||||||
|  |     active: true | ||||||
|  |     autoCorrect: true | ||||||
|  |     useTrailingCommaOnDeclarationSite: true | ||||||
							
								
								
									
										11
									
								
								domain/stations/lint-baseline.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								domain/stations/lint-baseline.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <issues format="6" by="lint 8.10.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.10.1)" variant="all" version="8.10.1"> | ||||||
|  |  | ||||||
|  |     <issue | ||||||
|  |         id="Aligned16KB" | ||||||
|  |         message="The native library `arm64-v8a/libmockkjvmtiagent.so` (from `io.mockk:mockk-agent-android:1.14.2`) is not 16 KB aligned"> | ||||||
|  |         <location | ||||||
|  |             file="$GRADLE_USER_HOME/caches/8.11.1/transforms/60089ed8c197d152569cb75de3649edc/transformed/mockk-agent-android-1.14.2/jni/arm64-v8a/libmockkjvmtiagent.so"/> | ||||||
|  |     </issue> | ||||||
|  |  | ||||||
|  | </issues> | ||||||
							
								
								
									
										33
									
								
								ui/stations/config/detekt/detekt.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								ui/stations/config/detekt/detekt.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | # Exceptions for compose. See https://detekt.dev/docs/introduction/compose | ||||||
|  | naming: | ||||||
|  |   FunctionNaming: | ||||||
|  |     functionPattern: '[a-zA-Z][a-zA-Z0-9]*' | ||||||
|  |  | ||||||
|  |   TopLevelPropertyNaming: | ||||||
|  |     constantPattern: '[A-Z][A-Za-z0-9]*' | ||||||
|  |  | ||||||
|  | complexity: | ||||||
|  |   LongParameterList: | ||||||
|  |     ignoreAnnotated: ['Composable'] | ||||||
|  |   TooManyFunctions: | ||||||
|  |     ignoreAnnotatedFunctions: ['Preview'] | ||||||
|  |  | ||||||
|  | style: | ||||||
|  |   MagicNumber: | ||||||
|  |     ignorePropertyDeclaration: true | ||||||
|  |     ignoreCompanionObjectPropertyDeclaration: true | ||||||
|  |     ignoreAnnotated: ['Composable'] | ||||||
|  |  | ||||||
|  |   UnusedPrivateMember: | ||||||
|  |     ignoreAnnotated: ['Composable'] | ||||||
|  |  | ||||||
|  | # Deviations from defaults | ||||||
|  | formatting: | ||||||
|  |   TrailingCommaOnCallSite: | ||||||
|  |     active: true | ||||||
|  |     autoCorrect: true | ||||||
|  |     useTrailingCommaOnCallSite: true | ||||||
|  |   TrailingCommaOnDeclarationSite: | ||||||
|  |     active: true | ||||||
|  |     autoCorrect: true | ||||||
|  |     useTrailingCommaOnDeclarationSite: true | ||||||
							
								
								
									
										18
									
								
								ui/stations/lint-baseline.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ui/stations/lint-baseline.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <issues format="6" by="lint 8.10.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.10.1)" variant="all" version="8.10.1"> | ||||||
|  |  | ||||||
|  |     <issue | ||||||
|  |         id="Aligned16KB" | ||||||
|  |         message="The native library `arm64-v8a/libmockkjvmtiagent.so` (from `io.mockk:mockk-agent-android:1.14.2`) is not 16 KB aligned"> | ||||||
|  |         <location | ||||||
|  |             file="$GRADLE_USER_HOME/caches/8.11.1/transforms/60089ed8c197d152569cb75de3649edc/transformed/mockk-agent-android-1.14.2/jni/arm64-v8a/libmockkjvmtiagent.so"/> | ||||||
|  |     </issue> | ||||||
|  |  | ||||||
|  |     <issue | ||||||
|  |         id="Aligned16KB" | ||||||
|  |         message="The native library `arm64-v8a/libmockkjvmtiagent.so` (from `io.mockk:mockk-agent-android:1.14.2`) is not 16 KB aligned"> | ||||||
|  |         <location | ||||||
|  |             file="$GRADLE_USER_HOME/caches/8.11.1/transforms/60089ed8c197d152569cb75de3649edc/transformed/mockk-agent-android-1.14.2/jni/arm64-v8a/libmockkjvmtiagent.so"/> | ||||||
|  |     </issue> | ||||||
|  |  | ||||||
|  | </issues> | ||||||
| @@ -1,53 +1,125 @@ | |||||||
| package dev.adriankuta.flights.ui.home | package dev.adriankuta.flights.ui.home | ||||||
|  |  | ||||||
|  | import androidx.activity.compose.BackHandler | ||||||
|  | import androidx.compose.animation.AnimatedVisibility | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
| import androidx.compose.foundation.lazy.LazyColumn | import androidx.compose.foundation.lazy.LazyColumn | ||||||
| import androidx.compose.foundation.lazy.LazyListScope | import androidx.compose.foundation.lazy.LazyListScope | ||||||
| import androidx.compose.foundation.lazy.items | import androidx.compose.foundation.lazy.items | ||||||
|  | import androidx.compose.material.icons.Icons | ||||||
|  | import androidx.compose.material.icons.filled.Search | ||||||
|  | import androidx.compose.material3.ExperimentalMaterial3Api | ||||||
|  | import androidx.compose.material3.Icon | ||||||
|  | import androidx.compose.material3.OutlinedTextField | ||||||
| import androidx.compose.material3.Text | import androidx.compose.material3.Text | ||||||
| import androidx.compose.runtime.Composable | import androidx.compose.runtime.Composable | ||||||
|  | import androidx.compose.runtime.LaunchedEffect | ||||||
| import androidx.compose.runtime.getValue | import androidx.compose.runtime.getValue | ||||||
|  | import androidx.compose.runtime.key | ||||||
|  | import androidx.compose.runtime.mutableStateOf | ||||||
|  | import androidx.compose.runtime.remember | ||||||
|  | import androidx.compose.runtime.setValue | ||||||
| import androidx.compose.ui.Modifier | import androidx.compose.ui.Modifier | ||||||
|  | import androidx.compose.ui.res.stringResource | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
| import androidx.hilt.navigation.compose.hiltViewModel | import androidx.hilt.navigation.compose.hiltViewModel | ||||||
| import androidx.lifecycle.compose.collectAsStateWithLifecycle | import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||||||
| import dev.adriankuta.flights.domain.types.AirportInfo | import dev.adriankuta.flights.domain.types.AirportInfo | ||||||
| import dev.adriankuta.flights.domain.types.Country | import dev.adriankuta.flights.domain.types.Country | ||||||
|  | import dev.adriankuta.flights.ui.home.components.AirportInfoCompact | ||||||
| import dev.adriankuta.flights.ui.home.components.AirportInfoItem | import dev.adriankuta.flights.ui.home.components.AirportInfoItem | ||||||
| import dev.adriankuta.flights.ui.home.components.CountryItem | import dev.adriankuta.flights.ui.home.components.CountryItem | ||||||
|  | import dev.adriankuta.flights.ui.stations.R | ||||||
|  |  | ||||||
| @Composable | @Composable | ||||||
| internal fun StationsScreen( | internal fun StationsScreen( | ||||||
|     stationScreenViewModel: StationsScreenViewModel = hiltViewModel(), |     stationScreenViewModel: StationsScreenViewModel = hiltViewModel(), | ||||||
| ) { | ) { | ||||||
|     val originAirports by stationScreenViewModel.originAirports.collectAsStateWithLifecycle() |     val originAirports by stationScreenViewModel.originAirports.collectAsStateWithLifecycle() | ||||||
|  |     val searchQuery by stationScreenViewModel.searchQuery.collectAsStateWithLifecycle() | ||||||
|  |     val selectedOriginAirport by stationScreenViewModel.selectedOriginAirport.collectAsStateWithLifecycle() | ||||||
|  |  | ||||||
|  |     BackHandler(selectedOriginAirport != null) { | ||||||
|  |         stationScreenViewModel.clearSelectedAirport() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     StationsScreen( |     StationsScreen( | ||||||
|         originAirports = originAirports, |         originAirports = originAirports, | ||||||
|  |         searchQuery = searchQuery, | ||||||
|  |         selectedOriginAirport = selectedOriginAirport, | ||||||
|  |         onSearchQueryChang = stationScreenViewModel::onSearchQueryChanged, | ||||||
|  |         onAirportSelect = stationScreenViewModel::onAirportSelected, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @OptIn(ExperimentalMaterial3Api::class) | ||||||
| @Composable | @Composable | ||||||
| private fun StationsScreen( | private fun StationsScreen( | ||||||
|     originAirports: OriginAirportsUiState, |     originAirports: OriginAirportsUiState, | ||||||
|  |     searchQuery: String, | ||||||
|  |     selectedOriginAirport: AirportInfo?, | ||||||
|  |     onSearchQueryChang: (String) -> Unit, | ||||||
|  |     onAirportSelect: (AirportInfo) -> Unit, | ||||||
| ) { | ) { | ||||||
|     LazyColumn( |     Column( | ||||||
|         modifier = Modifier.fillMaxWidth(), |         modifier = Modifier.fillMaxWidth(), | ||||||
|     ) { |     ) { | ||||||
|         when (originAirports) { |         AnimatedVisibility(selectedOriginAirport != null) { | ||||||
|             is OriginAirportsUiState.Error -> item { Text("Error") } |             var airportInfo by remember { mutableStateOf(selectedOriginAirport!!) } | ||||||
|             is OriginAirportsUiState.Loading -> item { Text("Loading") } |             LaunchedEffect(selectedOriginAirport) { | ||||||
|             is OriginAirportsUiState.Success -> groupedAirports(originAirports.groupedAirports) |                 selectedOriginAirport?.let { airportInfo = it } | ||||||
|  |             } | ||||||
|  |             AirportInfoCompact( | ||||||
|  |                 data = airportInfo, | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         OutlinedTextField( | ||||||
|  |             value = searchQuery, | ||||||
|  |             onValueChange = onSearchQueryChang, | ||||||
|  |             modifier = Modifier | ||||||
|  |                 .fillMaxWidth() | ||||||
|  |                 .padding(16.dp), | ||||||
|  |             placeholder = { Text(text = stringResource(R.string.search_airports)) }, | ||||||
|  |             leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, | ||||||
|  |             singleLine = true, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         LazyColumn( | ||||||
|  |             modifier = Modifier.fillMaxWidth(), | ||||||
|  |         ) { | ||||||
|  |             when (originAirports) { | ||||||
|  |                 is OriginAirportsUiState.Error -> item { Text("Error") } | ||||||
|  |                 is OriginAirportsUiState.Loading -> item { Text("Loading") } | ||||||
|  |                 is OriginAirportsUiState.Success -> groupedAirports( | ||||||
|  |                     data = originAirports.groupedAirports, | ||||||
|  |                     onAirportSelected = onAirportSelect, | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| private fun LazyListScope.groupedAirports( | private fun LazyListScope.groupedAirports( | ||||||
|     data: Map<Country, List<AirportInfo>>, |     data: Map<Country, List<AirportInfo>>, | ||||||
|  |     onAirportSelected: (AirportInfo) -> Unit, | ||||||
| ) { | ) { | ||||||
|     data.keys.forEach { country -> |     data.keys.forEach { country -> | ||||||
|         stickyHeader { CountryItem(country) } |         stickyHeader { | ||||||
|  |             key(country.code) { | ||||||
|  |                 CountryItem(data = country, modifier = Modifier.animateItem()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         items(data[country]!!) { airport -> |         items(data[country]!!) { airport -> | ||||||
|             AirportInfoItem(airport) |             key(airport.code) { | ||||||
|  |                 AirportInfoItem( | ||||||
|  |                     data = airport, | ||||||
|  |                     modifier = Modifier.animateItem(), | ||||||
|  |                     onClick = onAirportSelected, | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,8 +9,10 @@ import dev.adriankuta.flights.domain.stations.ObserveAirportsGroupedByCountry | |||||||
| import dev.adriankuta.flights.domain.types.AirportInfo | import dev.adriankuta.flights.domain.types.AirportInfo | ||||||
| import dev.adriankuta.flights.domain.types.Country | import dev.adriankuta.flights.domain.types.Country | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
|  | import kotlinx.coroutines.flow.MutableStateFlow | ||||||
| import kotlinx.coroutines.flow.SharingStarted | import kotlinx.coroutines.flow.SharingStarted | ||||||
| import kotlinx.coroutines.flow.map | import kotlinx.coroutines.flow.StateFlow | ||||||
|  | import kotlinx.coroutines.flow.combine | ||||||
| import kotlinx.coroutines.flow.stateIn | import kotlinx.coroutines.flow.stateIn | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
|  |  | ||||||
| @@ -19,8 +21,27 @@ internal class StationsScreenViewModel @Inject constructor( | |||||||
|     observeAirportsGroupedByCountry: ObserveAirportsGroupedByCountry, |     observeAirportsGroupedByCountry: ObserveAirportsGroupedByCountry, | ||||||
| ) : ViewModel() { | ) : ViewModel() { | ||||||
|  |  | ||||||
|  |     private val _searchQuery = MutableStateFlow("") | ||||||
|  |     val searchQuery: StateFlow<String> = _searchQuery | ||||||
|  |  | ||||||
|  |     private val _selectedOriginAirport = MutableStateFlow<AirportInfo?>(null) | ||||||
|  |     val selectedOriginAirport: StateFlow<AirportInfo?> = _selectedOriginAirport | ||||||
|  |  | ||||||
|  |     fun onSearchQueryChanged(query: String) { | ||||||
|  |         _searchQuery.value = query | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun onAirportSelected(airport: AirportInfo) { | ||||||
|  |         _selectedOriginAirport.value = airport | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun clearSelectedAirport() { | ||||||
|  |         _selectedOriginAirport.value = null | ||||||
|  |     } | ||||||
|  |  | ||||||
|     val originAirports = originAirportsUiState( |     val originAirports = originAirportsUiState( | ||||||
|         useCase = observeAirportsGroupedByCountry, |         useCase = observeAirportsGroupedByCountry, | ||||||
|  |         searchQuery = searchQuery, | ||||||
|     ).stateIn( |     ).stateIn( | ||||||
|         scope = viewModelScope, |         scope = viewModelScope, | ||||||
|         started = SharingStarted.WhileSubscribed(5_000), |         started = SharingStarted.WhileSubscribed(5_000), | ||||||
| @@ -30,16 +51,33 @@ internal class StationsScreenViewModel @Inject constructor( | |||||||
|  |  | ||||||
| private fun originAirportsUiState( | private fun originAirportsUiState( | ||||||
|     useCase: ObserveAirportsGroupedByCountry, |     useCase: ObserveAirportsGroupedByCountry, | ||||||
|  |     searchQuery: StateFlow<String>, | ||||||
| ): Flow<OriginAirportsUiState> { | ): Flow<OriginAirportsUiState> { | ||||||
|     return useCase() |     return combine( | ||||||
|         .asResult() |         useCase().asResult(), | ||||||
|         .map { result -> |         searchQuery, | ||||||
|             when (result) { |     ) { result, query -> | ||||||
|                 is Result.Error -> OriginAirportsUiState.Error(result.exception) |         when (result) { | ||||||
|                 is Result.Loading -> OriginAirportsUiState.Loading |             is Result.Error -> OriginAirportsUiState.Error(result.exception) | ||||||
|                 is Result.Success -> OriginAirportsUiState.Success(result.data.orEmpty()) |             is Result.Loading -> OriginAirportsUiState.Loading | ||||||
|  |             is Result.Success -> { | ||||||
|  |                 val airports = result.data.orEmpty() | ||||||
|  |                 if (query.isBlank()) { | ||||||
|  |                     OriginAirportsUiState.Success(airports) | ||||||
|  |                 } else { | ||||||
|  |                     val filteredAirports = airports.mapValues { (_, airportList) -> | ||||||
|  |                         airportList.filter { airport -> | ||||||
|  |                             airport.name.contains(query, ignoreCase = true) || | ||||||
|  |                                 airport.code.contains(query, ignoreCase = true) || | ||||||
|  |                                 airport.city.name.contains(query, ignoreCase = true) || | ||||||
|  |                                 airport.country.name.contains(query, ignoreCase = true) | ||||||
|  |                         } | ||||||
|  |                     }.filterValues { it.isNotEmpty() } | ||||||
|  |                     OriginAirportsUiState.Success(filteredAirports) | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| internal sealed interface OriginAirportsUiState { | internal sealed interface OriginAirportsUiState { | ||||||
|   | |||||||
| @@ -0,0 +1,60 @@ | |||||||
|  | package dev.adriankuta.flights.ui.home.components | ||||||
|  |  | ||||||
|  | import androidx.compose.foundation.layout.Column | ||||||
|  | import androidx.compose.foundation.layout.Row | ||||||
|  | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | import androidx.compose.foundation.layout.padding | ||||||
|  | import androidx.compose.material3.Card | ||||||
|  | import androidx.compose.material3.CardDefaults | ||||||
|  | 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.text.font.FontWeight | ||||||
|  | import androidx.compose.ui.unit.dp | ||||||
|  | import dev.adriankuta.flights.domain.types.AirportInfo | ||||||
|  |  | ||||||
|  | @Composable | ||||||
|  | internal fun AirportInfoCompact( | ||||||
|  |     data: AirportInfo, | ||||||
|  |     modifier: Modifier = Modifier, | ||||||
|  | ) { | ||||||
|  |     Card( | ||||||
|  |         modifier = modifier | ||||||
|  |             .fillMaxWidth() | ||||||
|  |             .padding(horizontal = 16.dp, vertical = 8.dp), | ||||||
|  |         elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), | ||||||
|  |         colors = CardDefaults.cardColors( | ||||||
|  |             containerColor = MaterialTheme.colorScheme.primaryContainer, | ||||||
|  |         ), | ||||||
|  |     ) { | ||||||
|  |         Row( | ||||||
|  |             modifier = Modifier | ||||||
|  |                 .fillMaxWidth() | ||||||
|  |                 .padding(12.dp), | ||||||
|  |             verticalAlignment = Alignment.CenterVertically, | ||||||
|  |         ) { | ||||||
|  |             Column(modifier = Modifier.weight(1f)) { | ||||||
|  |                 Text( | ||||||
|  |                     text = data.name, | ||||||
|  |                     style = MaterialTheme.typography.titleMedium, | ||||||
|  |                     fontWeight = FontWeight.Bold, | ||||||
|  |                 ) | ||||||
|  |                 Text( | ||||||
|  |                     text = "${data.code} • ${data.city.name}", | ||||||
|  |                     style = MaterialTheme.typography.bodyMedium, | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             if (data.isBase) { | ||||||
|  |                 Text( | ||||||
|  |                     text = "BASE", | ||||||
|  |                     style = MaterialTheme.typography.labelMedium, | ||||||
|  |                     color = MaterialTheme.colorScheme.primary, | ||||||
|  |                     fontWeight = FontWeight.Bold, | ||||||
|  |                     modifier = Modifier.padding(start = 8.dp), | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -19,12 +19,14 @@ import dev.adriankuta.flights.domain.types.AirportInfo | |||||||
| internal fun AirportInfoItem( | internal fun AirportInfoItem( | ||||||
|     data: AirportInfo, |     data: AirportInfo, | ||||||
|     modifier: Modifier = Modifier, |     modifier: Modifier = Modifier, | ||||||
|  |     onClick: (AirportInfo) -> Unit = {}, | ||||||
| ) { | ) { | ||||||
|     Card( |     Card( | ||||||
|         modifier = modifier |         modifier = modifier | ||||||
|             .fillMaxWidth() |             .fillMaxWidth() | ||||||
|             .padding(horizontal = 16.dp, vertical = 4.dp), |             .padding(horizontal = 16.dp, vertical = 4.dp), | ||||||
|         elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), |         elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), | ||||||
|  |         onClick = { onClick(data) }, | ||||||
|     ) { |     ) { | ||||||
|         Column( |         Column( | ||||||
|             modifier = Modifier |             modifier = Modifier | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <resources> | <resources> | ||||||
|     <string name="stations_screen_title">Stations</string> |     <string name="stations_screen_title">Stations</string> | ||||||
|  |     <string name="search_airports">Search airports</string> | ||||||
| </resources> | </resources> | ||||||
		Reference in New Issue
	
	Block a user