Skip to content

Commit c513ed0

Browse files
authored
Merge pull request #14671 from woocommerce/issue/WOOMOB-1237-booking-list-sorting-ui
[Bookings] Add "Sort by" UI to booking list screen
2 parents 10ed2e6 + 4ad6e93 commit c513ed0

File tree

7 files changed

+276
-1
lines changed

7 files changed

+276
-1
lines changed

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/list/BookingListScreen.kt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ package com.woocommerce.android.ui.bookings.list
22

33
import androidx.compose.foundation.background
44
import androidx.compose.foundation.clickable
5+
import androidx.compose.foundation.layout.Arrangement
56
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.PaddingValues
68
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.Spacer
710
import androidx.compose.foundation.layout.WindowInsets
11+
import androidx.compose.foundation.layout.defaultMinSize
812
import androidx.compose.foundation.layout.fillMaxSize
913
import androidx.compose.foundation.layout.fillMaxWidth
1014
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.foundation.layout.width
1116
import androidx.compose.foundation.layout.wrapContentSize
1217
import androidx.compose.foundation.layout.wrapContentWidth
1318
import androidx.compose.foundation.lazy.LazyColumn
@@ -17,12 +22,14 @@ import androidx.compose.foundation.lazy.rememberLazyListState
1722
import androidx.compose.material.icons.Icons
1823
import androidx.compose.material.icons.automirrored.filled.ArrowBack
1924
import androidx.compose.material.icons.filled.Search
25+
import androidx.compose.material3.ButtonDefaults
2026
import androidx.compose.material3.CircularProgressIndicator
2127
import androidx.compose.material3.ExperimentalMaterial3Api
2228
import androidx.compose.material3.HorizontalDivider
2329
import androidx.compose.material3.Icon
2430
import androidx.compose.material3.IconButton
2531
import androidx.compose.material3.MaterialTheme
32+
import androidx.compose.material3.OutlinedButton
2633
import androidx.compose.material3.Scaffold
2734
import androidx.compose.material3.Text
2835
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@@ -35,6 +42,7 @@ import androidx.compose.ui.Alignment
3542
import androidx.compose.ui.Modifier
3643
import androidx.compose.ui.focus.FocusRequester
3744
import androidx.compose.ui.focus.focusRequester
45+
import androidx.compose.ui.res.painterResource
3846
import androidx.compose.ui.res.stringResource
3947
import androidx.compose.ui.unit.dp
4048
import com.woocommerce.android.R
@@ -59,6 +67,7 @@ fun BookingListScreen(viewModel: BookingListViewModel) {
5967

6068
@Composable
6169
fun BookingListScreen(state: BookingListViewState) {
70+
state.sortBottomSheetState?.let { BookingSortBottomSheet(it) }
6271
Scaffold(
6372
topBar = {
6473
Toolbar(
@@ -90,6 +99,8 @@ fun BookingListScreen(state: BookingListViewState) {
9099
state.tabState.onTabChanged(it)
91100
}
92101
)
102+
BookingListControls(state.controlsState)
103+
HorizontalDivider(thickness = 0.5.dp)
93104

94105
when {
95106
state.contentState.isNotEmpty() -> {
@@ -227,6 +238,51 @@ private fun BookingList(
227238
}
228239
}
229240

241+
@Composable
242+
private fun BookingListControls(
243+
state: BookingListControlsState,
244+
modifier: Modifier = Modifier
245+
) {
246+
Row(
247+
modifier = modifier
248+
.fillMaxWidth()
249+
.background(MaterialTheme.colorScheme.surface)
250+
.padding(horizontal = 8.dp, vertical = 4.dp),
251+
horizontalArrangement = Arrangement.spacedBy(8.dp)
252+
) {
253+
OutlinedButton(
254+
modifier = Modifier.defaultMinSize(minHeight = 36.dp),
255+
contentPadding = PaddingValues(start = 16.dp, end = 8.dp),
256+
onClick = state.onSortClick,
257+
) {
258+
Text(
259+
text = state.selectedSortOption.shortName(),
260+
style = MaterialTheme.typography.bodyMedium,
261+
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
262+
)
263+
Spacer(modifier = Modifier.width(8.dp))
264+
Icon(
265+
painter = painterResource(id = R.drawable.ic_arrow_drop_down),
266+
contentDescription = null,
267+
tint = MaterialTheme.colorScheme.onSurfaceVariant
268+
)
269+
}
270+
OutlinedButton(
271+
modifier = Modifier.defaultMinSize(minWidth = 88.dp, minHeight = 36.dp),
272+
contentPadding = PaddingValues(horizontal = 16.dp),
273+
colors = ButtonDefaults.outlinedButtonColors().copy(
274+
contentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
275+
),
276+
onClick = state.onFilterClick,
277+
) {
278+
Text(
279+
text = stringResource(R.string.bookings_filters_default_title),
280+
style = MaterialTheme.typography.bodyMedium,
281+
)
282+
}
283+
}
284+
}
285+
230286
@Composable
231287
private fun BookingListTab.name(): String = when (this) {
232288
BookingListTab.Today -> stringResource(R.string.bookings_tab_today)
@@ -262,6 +318,12 @@ private fun BookingListPreview() {
262318
selectedTab = BookingListTab.Today,
263319
onTabChanged = {}
264320
),
321+
controlsState = BookingListControlsState(
322+
selectedSortOption = BookingListSortOption.NewestToOldest,
323+
onSortClick = {},
324+
onFilterClick = {}
325+
),
326+
sortBottomSheetState = null,
265327
searchState = BookingListSearchState(
266328
query = null,
267329
onQueryChanged = {}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/list/BookingListViewModel.kt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ class BookingListViewModel @Inject constructor(
3838
key = "searchQuery"
3939
)
4040

41+
private val sortOptionsByTab = MutableStateFlow(
42+
mapOf(
43+
BookingListTab.Today to BookingListSortOption.NewestToOldest,
44+
BookingListTab.Upcoming to BookingListSortOption.NewestToOldest,
45+
BookingListTab.All to BookingListSortOption.NewestToOldest,
46+
)
47+
)
48+
49+
private val isSortSheetVisible = MutableStateFlow(false)
50+
4151
private var bookingsFetchJob: Job? = null
4252
private var bookingsLoadMoreJob: Job? = null
4353

@@ -65,14 +75,31 @@ class BookingListViewModel @Inject constructor(
6575
val state = combine(
6676
contentState,
6777
selectedTab,
78+
sortOptionsByTab,
79+
isSortSheetVisible,
6880
searchState
69-
) { contentState, selectedTab, searchState ->
81+
) { contentState, selectedTab, sortOptionsByTab, sheetVisible, searchState ->
82+
val sortOption = sortOptionsByTab[selectedTab] ?: BookingListSortOption.NewestToOldest
7083
BookingListViewState(
7184
contentState = contentState,
7285
tabState = BookingListTabState(
7386
selectedTab = selectedTab,
7487
onTabChanged = ::onTabChanged
7588
),
89+
controlsState = BookingListControlsState(
90+
selectedSortOption = sortOption,
91+
onSortClick = ::onSortClicked,
92+
onFilterClick = ::onFilterClicked
93+
),
94+
sortBottomSheetState = if (sheetVisible) {
95+
BookingListSortBottomSheetState(
96+
selectedOption = sortOption,
97+
onSelect = ::onSortOptionSelected,
98+
onDismiss = ::onSortDismiss
99+
)
100+
} else {
101+
null
102+
},
76103
searchState = searchState
77104
)
78105
}.asLiveData()
@@ -138,6 +165,26 @@ class BookingListViewModel @Inject constructor(
138165
selectedTab.value = tab
139166
}
140167

168+
private fun onSortClicked() {
169+
isSortSheetVisible.value = true
170+
}
171+
172+
private fun onSortOptionSelected(option: BookingListSortOption) {
173+
val tab = selectedTab.value
174+
sortOptionsByTab.value = sortOptionsByTab.value.toMutableMap()
175+
.also { it[tab] = option }
176+
isSortSheetVisible.value = false
177+
// TODO Apply the selected sorting to the data for the active tab
178+
}
179+
180+
private fun onSortDismiss() {
181+
isSortSheetVisible.value = false
182+
}
183+
184+
private fun onFilterClicked() {
185+
// TODO Show filter bottom sheet
186+
}
187+
141188
private fun prepareFilters(): List<BookingsFilterOption> = with(filtersBuilder) {
142189
listOfNotNull(
143190
selectedTab.value.asDateRangeFilter()

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/list/BookingListViewState.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import java.time.format.FormatStyle
1212
data class BookingListViewState(
1313
val contentState: BookingListContentState,
1414
val tabState: BookingListTabState,
15+
val controlsState: BookingListControlsState,
16+
val sortBottomSheetState: BookingListSortBottomSheetState?,
1517
val searchState: BookingListSearchState
1618
) {
1719
// TODO combine with other filters when available
@@ -42,6 +44,12 @@ data class BookingListTabState(
4244
val onTabChanged: (BookingListTab) -> Unit
4345
)
4446

47+
data class BookingListControlsState(
48+
val selectedSortOption: BookingListSortOption,
49+
val onSortClick: () -> Unit,
50+
val onFilterClick: () -> Unit
51+
)
52+
4553
data class BookingListItem(
4654
val id: Long,
4755
val summary: BookingSummaryModel
@@ -55,6 +63,16 @@ enum class BookingListTab {
5563
Today, Upcoming, All
5664
}
5765

66+
enum class BookingListSortOption {
67+
NewestToOldest, OldestToNewest
68+
}
69+
70+
data class BookingListSortBottomSheetState(
71+
val selectedOption: BookingListSortOption,
72+
val onSelect: (BookingListSortOption) -> Unit,
73+
val onDismiss: () -> Unit
74+
)
75+
5876
fun Booking.toUiModel(): BookingListItem {
5977
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(
6078
FormatStyle.MEDIUM,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.woocommerce.android.ui.bookings.list
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.clickable
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Spacer
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.foundation.layout.size
9+
import androidx.compose.foundation.rememberScrollState
10+
import androidx.compose.foundation.verticalScroll
11+
import androidx.compose.material3.ExperimentalMaterial3Api
12+
import androidx.compose.material3.ListItem
13+
import androidx.compose.material3.ListItemDefaults
14+
import androidx.compose.material3.MaterialTheme
15+
import androidx.compose.material3.Text
16+
import androidx.compose.material3.rememberModalBottomSheetState
17+
import androidx.compose.runtime.Composable
18+
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.graphics.vector.ImageVector
20+
import androidx.compose.ui.res.stringResource
21+
import androidx.compose.ui.res.vectorResource
22+
import androidx.compose.ui.text.font.FontWeight
23+
import androidx.compose.ui.unit.dp
24+
import com.woocommerce.android.R
25+
import com.woocommerce.android.ui.compose.component.WCModalBottomSheet
26+
27+
@OptIn(ExperimentalMaterial3Api::class)
28+
@Composable
29+
fun BookingSortBottomSheet(state: BookingListSortBottomSheetState) {
30+
val sheetState = rememberModalBottomSheetState()
31+
WCModalBottomSheet(
32+
sheetState = sheetState,
33+
onDismissRequest = state.onDismiss
34+
) {
35+
Column(
36+
modifier = Modifier
37+
.padding(bottom = 16.dp)
38+
.verticalScroll(rememberScrollState())
39+
) {
40+
Text(
41+
modifier = Modifier.padding(horizontal = 16.dp),
42+
text = stringResource(id = R.string.product_list_sorting_header),
43+
style = MaterialTheme.typography.titleSmall,
44+
color = MaterialTheme.colorScheme.onSurfaceVariant
45+
)
46+
Spacer(modifier = Modifier.size(16.dp))
47+
BookingListSortOption.entries.forEach { option ->
48+
ListItem(
49+
modifier = Modifier.clickable { state.onSelect(option) },
50+
colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceContainerLow),
51+
headlineContent = {
52+
Text(
53+
text = option.name(),
54+
fontWeight = FontWeight.Medium
55+
)
56+
},
57+
trailingContent = {
58+
if (state.selectedOption == option) {
59+
Image(
60+
modifier = Modifier.padding(end = 12.dp),
61+
imageVector = ImageVector.vectorResource(R.drawable.ic_check),
62+
contentDescription = stringResource(R.string.product_list_sorting_list_item),
63+
)
64+
}
65+
}
66+
)
67+
}
68+
}
69+
}
70+
}
71+
72+
@Composable
73+
fun BookingListSortOption.name() = when (this) {
74+
BookingListSortOption.NewestToOldest -> stringResource(R.string.bookings_sort_by_newest_to_oldest)
75+
BookingListSortOption.OldestToNewest -> stringResource(R.string.bookings_sort_by_oldest_to_newest)
76+
}
77+
78+
@Composable
79+
fun BookingListSortOption.shortName() = when (this) {
80+
BookingListSortOption.NewestToOldest -> stringResource(R.string.bookings_sort_by_newest_to_oldest_short)
81+
BookingListSortOption.OldestToNewest -> stringResource(R.string.bookings_sort_by_oldest_to_newest_short)
82+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="15dp"
3+
android:height="15dp"
4+
android:viewportWidth="15"
5+
android:viewportHeight="15">
6+
<path
7+
android:fillColor="@color/woo_purple_40"
8+
android:pathData="M5.819,14.411c-0.415,0 -0.722,-0.174 -1.013,-0.531L0.506,8.5C0.3,8.244 0.208,8.003 0.208,7.754c0,-0.573 0.423,-0.988 1.004,-0.988 0.365,0 0.614,0.133 0.855,0.457l3.719,4.773L12.999,0.54c0.249,-0.39 0.49,-0.532 0.896,-0.532 0.582,0 0.98,0.399 0.98,0.971 0,0.225 -0.066,0.457 -0.24,0.723L6.831,13.872a1.15,1.15 0,0 1,-1.013 0.54" />
9+
</vector>

WooCommerce/src/main/res/values/strings.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4130,6 +4130,12 @@
41304130
<string name="bookings_tab_all">All</string>
41314131
<string name="bookings_search_hint">Search bookings</string>
41324132
<string name="bookings_search_with_filters_hint">Search filtered bookings</string>
4133+
<string name="bookings_sort_by_oldest_to_newest">Oldest to newest</string>
4134+
<string name="bookings_sort_by_newest_to_oldest">Newest to oldest</string>
4135+
<string name="bookings_sort_by_oldest_to_newest_short">Oldest</string>
4136+
<string name="bookings_sort_by_newest_to_oldest_short">Newest</string>
4137+
<string name="bookings_filters_default_title">Filters</string>
4138+
<string name="bookings_filters_count_title">Filters (%d)</string>
41334139

41344140
<!-- Booking details view-->
41354141
<string name="booking_details_title">Booking #%s</string>

0 commit comments

Comments
 (0)