-
Notifications
You must be signed in to change notification settings - Fork 136
[Bookings] Type filter #14858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bookings] Type filter #14858
Changes from all commits
1e482a7
13b6c07
0dc120f
8236942
a249bb6
640ca4d
3b08b52
4db1805
2291267
d77fed5
7976b4c
60c9f47
59ba3a9
e1bee75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.woocommerce.android.ui.bookings.filter | ||
|
|
||
| import androidx.annotation.StringRes | ||
|
|
||
| /** | ||
| * UI model simple filter item | ||
| */ | ||
| data class BookingFilterListItem( | ||
| @StringRes val title: Int, | ||
| val value: String? = null, | ||
| val onClick: () -> Unit = {} | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ import androidx.navigation.compose.NavHost | |
| import androidx.navigation.compose.composable | ||
| import androidx.navigation.compose.rememberNavController | ||
| import com.woocommerce.android.R | ||
| import com.woocommerce.android.ui.bookings.filter.type.BookingTypeFilterRoute | ||
| import com.woocommerce.android.ui.compose.component.Toolbar | ||
| import com.woocommerce.android.ui.compose.component.WCColoredButton | ||
| import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews | ||
|
|
@@ -122,7 +123,9 @@ private fun FiltersNavHost( | |
| TODO() | ||
| } | ||
| composable(BookingFilterPage.BookingType.route) { | ||
| TODO() | ||
| BookingTypeFilterRoute(initialType = state.currentBookingType) { type -> | ||
| state.onUpdateFilterOption(type) | ||
| } | ||
|
Comment on lines
-125
to
+128
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit confused with this new filter "BookingType" and the one added below in line 133 "Service/Event". I think we are adding the same filter twice. Might be worth double checking with Wagner or with folks from ciab-bookings how do they want to refer to this filter. Currently in desktop experience they are referring to it as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| composable(BookingFilterPage.Customer.route) { | ||
| TODO() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package com.woocommerce.android.ui.bookings.filter | ||
|
|
||
| import androidx.compose.foundation.clickable | ||
| import androidx.compose.foundation.layout.Column | ||
| import androidx.compose.foundation.layout.Row | ||
| import androidx.compose.foundation.layout.defaultMinSize | ||
| import androidx.compose.foundation.layout.fillMaxWidth | ||
| import androidx.compose.foundation.layout.padding | ||
| import androidx.compose.foundation.layout.size | ||
| import androidx.compose.foundation.lazy.LazyColumn | ||
| import androidx.compose.foundation.lazy.items | ||
| import androidx.compose.material3.HorizontalDivider | ||
| import androidx.compose.material3.Icon | ||
| 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.res.painterResource | ||
| import androidx.compose.ui.res.stringResource | ||
| import androidx.compose.ui.text.style.TextOverflow | ||
| import androidx.compose.ui.unit.dp | ||
| import com.woocommerce.android.R | ||
|
|
||
| @Composable | ||
| fun SingleChoiceFilterPage( | ||
| items: List<BookingFilterListItem>, | ||
| selectedValue: String?, | ||
| modifier: Modifier = Modifier, | ||
| ) { | ||
| LazyColumn(modifier = modifier) { | ||
| items(items) { item -> | ||
| SingleChoiceRow( | ||
| text = stringResource(item.title), | ||
| selected = item.value == selectedValue, | ||
| onClick = { item.onClick() } | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| private fun SingleChoiceRow( | ||
| text: String, | ||
| selected: Boolean, | ||
| onClick: () -> Unit, | ||
| ) { | ||
| Column(modifier = Modifier.fillMaxWidth()) { | ||
| Row( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .defaultMinSize(minHeight = 64.dp) | ||
| .clickable(onClick = onClick) | ||
| .padding(horizontal = 16.dp), | ||
| verticalAlignment = Alignment.CenterVertically | ||
| ) { | ||
| Text( | ||
| text = text, | ||
| style = MaterialTheme.typography.titleMedium, | ||
| overflow = TextOverflow.Ellipsis, | ||
| modifier = Modifier.weight(1f) | ||
| ) | ||
| if (selected) { | ||
| Icon( | ||
| painter = painterResource(id = R.drawable.ic_done_secondary), | ||
| contentDescription = null, | ||
| modifier = Modifier.size(26.dp), | ||
| tint = MaterialTheme.colorScheme.primary | ||
| ) | ||
| } | ||
| } | ||
| HorizontalDivider(thickness = 0.5.dp) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.woocommerce.android.ui.bookings.filter.type | ||
|
|
||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.collectAsState | ||
| import androidx.compose.runtime.getValue | ||
| import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel | ||
| import com.woocommerce.android.ui.bookings.filter.SingleChoiceFilterPage | ||
| import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsFilterOption | ||
|
|
||
| @Composable | ||
| fun BookingTypeFilterRoute( | ||
| initialType: BookingsFilterOption.BookingType, | ||
| onTypeFilterChanged: (BookingsFilterOption.BookingType) -> Unit, | ||
| ) { | ||
| val viewModel = hiltViewModel<BookingTypeFilterViewModel, BookingTypeFilterViewModel.Factory> { factory -> | ||
| factory.create(initialType, onTypeFilterChanged) | ||
| } | ||
|
|
||
| val uiState by viewModel.uiState.collectAsState() | ||
| BookingTypeFilterPage(uiState) | ||
| } | ||
|
|
||
| @Composable | ||
| fun BookingTypeFilterPage(state: BookingTypeFilterUiState) { | ||
| SingleChoiceFilterPage( | ||
| items = state.items, | ||
| selectedValue = state.selectedType.filterValue, | ||
| ) | ||
| } |
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to use a structure similar to |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.woocommerce.android.ui.bookings.filter.type | ||
|
|
||
| import androidx.annotation.StringRes | ||
| import com.woocommerce.android.R | ||
| import com.woocommerce.android.ui.bookings.filter.BookingFilterListItem | ||
| import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsFilterOption.BookingType | ||
|
|
||
| data class BookingTypeFilterUiState( | ||
| val selectedType: BookingType = BookingType.Any, | ||
| val onTypeSelected: (BookingType) -> Unit = {}, | ||
| ) { | ||
| val items: List<BookingFilterListItem> = availableBookingTypes().map { type -> | ||
| BookingFilterListItem( | ||
| title = type.titleRes, | ||
| value = type.filterValue, | ||
| onClick = { onTypeSelected(type) } | ||
| ) | ||
| } | ||
|
|
||
| val BookingType.titleRes: Int | ||
| @StringRes get() = when (this) { | ||
| BookingType.Any -> R.string.bookings_filter_default | ||
| BookingType.Service -> R.string.bookings_filter_type_service | ||
| BookingType.Event -> R.string.bookings_filter_type_event | ||
| } | ||
|
|
||
| private fun availableBookingTypes(): List<BookingType> = listOf( | ||
| BookingType.Any, | ||
| BookingType.Service, | ||
| BookingType.Event, | ||
| ) | ||
| } | ||
|
|
||
| val BookingType.filterValue: String? | ||
| // TODO Update this with actual endpoint values | ||
| get() = when (this) { | ||
| BookingType.Service -> "service" | ||
| BookingType.Event -> "event" | ||
| BookingType.Any -> null | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.woocommerce.android.ui.bookings.filter.type | ||
|
|
||
| import androidx.lifecycle.SavedStateHandle | ||
| import com.woocommerce.android.viewmodel.ScopedViewModel | ||
| import dagger.assisted.Assisted | ||
| import dagger.assisted.AssistedFactory | ||
| import dagger.assisted.AssistedInject | ||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.flow.StateFlow | ||
| import kotlinx.coroutines.flow.update | ||
| import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsFilterOption | ||
|
|
||
| @HiltViewModel(assistedFactory = BookingTypeFilterViewModel.Factory::class) | ||
| class BookingTypeFilterViewModel @AssistedInject constructor( | ||
| @Assisted private val initialType: BookingsFilterOption.BookingType, | ||
| @Assisted private val onTypeFilterChanged: (BookingsFilterOption.BookingType) -> Unit, | ||
| savedStateHandle: SavedStateHandle | ||
| ) : ScopedViewModel(savedStateHandle) { | ||
|
|
||
| private val _uiState = MutableStateFlow( | ||
| BookingTypeFilterUiState(selectedType = initialType, onTypeSelected = ::onTypeSelected) | ||
| ) | ||
| val uiState: StateFlow<BookingTypeFilterUiState> = _uiState | ||
|
|
||
| private fun onTypeSelected(type: BookingsFilterOption.BookingType) { | ||
| if (_uiState.value.selectedType != type) { | ||
| _uiState.update { current -> current.copy(selectedType = type) } | ||
| } | ||
| onTypeFilterChanged(type) | ||
| } | ||
|
|
||
| @AssistedFactory | ||
| interface Factory { | ||
| fun create( | ||
| initialType: BookingsFilterOption.BookingType, | ||
| onTypeFilterChanged: (BookingsFilterOption.BookingType) -> Unit | ||
| ): BookingTypeFilterViewModel | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,11 @@ sealed interface BookingsFilterOption { | |
|
|
||
| object PaymentStatus : BookingsFilterOption | ||
|
|
||
| object BookingType : BookingsFilterOption | ||
| sealed interface BookingType : BookingsFilterOption { | ||
| object Any : BookingType | ||
| object Service : BookingType | ||
| object Event : BookingType | ||
| } | ||
|
Comment on lines
+12
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @irfano I'm late, but I think something like this would be more consistent with other filters here. Having it like this wouldn't require different filtering logic in the ViewModel to set the state. data class BookingType(
val value: Type
) : BookingsFilterOption {
enum class Type {
ANY, SERVICE, EVENT
}
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for noticing this and sharing your feedback, Adam. This was the alternative I had in mind. I would either do this or update the |
||
|
|
||
| data class Customer(val customerId: Long, val customerName: String) : BookingsFilterOption | ||
|
|
||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I extracted this from
BookingFilterListUiStateto make it reusable across all screens, not just the root page.