-
Notifications
You must be signed in to change notification settings - Fork 135
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
[WooPos][Non Simple Product types] Retry UI for pagination errors on variations screens #12972
base: trunk
Are you sure you want to change the base?
Changes from 11 commits
bddcae5
9873533
42f4e1a
0fe310a
d84a3a8
455d396
30b6d5d
53a1838
bab7cbe
8ceaa5f
e5f7823
a45bd6c
71ee602
84febe1
5b60693
20d14bd
9685cd2
0bd442d
e40c789
e94d0e5
3ed43d3
187dfcd
6fe46f2
0f5cf15
d4d0060
81cb158
d03d6a2
28584e8
aa38f10
6a3b777
6bb0870
19a7ed5
ebbf621
623125f
a9780a3
45a1d7e
ba90251
4a920cb
8c3af25
1eb6c32
ef53ce9
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 |
---|---|---|
|
@@ -169,15 +169,22 @@ private fun MainItemsList( | |
listState, | ||
onItemClicked, | ||
onEndOfItemListReached, | ||
) | ||
) { | ||
ProductsError( | ||
modifier = Modifier.height(500.dp), | ||
onRetryClicked = { | ||
onEndOfItemListReached() | ||
} | ||
) | ||
} | ||
} | ||
} | ||
|
||
is WooPosItemsViewState.Loading -> ItemsLoadingIndicator() | ||
|
||
is WooPosItemsViewState.Empty -> ProductsEmptyList() | ||
|
||
is WooPosItemsViewState.Error -> ProductsError { onRetryClicked() } | ||
is WooPosItemsViewState.Error -> ProductsError(modifier = Modifier.width(640.dp)) { onRetryClicked() } | ||
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. Where does 640 come from? How does this look on a screen of 800 or whatever min we support now? 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. This code was already present in the 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. it's a bug I beleave, we should fix that probably not expand it to another screen 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. Just to understand more. What problem do you see using width of 640? We could use fillMaxWidth() instead. |
||
} | ||
} | ||
PullRefreshIndicator( | ||
|
@@ -300,13 +307,13 @@ fun ProductsEmptyList() { | |
} | ||
|
||
@Composable | ||
fun ProductsError(onRetryClicked: () -> Unit) { | ||
fun ProductsError(modifier: Modifier, onRetryClicked: () -> Unit) { | ||
Box( | ||
modifier = Modifier.fillMaxSize(), | ||
contentAlignment = Alignment.Center, | ||
) { | ||
WooPosErrorScreen( | ||
modifier = Modifier.width(640.dp), | ||
modifier = modifier, | ||
message = stringResource(id = R.string.woopos_products_loading_error_title), | ||
reason = stringResource(id = R.string.woopos_products_loading_error_message), | ||
primaryButton = Button( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,10 +16,6 @@ import androidx.compose.material.ExperimentalMaterialApi | |
import androidx.compose.material.Icon | ||
import androidx.compose.material.IconButton | ||
import androidx.compose.material.MaterialTheme | ||
import androidx.compose.material.Scaffold | ||
import androidx.compose.material.SnackbarDuration | ||
import androidx.compose.material.SnackbarHost | ||
import androidx.compose.material.SnackbarHostState | ||
import androidx.compose.material.Text | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.automirrored.filled.ArrowBack | ||
|
@@ -29,17 +25,13 @@ import androidx.compose.material.pullrefresh.rememberPullRefreshState | |
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.collectAsState | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalLifecycleOwner | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.text.font.FontWeight | ||
import androidx.compose.ui.unit.dp | ||
import androidx.constraintlayout.compose.ConstraintLayout | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
import androidx.lifecycle.Lifecycle | ||
import androidx.lifecycle.flowWithLifecycle | ||
import com.woocommerce.android.R | ||
import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview | ||
import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme | ||
|
@@ -61,27 +53,10 @@ fun WooPosVariationsScreen( | |
onBackClicked: () -> Unit | ||
) { | ||
val viewModel: WooPosVariationsViewModel = hiltViewModel() | ||
val snackbarHostState = remember { SnackbarHostState() } | ||
val lifecycleOwner = LocalLifecycleOwner.current | ||
val paginationErrorMessage = stringResource(id = R.string.woopos_variations_screen_pagination_error) | ||
|
||
LaunchedEffect(variableProductData.id) { | ||
viewModel.init(variableProductData.id) | ||
} | ||
LaunchedEffect(Unit) { | ||
viewModel.events | ||
.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) | ||
.collect { event -> | ||
when (event) { | ||
is WooPosVariationsViewModel.WooPosVariationEvents.PaginationError -> { | ||
snackbarHostState.showSnackbar( | ||
message = paginationErrorMessage, | ||
duration = SnackbarDuration.Short | ||
) | ||
} | ||
} | ||
} | ||
} | ||
val state = viewModel.viewState | ||
WooPosVariationsScreens( | ||
modifier, | ||
|
@@ -102,7 +77,6 @@ fun WooPosVariationsScreen( | |
}, | ||
variableProductData, | ||
state, | ||
snackbarHostState, | ||
) | ||
} | ||
|
||
|
@@ -118,93 +92,82 @@ private fun WooPosVariationsScreens( | |
onRetryClicked: () -> Unit, | ||
variableProductData: VariableProductData, | ||
state: StateFlow<WooPosVariationsViewState>, | ||
snackbarHostState: SnackbarHostState, | ||
) { | ||
val itemState = state.collectAsState() | ||
val pullToRefreshState = rememberPullRefreshState( | ||
itemState.value.reloadingProductsWithPullToRefresh, | ||
onPullToRefresh | ||
) | ||
Scaffold( | ||
snackbarHost = { | ||
Box( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(bottom = 80.dp) | ||
) { | ||
SnackbarHost( | ||
hostState = snackbarHostState, | ||
modifier = Modifier.align(Alignment.Center) | ||
) | ||
} | ||
} | ||
Box( | ||
modifier = modifier | ||
.fillMaxSize() | ||
.pullRefresh(pullToRefreshState) | ||
.padding( | ||
start = 16.dp.toAdaptivePadding(), | ||
end = 16.dp.toAdaptivePadding(), | ||
top = 30.dp.toAdaptivePadding(), | ||
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 think 30 can not be in 4dp grid. 28 or 32 should be https://m2.material.io/design/layout/spacing-methods.html#baseline-grid 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. Done: 71ee602 |
||
bottom = 0.dp.toAdaptivePadding(), | ||
) | ||
) { | ||
Box( | ||
modifier = modifier | ||
.fillMaxSize() | ||
.pullRefresh(pullToRefreshState) | ||
.padding( | ||
start = 16.dp.toAdaptivePadding(), | ||
end = 16.dp.toAdaptivePadding(), | ||
top = 30.dp.toAdaptivePadding(), | ||
bottom = 0.dp.toAdaptivePadding(), | ||
) | ||
BackHandler(onBack = onBackClicked) | ||
Column( | ||
modifier = modifier.fillMaxHeight() | ||
) { | ||
BackHandler(onBack = onBackClicked) | ||
Column( | ||
modifier = modifier.fillMaxHeight() | ||
) { | ||
VariationsToolbar( | ||
variableProductData = variableProductData, | ||
onBackClicked = onBackClicked | ||
) | ||
when (val itemsState = itemState.value) { | ||
is WooPosVariationsViewState.Content -> { | ||
Spacer(modifier = Modifier.height(16.dp)) | ||
ItemList( | ||
state = itemsState, | ||
listState = rememberLazyListState(), | ||
onItemClicked = { | ||
onItemClicked( | ||
(it as WooPosItem.Variation).productId, | ||
it.id | ||
) | ||
VariationsToolbar( | ||
variableProductData = variableProductData, | ||
onBackClicked = onBackClicked | ||
) | ||
when (val itemsState = itemState.value) { | ||
is WooPosVariationsViewState.Content -> { | ||
val lazyListState = rememberLazyListState() | ||
Spacer(modifier = Modifier.height(16.dp)) | ||
ItemList( | ||
state = itemsState, | ||
listState = lazyListState, | ||
onItemClicked = { | ||
onItemClicked( | ||
(it as WooPosItem.Variation).productId, | ||
it.id | ||
) | ||
}, | ||
onEndOfProductsListReached = onEndOfItemListReached, | ||
onErrorWhilePaginating = { | ||
VariationsError(modifier = Modifier.height(500.dp)) { | ||
onEndOfItemListReached() | ||
} | ||
) { | ||
onEndOfItemListReached() | ||
} | ||
} | ||
|
||
is WooPosVariationsViewState.Loading -> ItemsLoadingIndicator( | ||
minOf(10, variableProductData.numOfVariations) | ||
) | ||
} | ||
|
||
is WooPosVariationsViewState.Error -> { | ||
VariationsError { | ||
onRetryClicked() | ||
} | ||
} | ||
is WooPosVariationsViewState.Loading -> ItemsLoadingIndicator( | ||
minOf(10, variableProductData.numOfVariations) | ||
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. If the goal is to show the exact amount of variations as loading items, then I don't understand why we need to take min from it or 10? 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. The goal is not to show the exact amount of loading items as variations. The goal is to show the exact amount of loading items as variations only if the variation is <= 10. 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. Then, I don't understand the logic behind that. I think we either want to indicate the number of items we load or don't want to do it and just show a generic loading screen similar to the products. Imo this just creates confusion for both us and the users on top of that it leaks logic to the view layer 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. Alright, fixed here: 8c3af25 |
||
) | ||
|
||
else -> {} | ||
is WooPosVariationsViewState.Error -> { | ||
VariationsError(modifier = Modifier.width(640.dp)) { | ||
onRetryClicked() | ||
} | ||
} | ||
|
||
else -> {} | ||
} | ||
PullRefreshIndicator( | ||
modifier = Modifier.align(Alignment.TopCenter), | ||
refreshing = itemState.value.reloadingProductsWithPullToRefresh, | ||
state = pullToRefreshState | ||
) | ||
} | ||
PullRefreshIndicator( | ||
modifier = Modifier.align(Alignment.TopCenter), | ||
refreshing = itemState.value.reloadingProductsWithPullToRefresh, | ||
state = pullToRefreshState | ||
) | ||
} | ||
} | ||
|
||
@Composable | ||
fun VariationsError(onRetryClicked: () -> Unit) { | ||
fun VariationsError(modifier: Modifier, onRetryClicked: () -> Unit) { | ||
Box( | ||
modifier = Modifier.fillMaxSize(), | ||
modifier = Modifier.fillMaxWidth(), | ||
contentAlignment = Alignment.Center, | ||
) { | ||
WooPosErrorScreen( | ||
modifier = Modifier.width(640.dp), | ||
modifier = modifier, | ||
message = stringResource(id = R.string.woopos_variations_loading_error_title), | ||
reason = stringResource(id = R.string.woopos_products_loading_error_message), | ||
primaryButton = Button( | ||
|
@@ -316,7 +279,6 @@ fun WooPosVariationsScreenPreview() { | |
numOfVariations = 20, | ||
), | ||
state = productState, | ||
snackbarHostState = SnackbarHostState() | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,11 +11,9 @@ import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState | |
import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.Job | ||
import kotlinx.coroutines.flow.MutableSharedFlow | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.SharingStarted | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asSharedFlow | ||
import kotlinx.coroutines.flow.stateIn | ||
import kotlinx.coroutines.launch | ||
import javax.inject.Inject | ||
|
@@ -37,11 +35,6 @@ class WooPosVariationsViewModel @Inject constructor( | |
initialValue = _viewState.value, | ||
) | ||
|
||
private val _events: MutableSharedFlow<WooPosVariationEvents> = MutableSharedFlow( | ||
extraBufferCapacity = 1 | ||
) | ||
val events = _events.asSharedFlow() | ||
|
||
private var fetchJob: Job? = null | ||
private var loadMoreJob: Job? = null | ||
|
||
|
@@ -100,18 +93,15 @@ class WooPosVariationsViewModel @Inject constructor( | |
if (!variationsDataSource.canLoadMore()) { | ||
return | ||
} | ||
_viewState.value = currentState.copy(loadingMore = true) | ||
_viewState.value = currentState.copy(loadingMore = true, errorLoadingMoreItems = false) | ||
loadMoreJob?.cancel() | ||
loadMoreJob = viewModelScope.launch { | ||
val result = variationsDataSource.loadMore(productId) | ||
if (result.isSuccess) { | ||
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. np:
|
||
Result.success(Unit) | ||
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. What does this line do? 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. This is no longer relevant as result of this change: a45bd6c |
||
if (!variationsDataSource.canLoadMore()) { | ||
_viewState.value = currentState.copy(loadingMore = false) | ||
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. Can you use currentState here? 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. This is no longer relevant as result of this change: a45bd6c |
||
} | ||
_viewState.value = currentState.copy(loadingMore = false, errorLoadingMoreItems = false) | ||
} else { | ||
_events.tryEmit(WooPosVariationEvents.PaginationError) | ||
_viewState.value = currentState.copy(loadingMore = false) | ||
_viewState.value = currentState.copy(loadingMore = false, errorLoadingMoreItems = true) | ||
} | ||
} | ||
} | ||
|
@@ -151,8 +141,4 @@ class WooPosVariationsViewModel @Inject constructor( | |
private fun onEndOfVariationsListReached(productId: Long) { | ||
loadMore(productId) | ||
} | ||
|
||
sealed class WooPosVariationEvents { | ||
data object PaginationError : WooPosVariationEvents() | ||
} | ||
} |
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.
The state defined like that can contain invalidate states, e.g.
loadingMore = true
anderrorLoadingMoreItems = true
.We probably should do something like
By the way,
ContentViewState
does not follow the naming convention of the top-level classes. It probably should beWooPosItemsViewState.
And there are a few more, e.g.,ToolbarAccessibilityLabels
, for instance. And some classes that are in theitems
package now, e.g.,WooPosBanner
should either contain an item in them or be placed in another packageThere 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.
Thanks for raising this. I had this on my list to refactor hence I kept the PR as draft. Here's the refactored state: a45bd6c