Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = {}
)
Comment on lines +8 to +12
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I extracted this from BookingFilterListUiState to make it reusable across all screens, not just the root page.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 Service/Event filter. But who knows if that is the final call:

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess “Service/Event” refers to the name of the service or event, while “Booking type” indicates whether the booking is for a service or an event. These both screens are from Figma, so let’s keep both.

image

}
composable(BookingFilterPage.Customer.route) {
TODO()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ data class BookingFilterListUiState(
val onClose: () -> Unit = {},
val onShowBookings: () -> Unit = {},
val openPage: (BookingFilterPage) -> Unit = {},
val onUpdateFilterOption: (BookingsFilterOption) -> Unit = {}
) {

val items: List<BookingFilterListItem> = availableBookingFilters().map { page ->
Expand All @@ -35,6 +36,11 @@ data class BookingFilterListUiState(
)
}

val currentBookingType: BookingsFilterOption.BookingType
get() = newBookingFilters.getOrDefault<BookingsFilterOption.BookingType>(
initialBookingFilters?.bookingType
) ?: BookingsFilterOption.BookingType.Any

@DrawableRes
val navigationIcon: Int = when (currentPage) {
BookingFilterPage.List -> R.drawable.ic_gridicons_cross_24dp
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,6 +25,7 @@ class BookingFilterListViewModel @Inject constructor(
onClose = ::onClose,
onShowBookings = ::onShowBookings,
openPage = ::onOpenPage,
onUpdateFilterOption = ::onUpdateFilterOption,
)
)
val uiState = _uiState.asLiveData()
Expand All @@ -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
Expand Down
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,
)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to use a structure similar to BookingFilterListUiState here.

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
}
}
2 changes: 2 additions & 0 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4242,6 +4242,8 @@
<string name="bookings_filter_title_date">Date &amp; time</string>
<string name="bookings_filter_title_service_event">Service / Event</string>
<string name="bookings_filter_default">Any</string>
<string name="bookings_filter_type_service">Service</string>
<string name="bookings_filter_type_event">Event</string>

<!-- Booking details view-->
<string name="booking_details_title">Booking #%s</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
        }
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 onUpdateFilterOption() function, I chose the latter to avoid defining a new Type class. But your suggestion might actually be better, I’ll consider it and update BookingType in my next PR.


data class Customer(val customerId: Long, val customerName: String) : BookingsFilterOption

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading