Skip to content

Commit cd0480a

Browse files
authored
Merge pull request #14578 from woocommerce/feat/WOOMOB-1149-pos-historical-orders-ptr
[POS Historical Orders] Pull to Refresh
2 parents f62c9f8 + 6f414cc commit cd0480a

File tree

6 files changed

+456
-154
lines changed

6 files changed

+456
-154
lines changed

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersDataSource.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import org.wordpress.android.fluxc.persistence.entity.OrderEntity
1111
import javax.inject.Inject
1212

1313
sealed class LoadOrdersResult {
14-
data class Success(val orders: List<Order>) : LoadOrdersResult()
14+
data class SuccessCache(val orders: List<Order>) : LoadOrdersResult()
15+
data class SuccessRemote(val orders: List<Order>) : LoadOrdersResult()
1516
data class Error(val message: String) : LoadOrdersResult()
1617
}
1718

@@ -26,7 +27,9 @@ class WooPosOrdersDataSource @Inject constructor(
2627
}
2728
fun loadOrders(): Flow<LoadOrdersResult> = flow {
2829
val cached = ordersCache.getAll()
29-
emit(LoadOrdersResult.Success(cached))
30+
if (cached.isNotEmpty()) {
31+
emit(LoadOrdersResult.SuccessCache(cached))
32+
}
3033

3134
val result = restClient.fetchOrders(
3235
site = selectedSite.get(),
@@ -43,11 +46,13 @@ class WooPosOrdersDataSource @Inject constructor(
4346
} else {
4447
val mapped = result.orders.toAppModels()
4548
ordersCache.setAll(mapped)
46-
emit(LoadOrdersResult.Success(result.orders.toAppModels()))
49+
emit(LoadOrdersResult.SuccessRemote(result.orders.toAppModels()))
4750
}
4851
}
4952

53+
fun clearCache() = ordersCache.clear()
54+
5055
private suspend fun List<OrderEntity>.toAppModels(): List<Order> = map {
5156
orderMapper.toAppModel(it)
52-
} ?: emptyList()
57+
}
5358
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersScreen.kt

Lines changed: 105 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler
44
import androidx.compose.foundation.background
55
import androidx.compose.foundation.clickable
66
import androidx.compose.foundation.layout.Arrangement
7+
import androidx.compose.foundation.layout.Box
78
import androidx.compose.foundation.layout.Column
89
import androidx.compose.foundation.layout.PaddingValues
910
import androidx.compose.foundation.layout.Row
@@ -14,6 +15,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
1415
import androidx.compose.foundation.layout.padding
1516
import androidx.compose.foundation.lazy.LazyColumn
1617
import androidx.compose.foundation.lazy.items
18+
import androidx.compose.material.ExperimentalMaterialApi
19+
import androidx.compose.material.pullrefresh.PullRefreshIndicator
20+
import androidx.compose.material.pullrefresh.pullRefresh
21+
import androidx.compose.material.pullrefresh.rememberPullRefreshState
1722
import androidx.compose.material3.MaterialTheme
1823
import androidx.compose.runtime.Composable
1924
import androidx.compose.runtime.collectAsState
@@ -27,16 +32,16 @@ import androidx.compose.ui.semantics.semantics
2732
import androidx.compose.ui.text.font.FontWeight
2833
import androidx.hilt.navigation.compose.hiltViewModel
2934
import com.woocommerce.android.R
30-
import com.woocommerce.android.extensions.formatToDDMMMYYYY
31-
import com.woocommerce.android.model.Order
3235
import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview
3336
import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosText
3437
import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosToolbar
3538
import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing
3639
import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme
3740
import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTypography
41+
import com.woocommerce.android.ui.woopos.home.items.WooPosPullToRefreshState
3842
import com.woocommerce.android.ui.woopos.root.navigation.WooPosNavigationEvent
3943

