mirror of
https://github.com/AdrianKuta/android-challange-adrian-kuta.git
synced 2025-07-01 21:17:59 +02:00
Refactor: Introduce SearchForm and PassengersOptions composables
This commit refactors the HomeScreen by extracting the search form logic into a new `SearchForm` composable and the passenger selection into a `PassengersOptions` composable. Key changes: - Created `PassengersOptions.kt` with a composable function to handle adult, teen, and child passenger counts. - Created `SearchForm.kt` with a composable function that encapsulates airport selection, date picking, passenger options, and the search button. - Updated `HomeScreen.kt`: - Replaced the previous inline form layout with the new `SearchForm` composable. - Made the screen content vertically scrollable using `Box` and `verticalScroll`. - Passed a new `onSearch` lambda to the `HomeScreenContent`. - Updated `HomeScreenViewModel.kt`: - Modified `updateAdultCount`, `updateTeenCount`, and `updateChildCount` to accept the new count directly instead of a diff. - Added a `search()` function (currently logs to Timber). - Ensured child count does not exceed adult count when adult count changes. - Updated `Counter.kt` in `ui/sharedui`: - Changed `onCountChange` parameter to `onValueChange` which now receives the new absolute value. - Added a `maxVal` parameter to limit the maximum value of the counter.
This commit is contained in:
@ -1,16 +1,10 @@
|
||||
package dev.adriankuta.flights.ui.home
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -25,9 +19,7 @@ import dev.adriankuta.flights.domain.types.MacCity
|
||||
import dev.adriankuta.flights.domain.types.Region
|
||||
import dev.adriankuta.flights.ui.designsystem.theme.FlightsTheme
|
||||
import dev.adriankuta.flights.ui.designsystem.theme.PreviewDevices
|
||||
import dev.adriankuta.flights.ui.home.components.AirportDropdown
|
||||
import dev.adriankuta.flights.ui.home.components.DatePicker
|
||||
import dev.adriankuta.flights.ui.sharedui.Counter
|
||||
import dev.adriankuta.flights.ui.home.components.SearchForm
|
||||
import java.time.LocalDate
|
||||
|
||||
@Composable
|
||||
@ -44,6 +36,7 @@ internal fun HomeScreen(
|
||||
onAdultCountChange = viewModel::updateAdultCount,
|
||||
onTeenCountChange = viewModel::updateTeenCount,
|
||||
onChildCountChange = viewModel::updateChildCount,
|
||||
onSearch = viewModel::search,
|
||||
)
|
||||
}
|
||||
|
||||
@ -56,10 +49,16 @@ private fun HomeScreen(
|
||||
onAdultCountChange: (Int) -> Unit,
|
||||
onTeenCountChange: (Int) -> Unit,
|
||||
onChildCountChange: (Int) -> Unit,
|
||||
onSearch: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.padding(16.dp),
|
||||
val scrollState = rememberScrollState()
|
||||
Box(
|
||||
modifier = modifier
|
||||
.padding(16.dp)
|
||||
.verticalScroll(
|
||||
state = scrollState,
|
||||
),
|
||||
) {
|
||||
when (uiState) {
|
||||
is HomeUiState.Error -> Text("Error")
|
||||
@ -72,90 +71,12 @@ private fun HomeScreen(
|
||||
onAdultCountChange = onAdultCountChange,
|
||||
onTeenCountChange = onTeenCountChange,
|
||||
onChildCountChange = onChildCountChange,
|
||||
onSearch = onSearch,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.SearchForm(
|
||||
uiState: HomeUiState.Success,
|
||||
onOriginAirportSelect: (AirportInfo) -> Unit,
|
||||
onDestinationAirportSelect: (AirportInfo) -> Unit,
|
||||
onDateSelect: (LocalDate) -> Unit,
|
||||
onAdultCountChange: (Int) -> Unit,
|
||||
onTeenCountChange: (Int) -> Unit,
|
||||
onChildCountChange: (Int) -> Unit,
|
||||
) {
|
||||
AirportDropdown(
|
||||
label = "Origin Airport",
|
||||
airports = uiState.originAirports,
|
||||
selectedAirport = uiState.selectedOriginAirport,
|
||||
onAirportSelect = onOriginAirportSelect,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
AirportDropdown(
|
||||
label = "Destination Airport",
|
||||
airports = uiState.destinationAirports,
|
||||
selectedAirport = uiState.selectedDestinationAirport,
|
||||
onAirportSelect = onDestinationAirportSelect,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = uiState.selectedOriginAirport != null,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
DatePicker(
|
||||
label = "Departure Date",
|
||||
selectedDate = uiState.selectedDate,
|
||||
onDateSelect = onDateSelect,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = uiState.selectedDestinationAirport != null,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text("Passengers")
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(intrinsicSize = IntrinsicSize.Min),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Counter(
|
||||
value = uiState.passengers.adultCount,
|
||||
onCountChange = onAdultCountChange,
|
||||
label = "Adults",
|
||||
modifier = Modifier.weight(1f),
|
||||
minVal = 1,
|
||||
)
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
Counter(
|
||||
value = uiState.passengers.teenCount,
|
||||
onCountChange = onTeenCountChange,
|
||||
label = "Teens",
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
Counter(
|
||||
value = uiState.passengers.childCount,
|
||||
onCountChange = onChildCountChange,
|
||||
label = "Children",
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewDevices
|
||||
@Composable
|
||||
private fun HomeScreenLoadingPreview() {
|
||||
@ -168,6 +89,7 @@ private fun HomeScreenLoadingPreview() {
|
||||
onAdultCountChange = {},
|
||||
onTeenCountChange = {},
|
||||
onChildCountChange = {},
|
||||
onSearch = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -222,6 +144,7 @@ private fun HomeScreenSuccessPreview() {
|
||||
onAdultCountChange = {},
|
||||
onTeenCountChange = {},
|
||||
onChildCountChange = {},
|
||||
onSearch = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,13 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class HomeScreenViewModel @Inject constructor(
|
||||
private val observeAirportsUseCase: ObserveAirportsUseCase,
|
||||
observeAirportsUseCase: ObserveAirportsUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
private val selectedOriginAirport = MutableStateFlow<AirportInfo?>(null)
|
||||
@ -71,19 +72,21 @@ class HomeScreenViewModel @Inject constructor(
|
||||
selectedDate.value = date
|
||||
}
|
||||
|
||||
fun updateAdultCount(diff: Int) {
|
||||
val newCount = adultCount.value + diff
|
||||
adultCount.value = newCount
|
||||
fun updateAdultCount(count: Int) {
|
||||
adultCount.value = count
|
||||
childCount.value = childCount.value.coerceAtMost(count)
|
||||
}
|
||||
|
||||
fun updateTeenCount(diff: Int) {
|
||||
val newCount = teenCount.value + diff
|
||||
teenCount.value = newCount
|
||||
fun updateTeenCount(count: Int) {
|
||||
teenCount.value = count
|
||||
}
|
||||
|
||||
fun updateChildCount(diff: Int) {
|
||||
val newCount = childCount.value + diff
|
||||
childCount.value = newCount
|
||||
fun updateChildCount(count: Int) {
|
||||
childCount.value = count
|
||||
}
|
||||
|
||||
fun search() {
|
||||
Timber.d("Search clicked")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,55 @@
|
||||
package dev.adriankuta.flights.ui.home.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.adriankuta.flights.ui.home.HomeUiState
|
||||
import dev.adriankuta.flights.ui.sharedui.Counter
|
||||
|
||||
@Composable
|
||||
internal fun PassengersOptions(
|
||||
uiState: HomeUiState.Success,
|
||||
onAdultCountChange: (Int) -> Unit,
|
||||
onTeenCountChange: (Int) -> Unit,
|
||||
onChildCountChange: (Int) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(intrinsicSize = IntrinsicSize.Min),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Counter(
|
||||
value = uiState.passengers.adultCount,
|
||||
onValueChange = onAdultCountChange,
|
||||
label = "Adults",
|
||||
modifier = Modifier.weight(1f),
|
||||
minVal = 1,
|
||||
)
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
Counter(
|
||||
value = uiState.passengers.teenCount,
|
||||
onValueChange = onTeenCountChange,
|
||||
label = "Teens",
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
Counter(
|
||||
value = uiState.passengers.childCount,
|
||||
onValueChange = onChildCountChange,
|
||||
label = "Children",
|
||||
modifier = Modifier.weight(1f),
|
||||
maxVal = uiState.passengers.adultCount,
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package dev.adriankuta.flights.ui.home.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.adriankuta.flights.domain.types.AirportInfo
|
||||
import dev.adriankuta.flights.ui.home.HomeUiState
|
||||
import java.time.LocalDate
|
||||
|
||||
@Composable
|
||||
internal fun SearchForm(
|
||||
uiState: HomeUiState.Success,
|
||||
onOriginAirportSelect: (AirportInfo) -> Unit,
|
||||
onDestinationAirportSelect: (AirportInfo) -> Unit,
|
||||
onDateSelect: (LocalDate) -> Unit,
|
||||
onAdultCountChange: (Int) -> Unit,
|
||||
onTeenCountChange: (Int) -> Unit,
|
||||
onChildCountChange: (Int) -> Unit,
|
||||
onSearch: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier) {
|
||||
AirportDropdown(
|
||||
label = "Origin Airport",
|
||||
airports = uiState.originAirports,
|
||||
selectedAirport = uiState.selectedOriginAirport,
|
||||
onAirportSelect = onOriginAirportSelect,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
AirportDropdown(
|
||||
label = "Destination Airport",
|
||||
airports = uiState.destinationAirports,
|
||||
selectedAirport = uiState.selectedDestinationAirport,
|
||||
onAirportSelect = onDestinationAirportSelect,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = uiState.selectedOriginAirport != null,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
DatePicker(
|
||||
label = "Departure Date",
|
||||
selectedDate = uiState.selectedDate,
|
||||
onDateSelect = onDateSelect,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = uiState.selectedDestinationAirport != null,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text("Passengers")
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
PassengersOptions(uiState, onAdultCountChange, onTeenCountChange, onChildCountChange)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Button(
|
||||
onClick = onSearch,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = uiState.selectedOriginAirport != null && uiState.selectedDestinationAirport != null,
|
||||
) {
|
||||
Text("Search Flights")
|
||||
}
|
||||
}
|
||||
}
|
@ -34,10 +34,11 @@ import dev.adriankuta.flights.ui.designsystem.theme.FlightsTheme
|
||||
@Composable
|
||||
fun Counter(
|
||||
value: Int,
|
||||
onCountChange: (diff: Int) -> Unit,
|
||||
onValueChange: (newValue: Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
label: String? = null,
|
||||
minVal: Int = 0,
|
||||
maxVal: Int = Int.MAX_VALUE,
|
||||
) {
|
||||
val view = LocalView.current
|
||||
|
||||
@ -84,7 +85,7 @@ fun Counter(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
onCountChange(-1)
|
||||
onValueChange(value - 1)
|
||||
},
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
enabled = value > minVal,
|
||||
@ -99,10 +100,10 @@ fun Counter(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
onCountChange(1)
|
||||
onValueChange(value + 1)
|
||||
},
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
enabled = value < 20,
|
||||
enabled = value < maxVal,
|
||||
) {
|
||||
Text(
|
||||
text = "+",
|
||||
@ -124,7 +125,8 @@ private fun CounterPreview() {
|
||||
Counter(
|
||||
value = tapCounter,
|
||||
label = "Taps",
|
||||
onCountChange = { tapCounter += it },
|
||||
onValueChange = { tapCounter += it },
|
||||
maxVal = 20,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user