Skip to content

Conversation

@toupper
Copy link
Contributor

@toupper toupper commented Oct 23, 2025

Description

With this PR we refresh the selected POS order after the user sends the email recept and they are navigated back. We do so by passing a parameter to the back navigation event that is then read by the POS orders navigation logic.

Test Steps

  1. Go to POS
  2. Go Orders
  3. On the selected Order details, tap on Email Receipt
  4. Enter the email and tap send
  5. When that ends succesfully and you are navigated back, see how the selected order is refreshed and the email appears in details and the list

Verify also that when the email is not sent, i.e. by tapping on the back button, the orders aren't refreshed.

Images/gif

Screen_Recording_20251024_112801_Woo.Dev.mp4
  • 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.

@toupper toupper marked this pull request as draft October 23, 2025 10:48
onSendReceiptClicked = { viewModel.onUIEvent(WooPosEmailReceiptUIEvent.SendEmailClicked) },
onBackClicked = { onNavigationEvent(WooPosNavigationEvent.GoBack) },
onEmailSent = { onNavigationEvent(WooPosNavigationEvent.GoBack) }
onEmailSent = { onNavigationEvent(WooPosNavigationEvent.GoBackWithResult(key = EMAIL_RECEIPT_SENT, true)) }
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 was wondering if this logic needs to be in the view model, but given that we already have the onEmailSent callback and we don't add more model knowledge to the screen, I left it there.

popBackStack()
}