44+
@OptIn(ExperimentalMaterialApi::class)
4045
@Composable
4146
fun WooPosOrdersScreen(
4247
onNavigationEvent: (WooPosNavigationEvent) -> Unit,
@@ -48,19 +53,59 @@ fun WooPosOrdersScreen(
4853
BackHandler { onNavigationEvent(WooPosNavigationEvent.GoBack) }
4954

5055
Row(modifier = Modifier.fillMaxSize()) {
51-
Column(
56+
OrdersList(
57+
state = state,
58+
onBackClicked = onBackClicked,
59+
onRefresh = viewModel::onRefresh,
60+
isRefreshing = state.pullToRefreshState == WooPosPullToRefreshState.Refreshing,
61+
onOrderSelected = viewModel::onOrderSelected,
5262
modifier = Modifier
5363
.weight(0.3f)
5464
.fillMaxHeight()
5565
.background(MaterialTheme.colorScheme.surface)
56-
) {
57-
WooPosToolbar(
58-
titleText = stringResource(R.string.woopos_orders_title),
59-
onBackClicked = onBackClicked,
60-
)
66+
)
67+
68+
OrderDetails(
69+
state = state,
70+
modifier = Modifier
71+
.weight(0.7f)
72+
.fillMaxHeight()
73+
.background(MaterialTheme.colorScheme.surfaceContainerLow)
74+
)
75+
}
76+
}
77+
78+
@OptIn(ExperimentalMaterialApi::class)
79+
@Composable
80+
private fun OrdersList(
81+
state: WooPosOrdersState,
82+
onBackClicked: () -> Unit,
83+
onRefresh: () -> Unit,
84+
isRefreshing: Boolean,
85+
onOrderSelected: (Long) -> Unit,
86+
modifier: Modifier = Modifier
87+
) {
88+
Column(modifier = modifier) {
89+
WooPosToolbar(
90+
titleText = stringResource(R.string.woopos_orders_title),
91+
onBackClicked = onBackClicked,
92+
)
93+
94+
val pullRefreshState = rememberPullRefreshState(
95+
refreshing = isRefreshing,
96+
onRefresh = onRefresh
97+
)
6198

62-
when {
63-
state.isLoading -> {
99+
Box(
100+
modifier = Modifier
101+
.fillMaxSize()
102+
.pullRefresh(
103+
pullRefreshState,
104+
enabled = state.pullToRefreshState != WooPosPullToRefreshState.Disabled
105+
)
106+
) {
107+
when (state) {
108+
is WooPosOrdersState.Loading -> {
64109
Column(
65110
modifier = Modifier.fillMaxSize(),
66111
horizontalAlignment = Alignment.CenterHorizontally
@@ -73,56 +118,77 @@ fun WooPosOrdersScreen(
73118
)
74119
}
75120
}
76-
state.error != null -> {
121+
122+
is WooPosOrdersState.Error -> {
77123
Column(
78124
modifier = Modifier.fillMaxSize(),
79125
horizontalAlignment = Alignment.CenterHorizontally
80126
) {
81127
WooPosText(
82-
text = state.error ?: stringResource(R.string.error_generic),
128+
text = state.message,
83129
style = WooPosTypography.BodyMedium,
84130
color = MaterialTheme.colorScheme.error,
85131
modifier = Modifier.padding(WooPosSpacing.Large.value)
86132
)
87133
}
88134
}
89-
state.orders.isEmpty() -> {
135+
136+
is WooPosOrdersState.Empty -> {
90137
Column(
91138
modifier = Modifier.fillMaxSize(),
92139
horizontalAlignment = Alignment.CenterHorizontally
93140
) {
94141
WooPosText(
95-
text = "No Orders Found",
142+
text = "No orders found",
96143
style = WooPosTypography.BodyMedium,
97144
color = MaterialTheme.colorScheme.onSurfaceVariant,
98145
modifier = Modifier.padding(WooPosSpacing.Large.value)
99146
)
100147
}
101148
}
102-
else -> {
149+
150+
is WooPosOrdersState.Content -> {
103151
WooPosOrdersListPaneScreen(
104-
orders = state.orders,
152+
items = state.items,
105153
selectedOrderId = state.selectedOrderId,
106-
onOrderSelected = viewModel::onOrderSelected,
154+
onOrderSelected = onOrderSelected,
107155
modifier = Modifier.fillMaxSize()
108156
)
109157
}
110158
}
159+
160+
PullRefreshIndicator(
161+
refreshing = isRefreshing,
162+
state = pullRefreshState,
163+
modifier = Modifier
164+
.align(Alignment.TopCenter)
165+
.padding(top = WooPosSpacing.XSmall.value),
166+
backgroundColor = MaterialTheme.colorScheme.surface,
167+
contentColor = MaterialTheme.colorScheme.primary
168+
)
111169
}
170+
}
171+
}
112172

113-
WooPosOrdersDetailPaneScreen(
114-
order = state.selectedOrder,
115-
modifier = Modifier
116-
.weight(0.7f)
117-
.fillMaxHeight()
118-
.background(MaterialTheme.colorScheme.surfaceContainerLow)
119-
)
173+
@Composable
174+
private fun OrderDetails(
175+
state: WooPosOrdersState,
176+
modifier: Modifier = Modifier
177+
) {
178+
val selectedItem: OrderItemViewState? = when (state) {
179+
is WooPosOrdersState.Content -> state.items.firstOrNull { it.id == state.selectedOrderId }
180+
else -> null
120181
}
182+
183+
WooPosOrdersDetailPaneScreen(
184+
selected = selectedItem,
185+
modifier = modifier.fillMaxSize()
186+
)
121187
}
122188

123189
@Composable
124190
fun WooPosOrdersListPaneScreen(
125-
orders: List<Order>,
191+
items: List<OrderItemViewState>,
126192
selectedOrderId: Long?,
127193
onOrderSelected: (Long) -> Unit,
128194
modifier: Modifier = Modifier
@@ -131,14 +197,13 @@ fun WooPosOrdersListPaneScreen(
131197
modifier = modifier,
132198
contentPadding = PaddingValues(vertical = WooPosSpacing.XSmall.value)
133199
) {
134-
items(orders, key = { it.id }) { order ->
135-
val isSelected = order.id == selectedOrderId
200+
items(items, key = { it.id }) { item ->
201+
val isSelected = item.id == selectedOrderId
136202
val background = if (isSelected) {
137203
MaterialTheme.colorScheme.primaryContainer
138204
} else {
139205
MaterialTheme.colorScheme.surface
140206
}
141-
142207
val foreground = if (isSelected) {
143208
MaterialTheme.colorScheme.onPrimaryContainer
144209
} else {
@@ -150,32 +215,21 @@ fun WooPosOrdersListPaneScreen(
150215
.fillMaxWidth()
151216
.clip(MaterialTheme.shapes.medium)
152217
.background(background)
153-
.clickable { onOrderSelected(order.id) }
218+
.clickable { onOrderSelected(item.id) }
154219
.semantics { selected = isSelected }
155220
.padding(
156221
horizontal = WooPosSpacing.Medium.value,
157222
vertical = WooPosSpacing.Medium.value
158223
),
159224
verticalAlignment = Alignment.Top
160225
) {
161-
Column(
162-
verticalArrangement = Arrangement.spacedBy(WooPosSpacing.XSmall.value)
163-
) {
164-
WooPosText(
165-
"Order #${order.number}",
166-
style = WooPosTypography.BodyMedium
167-
)
168-
WooPosText(
169-
text = order.dateCreated.formatToDDMMMYYYY(),
170-
style = WooPosTypography.BodySmall,
171-
color = foreground
172-
)
226+
Column(verticalArrangement = Arrangement.spacedBy(WooPosSpacing.XSmall.value)) {
227+
WooPosText(item.title, style = WooPosTypography.BodyMedium, color = foreground)
228+
WooPosText(item.date, style = WooPosTypography.BodySmall, color = foreground)
173229
}
174-
175230
Spacer(Modifier.weight(1f))
176-
177231
WooPosText(
178-
text = "${order.total} ${order.currency}",
232+
text = item.total,
179233
style = WooPosTypography.BodyMedium,
180234
modifier = Modifier.alignByBaseline()
181235
)
@@ -186,29 +240,22 @@ fun WooPosOrdersListPaneScreen(
186240

187241
@Composable
188242
fun WooPosOrdersDetailPaneScreen(
189-
order: Order?,
243+
selected: OrderItemViewState?,
190244
modifier: Modifier = Modifier
191245
) {
192-
Column(
193-
modifier = modifier.fillMaxSize()
194-
) {
246+
Column(modifier = modifier.fillMaxSize()) {
195247
WooPosToolbar(
196-
modifier = Modifier
197-
.fillMaxWidth(),
198-
titleText = "Order #${order?.number ?: "--"}",
248+
modifier = Modifier.fillMaxWidth(),
249+
titleText = selected?.title ?: "--",
199250
titleFontWeight = FontWeight.Bold
200251
)
201-
202252
Column(
203253
modifier = Modifier
204254
.fillMaxSize()
205-
.padding(
206-
start = WooPosSpacing.Large.value,
207-
end = WooPosSpacing.Large.value,
208-
)
255+
.padding(start = WooPosSpacing.Large.value, end = WooPosSpacing.Large.value)
209256
) {
210257
WooPosText(
211-
text = "Orders details will be displayed here",
258+
text = "Order details goes here",
212259
style = WooPosTypography.BodyMedium,
213260
color = MaterialTheme.colorScheme.onSurfaceVariant
214261
)
@@ -219,9 +266,5 @@ fun WooPosOrdersDetailPaneScreen(
219266
@WooPosPreview
220267
@Composable
221268
fun WooPosOrdersScreenPreview() {
222-
WooPosTheme {
223-
WooPosOrdersScreen(
224-
onNavigationEvent = {}
225-
)
226-
}
269+
WooPosTheme { WooPosOrdersScreen(onNavigationEvent = {}) }
227270
}
Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,45 @@
11
package com.woocommerce.android.ui.woopos.orders
22

3-
import com.woocommerce.android.model.Order
4-
5-
data class WooPosOrdersState(
6-
val orders: List<Order> = emptyList(),
7-
val selectedOrderId: Long? = null,
8-
val isLoading: Boolean = false,
9-
val error: String? = null
10-
) {
11-
val selectedOrder: Order?
12-
get() = selectedOrderId?.let { id -> orders.firstOrNull { it.id == id } }
3+
import androidx.compose.runtime.Immutable
4+
import com.woocommerce.android.ui.woopos.home.items.WooPosPaginationState
5+
import com.woocommerce.android.ui.woopos.home.items.WooPosPullToRefreshState
6+
7+
@Immutable
8+
data class OrderItemViewState(
9+
val id: Long,
10+
val title: String,
11+
val date: String,
12+
val total: String,
13+
val isSelected: Boolean
14+
)
15+
16+
@Immutable
17+
sealed class WooPosOrdersState {
18+
abstract val pullToRefreshState: WooPosPullToRefreshState
19+
20+
@Immutable
21+
data class Content(
22+
val items: List<OrderItemViewState>,
23+
override val pullToRefreshState: WooPosPullToRefreshState,
24+
val paginationState: WooPosPaginationState,
25+
val selectedOrderId: Long?
26+
) : WooPosOrdersState()
27+
28+
@Immutable
29+
data class Error(
30+
val message: String,
31+
) : WooPosOrdersState() {
32+
override val pullToRefreshState: WooPosPullToRefreshState = WooPosPullToRefreshState.Disabled
33+
}
34+
35+
@Immutable
36+
data object Loading : WooPosOrdersState() {
37+
override val pullToRefreshState: WooPosPullToRefreshState = WooPosPullToRefreshState.Disabled
38+
}
39+
40+
@Immutable
41+
data class Empty(
42+
override val pullToRefreshState: WooPosPullToRefreshState =
43+
WooPosPullToRefreshState.Enabled
44+
) : WooPosOrdersState()
1345
}

0 commit comments

Comments
 (0)