diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListItem.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListItem.kt new file mode 100644 index 000000000000..f4a251a8a5cf --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListItem.kt @@ -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 = {} +) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListScreen.kt index 4c647f02f7fa..ef2e5d8e5828 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListScreen.kt @@ -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) + } } composable(BookingFilterPage.Customer.route) { TODO() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListUiState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListUiState.kt index 8d72da0de22d..4959988da92f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListUiState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListUiState.kt @@ -25,6 +25,7 @@ data class BookingFilterListUiState( val onClose: () -> Unit = {}, val onShowBookings: () -> Unit = {}, val openPage: (BookingFilterPage) -> Unit = {}, + val onUpdateFilterOption: (BookingsFilterOption) -> Unit = {} ) { val items: List = availableBookingFilters().map { page -> @@ -35,6 +36,11 @@ data class BookingFilterListUiState( ) } + val currentBookingType: BookingsFilterOption.BookingType + get() = newBookingFilters.getOrDefault( + initialBookingFilters?.bookingType + ) ?: BookingsFilterOption.BookingType.Any + @DrawableRes val navigationIcon: Int = when (currentPage) { BookingFilterPage.List -> R.drawable.ic_gridicons_cross_24dp @@ -60,12 +66,6 @@ data class BookingFilterListUiState( } } -data class BookingFilterListItem( - @StringRes val title: Int, - val value: String? = null, - val onClick: () -> Unit = {} -) - val BookingFilterPage.titleRes: Int @StringRes get() = when (this) { BookingFilterPage.TeamMember -> R.string.bookings_filter_title_team_member diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListViewModel.kt index 086e870a7f66..ac649f20dd73 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListViewModel.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingFilters +import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsFilterOption import javax.inject.Inject @HiltViewModel @@ -24,6 +25,7 @@ class BookingFilterListViewModel @Inject constructor( onClose = ::onClose, onShowBookings = ::onShowBookings, openPage = ::onOpenPage, + onUpdateFilterOption = ::onUpdateFilterOption, ) ) val uiState = _uiState.asLiveData() @@ -38,6 +40,23 @@ class BookingFilterListViewModel @Inject constructor( } } + private fun onUpdateFilterOption(option: BookingsFilterOption) { + _uiState.update { current -> + val filtered = when (option) { + is BookingsFilterOption.BookingType -> { + current.newBookingFilters.filterNot { it is BookingsFilterOption.BookingType } + } + + else -> current.newBookingFilters.filterNot { it::class == option::class } + } + current.copy( + newBookingFilters = filtered + .plus(option) + .toSet() + ) + } + } + private fun getBookingFilter() { launch { // We don't observe changes here, just get the current value once diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/SingleChoiceFilterPage.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/SingleChoiceFilterPage.kt new file mode 100644 index 000000000000..2fe3b7893a15 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/SingleChoiceFilterPage.kt @@ -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, + 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) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/type/BookingTypeFilterPage.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/type/BookingTypeFilterPage.kt new file mode 100644 index 000000000000..e67da15b1a82 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/type/BookingTypeFilterPage.kt @@ -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 { 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, + ) +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/type/BookingTypeFilterUiState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/type/BookingTypeFilterUiState.kt new file mode 100644 index 000000000000..1a2a92dc74c0 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/type/BookingTypeFilterUiState.kt @@ -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 = 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 = 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 + } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/type/BookingTypeFilterViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/type/BookingTypeFilterViewModel.kt new file mode 100644 index 000000000000..9b85add3aeee --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/type/BookingTypeFilterViewModel.kt @@ -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 = _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 + } +} diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 9c0b222e1bd6..743b20876f9a 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4242,6 +4242,8 @@ Date & time Service / Event Any + Service + Event Booking #%s diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingsFilterOption.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingsFilterOption.kt index 02a6f8c79f8b..5e7ba72ce1f6 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingsFilterOption.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingsFilterOption.kt @@ -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 + } data class Customer(val customerId: Long, val customerName: String) : BookingsFilterOption diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingsRestClient.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingsRestClient.kt index d31dfcd80769..4c8659f2b819 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingsRestClient.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/bookings/BookingsRestClient.kt @@ -109,7 +109,7 @@ class BookingsRestClient @Inject constructor( BookingsFilterOption.TeamMember -> TODO() BookingsFilterOption.AttendanceStatus -> TODO() BookingsFilterOption.PaymentStatus -> TODO() - BookingsFilterOption.BookingType -> TODO() + is BookingsFilterOption.BookingType -> TODO() is BookingsFilterOption.Customer -> set("customer", filter.customerId.toString()) BookingsFilterOption.Location -> TODO()