is WooPosNavigationEvent.GoBackWithResult -> {
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 considered adding a specific event like GoBackAfterReceiptSent, but I’d rather keep this generic so it’s reusable across flows. Using GoBackWithResult keeps the navigation API abstract and avoids event proliferation (“one event per case”).

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Oct 23, 2025

📲 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 NameWooCommerce-Wear Android
Platform⌚️ Wear OS
FlavorJalapeno
Build TypeDebug
Commite0ea365
Direct Downloadwoocommerce-wear-prototype-build-pr14805-e0ea365.apk

if (shouldRefresh) {
viewModel.onRefresh()
// clear so it doesn't retrigger on recomposition / process death restore
entry.savedStateHandle[EMAIL_RECEIPT_REFRESH_ORDERS] = false
Copy link
Contributor

@kidinov kidinov Oct 23, 2025

Choose a reason for hiding this comment

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

Probably we should remove this from the bundel, not set to false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that's better, done in 02c24c8

import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTypography
import com.woocommerce.android.ui.woopos.root.navigation.WooPosNavigationEvent

const val EMAIL_RECEIPT_SENT = "email_receipt_sent"
Copy link
Contributor

Choose a reason for hiding this comment

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

I would consider placing this in the navigation because I believe it’s semantically related to that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's true, done in ddf806d

val viewModel: WooPosOrdersViewModel = hiltViewModel()
val state by viewModel.state.collectAsState()

val entry = remember(navController) { navController.currentBackStackEntry }
Copy link
Contributor

@kidinov kidinov Oct 23, 2025

Choose a reason for hiding this comment

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

Have you considered not passing navController inside the screen but passing the value to the screen from navigation composable? Both, I think, are fine, but separating the navigation part from the actual screen may be a bit cleaner.

Example:

@Composable
fun MyNavHost() {
    val navController = rememberNavController()
    
    NavHost(navController = navController, startDestination = "screenA") {
        composable("screenA") { backStackEntry ->
            val result = backStackEntry.savedStateHandle
                .getStateFlow<String?>("selected_item", null)
                .collectAsState()
            
            ScreenA(
                selectedItem = result.value,
                onNavigateToScreenB = {
                    navController.navigate("screenB")
                }
            )
        }
        
        composable("screenB") {
            ScreenB(
                onItemSelected = { item ->
                    navController.previousBackStackEntry
                        ?.savedStateHandle
                        ?.set("selected_item", item)
                    navController.popBackStack()
                }
            )
        }
    }
}

@Composable
fun ScreenA(selectedItem: String?, onNavigateToScreenB: () -> Unit) {
    Column {
        Text("Selected: ${selectedItem ?: "None"}")
        Button(onClick = onNavigateToScreenB) {
            Text("Go to Screen B")
        }
    }
}

@Composable
fun ScreenB(onItemSelected: (String) -> Unit) {
    Column {
        Button(onClick = { onItemSelected("Item 1") }) {
            Text("Select Item 1")
        }
        Button(onClick = { onItemSelected("Item 2") }) {
            Text("Select Item 2")
        }
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's cleaner that way, done in 68e10c3

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Oct 23, 2025

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

App NameWooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Commite0ea365
Direct Downloadwoocommerce-prototype-build-pr14805-e0ea365.apk

@toupper toupper added type: task An internally driven task. feature: POS labels Oct 23, 2025
@toupper toupper added this to the 23.6 milestone Oct 23, 2025
@toupper toupper marked this pull request as ready for review October 23, 2025 14:23
@toupper toupper requested a review from kidinov October 23, 2025 14:23
@kidinov kidinov self-assigned this Oct 23, 2025
import com.woocommerce.android.ui.woopos.root.navigation.WooPosNavigationEvent
import com.woocommerce.android.ui.woopos.root.navigation.navigateOnce

const val EMAIL_RECEIPT_SENT = "email_receipt_sent"
Copy link
Contributor

Choose a reason for hiding this comment

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

np: I'd recommend naming it EMAIL_RECEIPT_SENT_KEY - for clarity

@codecov-commenter
Copy link

codecov-commenter commented Oct 23, 2025

Codecov Report

❌ Patch coverage is 49.15254% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.26%. Comparing base (3d4c7df) to head (e0ea365).

Files with missing lines Patch % Lines
...os/root/navigation/WooPosNavigationEventHandler.kt 0.00% 13 Missing ⚠️
.../android/ui/woopos/orders/WooPosOrdersViewModel.kt 66.66% 0 Missing and 7 partials ⚠️
...android/ui/woopos/orders/WooPosOrdersNavigation.kt 0.00% 6 Missing ⚠️
...android/ui/woopos/orders/WooPosOrdersDataSource.kt 87.50% 1 Missing and 1 partial ⚠️
...ui/woopos/root/navigation/WooPosNavigationEvent.kt 0.00% 1 Missing ⚠️
...ndroid/ui/woopos/root/navigation/WooPosRootHost.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##              trunk   #14805      +/-   ##
============================================
+ Coverage     38.25%   38.26%   +0.01%     
- Complexity    10080    10088       +8     
============================================
  Files          2132     2132              
  Lines        120683   120735      +52     
  Branches      16543    16556      +13     
============================================
+ Hits          46170    46202      +32     
- Misses        69831    69843      +12     
- Partials       4682     4690       +8     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kidinov kidinov self-requested a review October 23, 2025 14:55
Copy link
Contributor

@kidinov kidinov left a comment

Choose a reason for hiding this comment

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

The approach LGTM! But:

  • Notice that it doesn't work and the email is not actually appeared neither on the list or in the order details
  • The selection of the order is lost
  • I doubt that we need to show the PTR indicator. Check the iOS UX for reference.

Also, there is an issue not related to the changes in the PR inWooPosEmailReceiptRepository::sendReceiptByEmail there is an assumption that the order is stored in the database, which may not be the case when you open the POS after a clean install; therefore, sending fails. So:

  • We either need to take the order from the in-memory cache, but correct me if I am wrong, it contains just 1 page of orders at max
  • We need to fetch the order from the remote if it's not in the database.

@toupper
Copy link
Contributor Author

toupper commented Oct 24, 2025

Thanks for the comment @kidinov!

Notice that it doesn't work and the email is not actually appeared neither on the list or in the order details
The selection of the order is lost
I doubt that we need to show the PTR indicator. Check the iOS UX for reference.

After merging from trunk, the order details are now displayed correctly.
Most importantly, after discussing with @staskus, we agreed that we don’t necessarily need to maintain full technical parity with iOS.

On iOS, the selected order is reloaded directly from the Email Receipt screen, but in our case, I’ve kept that logic within the POS Orders screen. This approach has the added benefit of making the email receipt action faster.

In this PR:
• We refresh only the selected order.
• We don’t show the Pull-to-Refresh indicator (since the user didn’t trigger it manually).
• Instead, we show a subtle LinearProgressIndicator to communicate that the order details are being refreshed.

Let me know what you think.

Also, there is an issue not related to the changes in the PR inWooPosEmailReceiptRepository::sendReceiptByEmail there is an assumption that the order is stored in the database, which may not be the case when you open the POS after a clean install; therefore, sending fails. So:

We either need to take the order from the in-memory cache, but correct me if I am wrong, it contains just 1 page of orders at max
We need to fetch the order from the remote if it's not in the database.

Good catch! Let's tackle that in a separate PR 👍

@toupper toupper requested a review from kidinov October 24, 2025 10:06
@toupper
Copy link
Contributor Author

toupper commented Oct 24, 2025

Also, there is an issue not related to the changes in the PR inWooPosEmailReceiptRepository::sendReceiptByEmail there is an assumption that the order is stored in the database, which may not be the case when you open the POS after a clean install; therefore, sending fails. So:

We either need to take the order from the in-memory cache, but correct me if I am wrong, it contains just 1 page of orders at max
We need to fetch the order from the remote if it's not in the database.

Done in #14817

…fresh-after

# Conflicts:
#	WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/orders/WooPosOrdersScreen.kt
suspend fun loadMore(searchQuery: String? = null): Result<List<Order>> =
withContext(Dispatchers.IO) { loadNextPage(searchQuery) }

suspend fun refreshOrderById(orderId: Long): Result<Order> {
Copy link
Contributor

@kidinov kidinov Oct 28, 2025

Choose a reason for hiding this comment

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

You don't update the cache, so if I understand correctly, after returning back to this screen, the order will be displayed without the updated email as cached data firstly used

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, fixed it in 9052df4

}
}

fun onBackFromSuccesfullySendingEmailReceipt() {
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a typo in succesfully

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! Done in 7f696e1

Spacer(modifier = Modifier.height(WooPosSpacing.Small.value))

AnimatedVisibility(visible = state.isRefreshingSelectedDetails) {
LinearProgressIndicator(
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't have a Woo POS-specific linear progress component, and the default one uses colors that do not match the POS colors or style. I'd suggest not having any loading element at all. It'll simplify the code and won't bother merchants with not really clear what and why reloading indicator

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice, let's remove it then. Done in 7ca6800

}
}

private suspend fun applyOrderUpdate(updated: Order) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably can be simplified to

    private suspend fun applyOrderUpdate(updated: Order) {
        val current = _state.value as? WooPosOrdersState.Content ?: return
        val loaded = current.items as? WooPosOrdersState.Content.Items.Loaded ?: return

        val selectedId = loaded.items.keys.firstOrNull { it.isSelected }?.id
        val newItem = mapOrderItem(updated, selectedId)
        val newDetails = mapOrderDetails(updated)

        val newMap = loaded.items.entries.associate { (item, details) ->
            if (item.id == updated.id) newItem to newDetails else item to details
        }

        _state.value = current.copy(
            items = WooPosOrdersState.Content.Items.Loaded(newMap),
            selectedDetails = if (selectedId == updated.id) newDetails else current.selectedDetails
        )
    }

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, changed in e829d88

Copy link
Contributor

@kidinov kidinov left a comment

Choose a reason for hiding this comment

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

It looks good to me; I'd just really consider removing the loading indicator, as this is more of a side effect of the temporary architecture we are currently using, and I think it's fine to not show it at the moment. If you think the loading indicator is needed, I think we need to consider using something else, not the one currently used, as I think it's not good for the current POS design language.

Also, there are a few minor suggestions - please take a look!

kidinov and others added 2 commits October 28, 2025 14:07
…fresh-after

# Conflicts:
#	WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/navigation/WooPosNavigationEventHandler.kt
@dangermattic
Copy link
Collaborator

1 Warning
⚠️ This PR is assigned to the milestone 23.6. This milestone is due in less than 2 days.
Please make sure to get it merged by then or assign it to a milestone with a later deadline.

Generated by 🚫 Danger

@toupper toupper merged commit f8bb8f0 into trunk Oct 29, 2025
17 checks passed
@toupper toupper deleted the feat/WOOMOB-1154-POS-Order-details-email-refresh-after branch October 29, 2025 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: POS type: task An internally driven task.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants