Skip to content
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

Open
wants to merge 41 commits into
base: trunk
Choose a base branch
from

Conversation

AnirudhBhat
Copy link
Contributor

@AnirudhBhat AnirudhBhat commented Nov 21, 2024

Closes: #12967

Description

This PR adds a new UI for retrying errors during paginating variations.

Along with this, this PR also does a couple of small changes:

In totals screen after payment is success. It navigates the screen back to items list screen so that when we click on "new order" button from the payment success screen, we end up on the items list screen instead of variations screen if we had proceeded to totals screen from variations screen.

Changes the messages which previously only mentioned only simple physical products can be added in POS. After this PR, it should say simple physical and variable products can be added. Below are the places where this got changed:

  1. Banner above the items list in items screen
  2. On clicking learn more in the banner, a dialog pops up with more information. The changes should reflect here as well
  3. When there are no products compatible for POS, we show an empty state. The changes should reflect here as well

Design

Screenshot 2024-11-19 at 7 19 03 AM

Implementation

Screenshot 2024-11-21 at 1 42 06 PM

Testing information

Retry pagination UI

  1. Navigate to POS mode (more menu -> POS)
  2. Click on any variable products that has more than 10 variations
  3. Ensure you navigate to the variations screen
  4. Turn on airplane mode
  5. Scroll down
  6. Ensure you see the pagination error UI
  7. Click on retry and ensure nothing happens as we are still in airplane mode
  8. Turn off airplane mode
  9. Now, click on retry button again
  10. Ensure the variations gets loaded

Navigate to items list after payment success

  1. Navigate to POS mode (more menu -> POS)
  2. Add products to cart
  3. Navigate to variations screen before checking out
  4. Ensure payment is success
  5. Click on "New Order" button
  6. Ensure you navigate to items list screen.

Changes to messages

  1. Navigate to POS mode (more menu -> POS)

  2. Ensure you see a banner that says both simple and variable products instead of just simple products

  3. Click on learn more in the banner

  4. Ensure the dialog also mentions both simple and variable products

  5. Navigate to POS mode where there are no compatible products to display (no simple and variable products)

  6. Ensure the empty state also says that only simple and variable products are compatible.

The tests that have been performed

Tested on both light and dark modes

Images/gif

pagination_retry_ui.mp4
Screenshot 2024-11-22 at 9 35 47 AM Screenshot 2024-11-22 at 9 35 55 AM
  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

Reviewer (or Author, in the case of optional code reviews):

Please make sure these conditions are met before approving the PR, or request changes if the PR needs improvement:

  • The PR is small and has a clear, single focus, or a valid explanation is provided in the description. If needed, please request to split it into smaller PRs.
  • Ensure Adequate Unit Test Coverage: The changes are reasonably covered by unit tests or an explanation is provided in the PR description.
  • Manual Testing: The author listed all the tests they ran, including smoke tests when needed (e.g., for refactorings). The reviewer confirmed that the PR works as expected on big (tablet) and small (phone) in case of UI changes, and no regressions are added.

@dangermattic
Copy link
Collaborator

dangermattic commented Nov 21, 2024

2 Errors
🚫 This PR is tagged with status: do not merge label(s).
🚫 PR is not assigned to a milestone.
1 Warning
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Nov 21, 2024

📲 You can test the changes from this Pull Request in WooCommerce-Wear Android by scanning the QR code below to install the corresponding build.
App Name WooCommerce-Wear Android
Platform⌚️ Wear OS
FlavorJalapeno
Build TypeDebug
Commitef53ce9
Direct Downloadwoocommerce-wear-prototype-build-pr12972-ef53ce9.apk

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Nov 21, 2024

📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App Name WooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Commitef53ce9
Direct Downloadwoocommerce-prototype-build-pr12972-ef53ce9.apk

@codecov-commenter
Copy link

codecov-commenter commented Nov 21, 2024

Codecov Report

Attention: Patch coverage is 52.77778% with 17 lines in your changes missing coverage. Please review.

Project coverage is 39.63%. Comparing base (3be8fe3) to head (e40c789).

Files with missing lines Patch % Lines
...ce/android/ui/woopos/home/items/WooPosItemsList.kt 0.00% 10 Missing ⚠️
...home/items/variations/WooPosVariationsViewModel.kt 63.15% 1 Missing and 6 partials ⚠️
Additional details and impacted files
@@                            Coverage Diff                             @@
##             issue/12846-analytics-for-variations   #12972      +/-   ##
==========================================================================
- Coverage                                   39.63%   39.63%   -0.01%     
+ Complexity                                   5973     5972       -1     
==========================================================================
  Files                                        1268     1268              
  Lines                                       73276    73285       +9     
  Branches                                    10057    10061       +4     
==========================================================================
+ Hits                                        29041    29043       +2     
- Misses                                      41656    41661       +5     
- Partials                                     2579     2581       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@AnirudhBhat AnirudhBhat added type: task An internally driven task. feature: point of sale POS project labels Nov 21, 2024
@kidinov
Copy link
Contributor

kidinov commented Nov 21, 2024

@AnirudhBhat 👋

Where does this design come from? IMO, it doesn't match expectations from this kind of UX in a mobile app.

I believe this should have:

  • The same height as a normal item
  • Just one line: icon | text | button
  • When the button is clicked, we replace that with the "loading indicator" list item till the response comes

Something like that maybe?

image

@malinajirka @samiuelson wdyt?

@AnirudhBhat
Copy link
Contributor Author

Where does this design come from? IMO, it doesn't match expectations from this kind of UX in a mobile app.

I believe this should have:

The same height as a normal item
Just one line: icon | text | button
When the button is clicked, we replace that with the "loading indicator" list item till the response comes
Something like that maybe?

Wagner gave the designs and we had conversation about this on Slack here

IMHO, I don't see any problem with this design and I don't feel it deviates from the mobile UX especially considering that this is run only on tablet devices.

  1. Clarity: It provides a clear, standalone visual for errors, which ensures users can immediately understand what went wrong.
  2. Consistency: It aligns with the rest of our app's (POS) error screens, it maintains uniformity and helps users understand that something went wrong quickly.
  3. Prominent Call to Action: The "Retry" button is clearly visible and easy to tap - as this was something we considered for POS that CTAs should be big and easily tappable considering our use case is for the POS.

If we show the error as another card with the same height as rest of the items. I can see few confusions that could arise:

  1. Clarity: On a quick glance, as the user will be scrolling, it's hard to understand what happened. Whether a new item got loaded or was it an error.
  2. Inconsistency: The way we show the error here is inconsistent with the rest of the screens on our POS system.
  3. Confusion: The card-style error display, with the same height as a list item, might create confusion by making it appear as though only one item failed to load, rather than multiple items or the entire batch.

What do you think?

…where it said we support only simple physical products.
@malinajirka
Copy link
Contributor

malinajirka commented Nov 22, 2024

I lean towards the approach Andrei mentioned (mostly because it's a standard behavior across apps on Android), but it's ultimately Wagner's decision, so I'd defer this to him.

One thing which feels broken is the animation that happens after you tap on Retry in the video - I'm not even sure, if more items loaded or if just the scrolling position changed. If we decide to go with this current approach, I think we need to polish it. If we decide to go with Andrei's approach, this problem disappears as the item will be replaced with the existing shimmer effect row so there won't be any change in the scrolling position at all.

@kidinov
Copy link
Contributor

kidinov commented Nov 22, 2024

@AnirudhBhat 👋

Clarity: It provides a clear, standalone visual for errors, which ensures users can immediately understand what went wrong.

With the item being so big, I have doubts that people will even understand what's going on, as your synthetical example is not what people will have. They will have a list of 20 or more products. They will scroll down where they will see an item with a shimmer effect, which after some time is replaced by a huge thing, but that huge thing won't be visible automatically. They will see, in most cases, just empty space at the bottom People will need to figure out that they need to scroll down more to find this "retry" button, and there is no necessity at all to have that thing so huge and so not in line with the rest of the items. On top of that the animation looks really wrong when things are that huge.

And again, this is completely not what people got used to see in mobile apps, so clarity is really questionable here

Consistency: It aligns with the rest of our app's (POS) error screens, it maintains uniformity and helps users understand that something went wrong quickly.

But this is not an error screen at all. It's a contextual error. People can easily keep using the shown products if the next page is not loaded

Prominent Call to Action: The "Retry" button is clearly visible and easy to tap - as this was something we considered for POS that CTAs should be big and easily tappable considering our use case is for the POS.

The button in the item can be as huge as we want/need

Looking into Figma design, I feel that there is a misunderstanding on the flow. In 2 out of 3 designs, there are no products shown at all. I'll ask Wagner to reconsider

@@ -8,4 +8,5 @@ interface ContentViewState {
val items: List<WooPosItem>
val loadingMore: Boolean
val reloadingProductsWithPullToRefresh: Boolean
val errorLoadingMoreItems: Boolean
Copy link
Contributor

@kidinov kidinov Nov 22, 2024

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 and errorLoadingMoreItems = true.

We probably should do something like

val paginationIndicator: PaginationIndicator

enum PaginationIndicator {
   Loading, Error, None
} 

By the way, ContentViewState does not follow the naming convention of the top-level classes. It probably should be WooPosItemsViewState. And there are a few more, e.g., ToolbarAccessibilityLabels, for instance. And some classes that are in the items package now, e.g., WooPosBanner should either contain an item in them or be placed in another package

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 raising this. I had this on my list to refactor hence I kept the PR as draft. Here's the refactored state: a45bd6c

.padding(
start = 16.dp.toAdaptivePadding(),
end = 16.dp.toAdaptivePadding(),
top = 30.dp.toAdaptivePadding(),
Copy link
Contributor

@kidinov kidinov Nov 22, 2024

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 71ee602

@@ -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) {
Result.success(Unit)
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this line do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is no longer relevant as result of this change: a45bd6c

@@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

np:

            _viewState.value = if (result.isSuccess) {
                currentState.copy(loadingMore = false, errorLoadingMoreItems = false)
            } else {
                currentState.copy(loadingMore = false, errorLoadingMoreItems = true)
            }

loadMoreJob?.cancel()
loadMoreJob = viewModelScope.launch {
val result = variationsDataSource.loadMore(productId)
if (result.isSuccess) {
Result.success(Unit)
if (!variationsDataSource.canLoadMore()) {
_viewState.value = currentState.copy(loadingMore = false)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you use currentState here? _viewState.value can be modified by the time the request comes back but you copying based on the old state

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is no longer relevant as result of this change: a45bd6c

Base automatically changed from issue/12846-analytics-for-variations to trunk November 28, 2024 06:34
@AnirudhBhat AnirudhBhat marked this pull request as ready for review November 28, 2024 07:17
@kidinov kidinov self-assigned this Nov 28, 2024
import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding

@Composable
fun WooPosPaginationErrorScreen(
Copy link
Contributor

Choose a reason for hiding this comment

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

np: maybe call it somehow closer to reality? WooPosPaginationErrorIndicator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: aa38f10

@@ -317,6 +324,17 @@ fun ProductsError(onRetryClicked: () -> Unit) {
}
}

@Composable
fun ProductsPaginationError(onRetryClicked: () -> Unit) {
Copy link
Contributor

Choose a reason for hiding this comment

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

if it's public then it should be called with WooPos...

Copy link
Contributor

Choose a reason for hiding this comment

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

And in other places the same

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 6a3b777

@@ -353,7 +371,7 @@ fun WooPosItemsScreenPreview(modifier: Modifier = Modifier) {
imageUrl = null,
),
),
loadingMore = true,
paginationState = PaginationState.Loading,
Copy link
Contributor

Choose a reason for hiding this comment

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

Shell we also have preview for the pagination error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 6bb0870

modifier = modifier
.fillMaxWidth()
.height(112.dp)
.clip(RoundedCornerShape(16.dp))
Copy link
Contributor

Choose a reason for hiding this comment

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

If the goal was to mimic the product's items, then it doesn't match. Please take a look at WooPosItemsList and copy paste the card and row setup from there

Btw, in WooPosItemsList there is naming without WooPos of a public ItemList

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 19a7ed5

verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you need 3 rows nested here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored here: 19a7ed5

WooPosPaginationErrorScreen(
message = stringResource(id = R.string.woopos_items_pagination_error),
primaryButton = Button(
text = stringResource(id = R.string.woopos_items_pagination_load_more_label),
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if it's missed but in both places it's still Failed to load more items. Please try again. with Load more, while in preview

    WooPosTheme {
        WooPosPaginationErrorScreen(
            message = "Error loading products",
            primaryButton = Button(
                text = "Load more",
                click = {}
            )
        )
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 623125f

@WooPosPreview
fun WooPosPaginationErrorScreenPreview() {
WooPosTheme {
WooPosPaginationErrorScreen(
Copy link
Contributor

Choose a reason for hiding this comment

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

I am considering whether a preview with a list or a list with outer padding would be better for development and debugging.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We already have a preview like this in WooPosItemsScreen. Let me know if I have misunderstood your query.

Copy link
Contributor

Choose a reason for hiding this comment

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

I find it a matter of personal preference, but switching between WooPosItemsScreen and the code in this file while changing card settings is not the most enjoyable experience. On top of that it takes time to rerender WooPosItemsScreen as it's huge and has multiple reviews

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 1eb6c32

}
}
is WooPosVariationsViewState.Loading -> ItemsLoadingIndicator(
minOf(10, variableProductData.numOfVariations)
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, fixed here: 8c3af25

@kidinov
Copy link
Contributor

kidinov commented Nov 28, 2024

11-28--11-37.mp4
11-28--11-42.mp4

@AnirudhBhat

some things I noticed:

  • 7 variations -> During loading still shows one loading indicator
  • 4 variations -> shows only 1 and every time you open it tries to load another page why clearly there is no
  • It actually loads something. When I simulate error it can end up with pagination error UI which seems to be doing another on click
    suspend fun loadMore(productId: Long): Result<Unit> = withContext(Dispatchers.IO) {
        if (Math.random() > 0.5) {
            delay(3000)
            Result.failure(Exception("Random error"))
        } else {
            val result = handler.loadMore(productId)
            if (result.isSuccess) {
                Result.success(Unit)
            } else {
                result.logFailure()
                Result.failure(
                    result.exceptionOrNull() ?: Exception("Unknown error while loading more variations")
                )
            }
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature: point of sale POS project status: do not merge Dependent on another PR, ready for review but not ready for merge. type: task An internally driven task.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Pos][Non-Simple Product type] Add retry button on variation screen for pagination error
6 participants