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

[Woo POS][Cash & Receipts] Order completion call. Navigation to the payment success #13086

Open
wants to merge 18 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,45 +1,38 @@
package com.woocommerce.android.ui.woopos.cashpayment

import androidx.compose.animation.core.spring
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.woocommerce.android.ui.woopos.home.HOME_ROUTE
import com.woocommerce.android.ui.woopos.root.navigation.WooPosNavigationEvent

private const val ROUTE = "cash_payment/{orderId}"
const val CASH_ROUTE_ORDER_ID_KEY = "orderId"
private const val CASH_ROUTE = "$HOME_ROUTE/cash_payment/{$CASH_ROUTE_ORDER_ID_KEY}"

fun NavController.navigateToCashPaymentScreen(orderId: Long) {
navigate("cash_payment/$orderId")
navigate(CASH_ROUTE.replace("{$CASH_ROUTE_ORDER_ID_KEY}", orderId.toString()))
}

fun NavGraphBuilder.cashPaymentScreen(
onNavigationEvent: (WooPosNavigationEvent) -> Unit
) {
composable(
route = ROUTE,
route = CASH_ROUTE,
arguments = listOf(
navArgument("orderId") { type = NavType.LongType }
navArgument(CASH_ROUTE_ORDER_ID_KEY) { type = NavType.LongType }
),
enterTransition = {
slideInHorizontally(
initialOffsetX = { fullWidth -> fullWidth },
animationSpec = spring(
dampingRatio = 0.8f,
stiffness = 200f
)
)
},
exitTransition = {
slideOutHorizontally(
targetOffsetX = { fullWidth -> fullWidth },
animationSpec = spring(
dampingRatio = 0.8f,
stiffness = 200f
)
)
},
) { backStackEntry ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
package com.woocommerce.android.ui.woopos.cashpayment

import com.woocommerce.android.model.Order
import com.woocommerce.android.model.OrderMapper
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.util.WooLog
import com.woocommerce.android.util.WooLog.T
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.wordpress.android.fluxc.model.WCOrderStatusModel
import org.wordpress.android.fluxc.store.WCGatewayStore
import org.wordpress.android.fluxc.store.WCOrderStore
import javax.inject.Inject

class WooPosCashPaymentRepository @Inject constructor(
private val selectedSite: SelectedSite,
private val orderStore: WCOrderStore,
private val orderMapper: OrderMapper,
private val gatewayStore: WCGatewayStore,
) {
suspend fun getOrderById(orderId: Long) = withContext(Dispatchers.IO) {
orderStore.getOrderByIdAndSite(orderId, selectedSite.get())?.let {
orderMapper.toAppModel(it)
}
}

suspend fun completeOrder(orderId: Long): Result<Unit> = withContext(Dispatchers.IO) {
val codGateway = gatewayStore.getGateway(selectedSite.get(), CASH_ON_DELIVERY_PAYMENT_TYPE)

val statusModel = orderStore.getOrderStatusForSiteAndKey(
selectedSite.get(),
Order.Status.Completed.value
) ?: WCOrderStatusModel(statusKey = Order.Status.Completed.value).apply {
label = statusKey
}

orderStore.updateOrderStatusAndPaymentMethod(
orderId = orderId,
site = selectedSite.get(),
newStatus = statusModel,
newPaymentMethodId = CASH_ON_DELIVERY_PAYMENT_TYPE,
codGateway?.title ?: "Pay in Person",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
codGateway?.title ?: "Pay in Person",
newPaymentMethodTitle = codGateway?.title ?: "Pay in Person",

💡 np

)
.filterIsInstance<WCOrderStore.UpdateOrderResult.RemoteUpdateResult>()
.map { result ->
if (result.event.isError) {
WooLog.e(T.POS, "Order completion failed - ${result.event.error.message}")
Result.failure(Exception(result.event.error.message))
} else {
Result.success(Unit)
}
}.first()
}

private companion object {
const val CASH_ON_DELIVERY_PAYMENT_TYPE = "cod"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ fun WooPosCashPaymentScreen(onNavigationEvent: (WooPosNavigationEvent) -> Unit)
onAmountChanged = { viewModel.onUIEvent(WooPosCashPaymentUIEvent.AmountChanged(it)) },
onCompleteOrderClicked = { viewModel.onUIEvent(WooPosCashPaymentUIEvent.CompleteOrderClicked) },
onBackClicked = { onNavigationEvent(WooPosNavigationEvent.BackFromCashPayment) },
onOrderComplete = { onNavigationEvent(WooPosNavigationEvent.OpenHomeFromCashPaymentAfterSuccessfulPayment) },
)
}

Expand All @@ -50,6 +51,7 @@ fun WooPosCashPaymentScreen(
onAmountChanged: (String) -> Unit,
onCompleteOrderClicked: () -> Unit,
onBackClicked: () -> Unit,
onOrderComplete: () -> Unit,
) {
Column(
modifier = Modifier
Expand All @@ -66,7 +68,7 @@ fun WooPosCashPaymentScreen(
)
}

WooPosCashPaymentState.Finishing -> TODO()
WooPosCashPaymentState.Complete -> onOrderComplete()
WooPosCashPaymentState.Initiating -> {
// show nothing
}
Expand All @@ -86,6 +88,7 @@ private fun Collecting(
Column(
modifier = Modifier
.width(540.dp)
.padding(32.dp)
) {
Spacer(modifier = Modifier.weight(1f))

Expand Down Expand Up @@ -133,9 +136,9 @@ private fun Collecting(
Spacer(modifier = Modifier.height(16.dp))

WooPosButton(
text = "Mark completed",
text = state.button.text,
onClick = onCompleteOrderClicked,
enabled = state.canBeOrderBeCompleted,
enabled = state.button.status == WooPosCashPaymentState.Collecting.Button.Status.ENABLED,
)

Spacer(modifier = Modifier.weight(1f))
Expand Down Expand Up @@ -196,11 +199,15 @@ fun WooPosTotalsPaymentCashScreenScreen() {
enteredAmount = "5$",
changeDue = "5$",
total = "10$",
canBeOrderBeCompleted = true,
button = WooPosCashPaymentState.Collecting.Button(
text = "Complete order",
Copy link
Collaborator

Choose a reason for hiding this comment

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

💡 To Res

status = WooPosCashPaymentState.Collecting.Button.Status.ENABLED
)
),
onAmountChanged = {},
onCompleteOrderClicked = {},
onBackClicked = {},
onOrderComplete = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,23 @@ sealed class WooPosCashPaymentState : Parcelable {
val enteredAmount: String,
val changeDue: String,
val total: String,
val canBeOrderBeCompleted: Boolean,
) : WooPosCashPaymentState()
val button: Button
) : WooPosCashPaymentState() {
@Parcelize
data class Button(
val text: String,
val status: Status,
) : Parcelable {
@Parcelize
enum class Status : Parcelable {
ENABLED,
DISABLED,
LOADING
}
}
}

object Finishing : WooPosCashPaymentState()
object Complete : WooPosCashPaymentState()

object Initiating : WooPosCashPaymentState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.woocommerce.android.ui.woopos.cashpayment
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.woocommerce.android.R
import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice
import com.woocommerce.android.viewmodel.ResourceProvider
import com.woocommerce.android.viewmodel.getStateFlow
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -15,9 +17,10 @@ import javax.inject.Inject
class WooPosCashPaymentViewModel @Inject constructor(
private val repository: WooPosCashPaymentRepository,
private val priceFormat: WooPosFormatPrice,
private val resourceProvider: ResourceProvider,
savedState: SavedStateHandle,
) : ViewModel() {
private val orderId = savedState.get<Long>("orderId")!!
private val orderId = savedState.get<Long>(CASH_ROUTE_ORDER_ID_KEY)!!

private val _state = savedState.getStateFlow<WooPosCashPaymentState>(
scope = viewModelScope,
Expand All @@ -34,15 +37,42 @@ class WooPosCashPaymentViewModel @Inject constructor(
enteredAmount = "",
changeDue = priceFormat(BigDecimal.ZERO),
total = priceFormat(order.total),
canBeOrderBeCompleted = false
button = WooPosCashPaymentState.Collecting.Button(
text = resourceProvider.getString(R.string.woopos_complete_cash_order_button),
status = WooPosCashPaymentState.Collecting.Button.Status.ENABLED
)
)
}
}

fun onUIEvent(event: WooPosCashPaymentUIEvent) {
when (event) {
is WooPosCashPaymentUIEvent.AmountChanged -> TODO()
WooPosCashPaymentUIEvent.CompleteOrderClicked -> TODO()
WooPosCashPaymentUIEvent.CompleteOrderClicked -> handleOrderCompletion()
}
}

private fun handleOrderCompletion() {
viewModelScope.launch {
val stateBeforeCompleting = _state.value as? WooPosCashPaymentState.Collecting ?: return@launch
Copy link
Collaborator

Choose a reason for hiding this comment

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

❓ Do we expect other states – shouldn't we crash? What will happen if we return here?

_state.value = stateBeforeCompleting.copy(
button = stateBeforeCompleting.button.copy(
status = WooPosCashPaymentState.Collecting.Button.Status.LOADING
)
)

val result = repository.completeOrder(orderId)
if (result.isSuccess) {
_state.value = WooPosCashPaymentState.Complete
} else {
val currentState = _state.value as? WooPosCashPaymentState.Collecting ?: return@launch
currentState
_state.value = currentState.copy(
button = currentState.button.copy(
status = WooPosCashPaymentState.Collecting.Button.Status.ENABLED
)
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
package com.woocommerce.android.ui.woopos.home

import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.woocommerce.android.ui.woopos.root.navigation.WooPosNavigationEvent

private const val HOME_ROUTE = "home"
const val HOME_ROUTE = "home"
const val HOME_PAYMENT_COMPLETED_VIA_CASH_KEY = "home_payment_completed_via_cash_key"

fun NavController.navigateToHomeScreen() {
navigate(HOME_ROUTE)
}

fun NavController.navigateToHomeScreenAfterSuccessfulCashPayment() {
previousBackStackEntry
?.savedStateHandle
?.set(HOME_PAYMENT_COMPLETED_VIA_CASH_KEY, true)

popBackStack()
}

fun NavGraphBuilder.homeScreen(
onNavigationEvent: (WooPosNavigationEvent) -> Unit
) {
Expand All @@ -33,13 +42,23 @@ fun NavGraphBuilder.homeScreen(
exitTransition = {
slideOutHorizontally(
targetOffsetX = { fullWidth -> -fullWidth },
animationSpec = spring(
dampingRatio = 0.8f,
stiffness = 200f
)
)
},
) {
WooPosHomeScreen(onNavigationEvent)
popEnterTransition = {
slideInHorizontally(
initialOffsetX = { fullWidth -> -fullWidth },
)
},
) { entry ->
val savedStateHandle = entry.savedStateHandle
val isPaymentCompletedViaCash = savedStateHandle.get<Boolean>(HOME_PAYMENT_COMPLETED_VIA_CASH_KEY) == true
if (isPaymentCompletedViaCash) {
savedStateHandle[HOME_PAYMENT_COMPLETED_VIA_CASH_KEY] = false
}

WooPosHomeScreen(
isPaymentCompletedViaCash = isPaymentCompletedViaCash,
onNavigationEvent = onNavigationEvent,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,25 @@ import com.woocommerce.android.ui.woopos.home.toolbar.WooPosFloatingToolbar
import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsScreen
import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsScreenPreview
import com.woocommerce.android.ui.woopos.root.navigation.WooPosNavigationEvent
import kotlinx.coroutines.delay
import org.wordpress.android.util.ToastUtils

@Composable
fun WooPosHomeScreen(
isPaymentCompletedViaCash: Boolean,
onNavigationEvent: (WooPosNavigationEvent) -> Unit
) {
val viewModel: WooPosHomeViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
val context = LocalContext.current

LaunchedEffect(Unit) {
if (isPaymentCompletedViaCash) {
delay(800)
viewModel.onUIEvent(WooPosHomeUIEvent.OnPaymentCompletedViaCash)
}
}

LaunchedEffect(viewModel.toastEvent) {
viewModel.toastEvent.collect { message ->
ToastUtils.showToast(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ sealed class WooPosHomeUIEvent {
data object SystemBackClicked : WooPosHomeUIEvent()
data object ExitConfirmationDialogDismissed : WooPosHomeUIEvent()
data object DismissProductsInfoDialog : WooPosHomeUIEvent()
data object OnPaymentCompletedViaCash : WooPosHomeUIEvent()
}
Loading
Loading