From 67b086662d2050fb69a57a242e4555a60f8851af Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 21 Nov 2024 19:10:21 +0100 Subject: [PATCH 001/132] Remove custom `if (isPos)` from controller This allows observing all the states in POS, without the dialog-based UI. Previously custom `if` statement was used to avoid showing (duplicate) success dialog. --- .../controller/CardReaderPaymentController.kt | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentController.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentController.kt index 61b5b3960f0..3ae2739a51f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentController.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentController.kt @@ -123,10 +123,6 @@ class CardReaderPaymentController( private var refetchOrderJob: Job? = null - private val CardReaderFlowParam.PaymentOrRefund.isPOS: Boolean - get() = this is CardReaderFlowParam.PaymentOrRefund.Payment && - this.paymentType == CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType.WOO_POS - private val _event: MutableSharedFlow = MutableSharedFlow() val event: Flow = _event @@ -459,20 +455,10 @@ class CardReaderPaymentController( ) { paymentReceiptHelper.storeReceiptUrl(orderId, paymentStatus.receiptUrl) appPrefs.setCardReaderSuccessfulPaymentTime() - if (paymentOrRefund.isPOS) { - scope.launch { - syncOrderStatus(orderId) - triggerEvent(CardReaderPaymentEvent.Exit) - } - } else { - triggerEvent(CardReaderPaymentEvent.PlaySuccessfulPaymentSound) - showPaymentSuccessfulState() - reFetchOrder() - } - } - private suspend fun syncOrderStatus(orderId: Long) { - orderRepository.fetchOrderById(orderId) + triggerEvent(CardReaderPaymentEvent.PlaySuccessfulPaymentSound) + showPaymentSuccessfulState() + reFetchOrder() } @VisibleForTesting From 3c6b36434ffeb7fa0f8b07efdde012997735d8a3 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 21 Nov 2024 19:11:24 +0100 Subject: [PATCH 002/132] Display `paymentState`s in Totals pane in POS --- .../woopos/home/totals/WooPosTotalsScreen.kt | 6 + .../home/totals/WooPosTotalsViewModel.kt | 129 +++++++++++++++++- .../home/totals/WooPosTotalsViewState.kt | 7 +- 3 files changed, 136 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 6d9ad6b3c69..ac48ec755ed 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -138,6 +138,11 @@ private fun TotalsLoaded( TotalsGrid(state) Spacer(modifier = Modifier.weight(1f)) + + Text( + text = state.paymentStateText, + style = MaterialTheme.typography.body1, + ) } AnimatedVisibility(visible = isButtonVisible) { @@ -281,6 +286,7 @@ fun WooPosTotalsScreenPreview(modifier: Modifier = Modifier) { orderSubtotalText = "$420.00", orderTotalText = "$462.00", orderTaxText = "$42.00", + paymentStateText = "Payment state" ), onUIEvent = {} ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 194da209f86..9b67ea13942 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -1,11 +1,33 @@ package com.woocommerce.android.ui.woopos.home.totals import android.os.Parcelable +import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.woocommerce.android.AppPrefs import com.woocommerce.android.R +import com.woocommerce.android.cardreader.CardReaderManager import com.woocommerce.android.model.Order +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.orders.details.OrderDetailRepository +import com.woocommerce.android.ui.payments.cardreader.CardReaderCountryConfigProvider +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingChecker +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracRefundErrorMapper +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracRefundableChecker +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentStateProvider +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderTrackCanceledFlow +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare +import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper +import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentStatus import com.woocommerce.android.ui.woopos.home.ChildToParentEvent @@ -16,6 +38,8 @@ import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice +import com.woocommerce.android.util.CoroutineDispatchers +import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.WooLog import com.woocommerce.android.util.WooLog.T import com.woocommerce.android.viewmodel.ResourceProvider @@ -25,8 +49,11 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize +import org.wordpress.android.fluxc.store.WooCommerceStore import javax.inject.Inject +private const val KEY_TTP_PAYMENT_IN_PROGRESS = "ttp_payment_in_progress" + @HiltViewModel class WooPosTotalsViewModel @Inject constructor( private val resourceProvider: ResourceProvider, @@ -37,7 +64,29 @@ class WooPosTotalsViewModel @Inject constructor( private val priceFormat: WooPosFormatPrice, private val analyticsTracker: WooPosAnalyticsTracker, private val networkStatus: WooPosNetworkStatus, - savedState: SavedStateHandle, + + private val cardReaderManager: CardReaderManager, + private val orderRepository: OrderDetailRepository, + private val selectedSite: SelectedSite, + private val appPrefs: AppPrefs = AppPrefs, + private val paymentCollectibilityChecker: CardReaderPaymentCollectibilityChecker, + private val interacRefundableChecker: CardReaderInteracRefundableChecker, + private val tracker: PaymentsFlowTracker, + private val trackCancelledFlow: CardReaderTrackCanceledFlow, + private val currencyFormatter: CurrencyFormatter, + private val errorMapper: CardReaderPaymentErrorMapper, + private val interacRefundErrorMapper: CardReaderInteracRefundErrorMapper, + private val wooStore: WooCommerceStore, + private val dispatchers: CoroutineDispatchers, + private val cardReaderTrackingInfoKeeper: CardReaderTrackingInfoKeeper, + private val paymentStateProvider: CardReaderPaymentStateProvider, + private val cardReaderPaymentOrderHelper: CardReaderPaymentOrderHelper, + private val paymentReceiptHelper: PaymentReceiptHelper, + private val cardReaderOnboardingChecker: CardReaderOnboardingChecker, + private val cardReaderConfigProvider: CardReaderCountryConfigProvider, + private val paymentReceiptShare: PaymentReceiptShare, + + private val savedState: SavedStateHandle, ) : ViewModel() { private companion object { @@ -60,6 +109,43 @@ class WooPosTotalsViewModel @Inject constructor( key = KEY_STATE, ) + private var isTTPPaymentInProgress: Boolean + get() = savedState.get(KEY_TTP_PAYMENT_IN_PROGRESS) == true + set(value) { + savedState[KEY_TTP_PAYMENT_IN_PROGRESS] = value + } + + private var cardReaderPaymentController: CardReaderPaymentController? = null + + private fun createCardReaderPaymentController(orderId: Long) { + cardReaderPaymentController = CardReaderPaymentController( + scope = viewModelScope, + cardReaderManager = cardReaderManager, + orderRepository = orderRepository, + selectedSite = selectedSite, + appPrefs = appPrefs, + paymentCollectibilityChecker = paymentCollectibilityChecker, + interacRefundableChecker = interacRefundableChecker, + tracker = tracker, + trackCancelledFlow = trackCancelledFlow, + currencyFormatter = currencyFormatter, + errorMapper = errorMapper, + interacRefundErrorMapper = interacRefundErrorMapper, + wooStore = wooStore, + dispatchers = dispatchers, + cardReaderTrackingInfoKeeper = cardReaderTrackingInfoKeeper, + paymentStateProvider = paymentStateProvider, + cardReaderPaymentOrderHelper = cardReaderPaymentOrderHelper, + paymentReceiptHelper = paymentReceiptHelper, + cardReaderOnboardingChecker = cardReaderOnboardingChecker, + cardReaderConfigProvider = cardReaderConfigProvider, + paymentReceiptShare = paymentReceiptShare, + paymentOrRefund = PaymentOrRefund.Payment(orderId = orderId, paymentType = PaymentOrRefund.Payment.PaymentType.WOO_POS), + cardReaderType = CardReaderType.EXTERNAL, + isTTPPaymentInProgress = ::isTTPPaymentInProgress, + ) + } + init { listenUpEvents() listenToPaymentsStatus() @@ -103,6 +189,7 @@ class WooPosTotalsViewModel @Inject constructor( } is ParentToChildrenEvent.BackFromCheckoutToCartClicked -> { + cardReaderPaymentController?.onBackPressed() uiState.value = InitialState } @@ -118,10 +205,18 @@ class WooPosTotalsViewModel @Inject constructor( cardReaderFacade.paymentStatus.collect { status -> when (status) { is WooPosCardReaderPaymentStatus.Success -> { + // ready to collect payment + Log.d("WooPosTotalsViewModel", "Ready to collect payment") val state = uiState.value check(state is WooPosTotalsViewState.Totals) - uiState.value = WooPosTotalsViewState.PaymentSuccess(orderTotalText = state.orderTotalText) - childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + val orderId = dataState.value.orderId + check(orderId != EMPTY_ORDER_ID) + check(uiState.value is WooPosTotalsViewState.Totals) + createCardReaderPaymentController(dataState.value.orderId) + cardReaderPaymentController?.start() + listenToPaymentController() +// uiState.value = WooPosTotalsViewState.PaymentSuccess(orderTotalText = state.orderTotalText) +// childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } is WooPosCardReaderPaymentStatus.Failure, is WooPosCardReaderPaymentStatus.Unknown -> Unit @@ -130,6 +225,29 @@ class WooPosTotalsViewModel @Inject constructor( } } + private fun listenToPaymentController() { + viewModelScope.launch { + cardReaderPaymentController?.paymentState?.collect { paymentState -> + Log.d("WooPosTotalsViewModel", "state: ${paymentState.javaClass.simpleName}") + val totalsState = uiState.value + if (totalsState is WooPosTotalsViewState.Totals) { + uiState.value = totalsState.copy( + paymentStateText = paymentState.javaClass.simpleName + ) + } + if (paymentState is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentSuccessful) { + uiState.value = WooPosTotalsViewState.PaymentSuccess(orderTotalText = paymentState.amountWithCurrencyLabel) + childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + } + } + } + viewModelScope.launch { + cardReaderPaymentController?.event?.collect { event -> + Log.d("WooPosTotalsViewModel", "event: ${event.javaClass.simpleName}") + } + } + } + private fun createOrderDraft(productIds: List) { viewModelScope.launch { uiState.value = WooPosTotalsViewState.Loading @@ -167,9 +285,14 @@ class WooPosTotalsViewModel @Inject constructor( orderSubtotalText = priceFormat(subtotalAmount), orderTaxText = priceFormat(taxAmount), orderTotalText = priceFormat(totalAmount), + paymentStateText = "" ) } + override fun onCleared() { + cardReaderPaymentController?.onCleared() + } + @Parcelize private data class TotalsDataState( val orderId: Long = EMPTY_ORDER_ID, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index 23a78af03d4..04cf8b9b710 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -8,9 +8,10 @@ sealed class WooPosTotalsViewState : Parcelable { data object Loading : WooPosTotalsViewState() data class Totals( - var orderSubtotalText: String, - var orderTaxText: String, - var orderTotalText: String, + val orderSubtotalText: String, + val orderTaxText: String, + val orderTotalText: String, + val paymentStateText: String, ) : WooPosTotalsViewState() data class PaymentSuccess(var orderTotalText: String) : WooPosTotalsViewState() From e3ef8cd577c20ebd6bc2c09991f735eca7258473 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 21 Nov 2024 19:15:59 +0100 Subject: [PATCH 003/132] Remove POS payment failure handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POS-specific payment failure handling is not applicable, because we're now observing payments states using `CardReaderPaymentController.paymentState`. In the dialog-based Payment flow, the result payment result is passed here — to the SelectPaymentMethod screen (start of the payment flow nav graph). --- .../SelectPaymentMethodViewModel.kt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt index 23d938de246..4fd1ca6146d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt @@ -325,6 +325,10 @@ class SelectPaymentMethodViewModel @Inject constructor( flow = cardReaderPaymentFlowParam.toAnalyticsFlowName(), ) } + if (cardReaderPaymentFlowParam.paymentType == WOO_POS) { + val result = if (connected) ReturnResultToWooPos.Success else ReturnResultToWooPos.Failure + triggerEvent(result) + } } fun onCardReaderPaymentCompleted() { @@ -342,19 +346,10 @@ class SelectPaymentMethodViewModel @Inject constructor( source = AnalyticsTracker.VALUE_SIMPLE_PAYMENTS_SOURCE_PAYMENT_METHOD, flow = cardReaderPaymentFlowParam.toAnalyticsFlowName(), ) - handleWooPosPaymentFailure() } } } - private fun handleWooPosPaymentFailure() { - // In case payment was initiated from the Woo POS mode, we need to propagate the payment - // result back, to close the SelectPaymentMethodFragment and handle failure on the Woo POS end. - if (cardReaderPaymentFlowParam.paymentType == WOO_POS) { - triggerEvent(ReturnResultToWooPos.Failure) - } - } - fun onScanToPayClicked() { launch { trackPaymentMethodSelection(AnalyticsTracker.VALUE_SCAN_TO_PAY_PAYMENT_FLOW) @@ -478,7 +473,7 @@ class SelectPaymentMethodViewModel @Inject constructor( SIMPLE -> NavigateBackToHub(CardReadersHub()) TRY_TAP_TO_PAY -> NavigateToTapToPaySummary(order.first()) ORDER, ORDER_CREATION -> NavigateBackToOrderList(order.first()) - WOO_POS -> ReturnResultToWooPos.Success + WOO_POS -> ReturnResultToWooPos.Success // TODO: throw illegal state exception } ) } From d694e4d1bd641679234b3744c5a0aa71ea1d8d17 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 21 Nov 2024 19:16:57 +0100 Subject: [PATCH 004/132] Return result to POS before opening Payment dialog --- .../CardReaderStatusCheckerDialogFragment.kt | 20 ++++++++++++++ .../CardReaderStatusCheckerViewModel.kt | 26 +++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt index df97bdf96d1..f07070a1150 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt @@ -8,6 +8,9 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.woocommerce.android.R import com.woocommerce.android.ui.payments.PaymentsBaseDialogFragment +import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnResultToWooPos +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentStatus import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -66,8 +69,25 @@ class CardReaderStatusCheckerDialogFragment : PaymentsBaseDialogFragment(R.layou ) ) } + is ReturnResultToWooPos -> { + parentFragmentManager.setFragmentResult( + WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, + Bundle().apply { + putParcelable( + WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_RESULT_KEY, + event.asWooPosCardReaderPaymentResult(), + ) + } + ) + } else -> event.isHandled = false } } } + + fun ReturnResultToWooPos.asWooPosCardReaderPaymentResult() = + when (this) { + is ReturnResultToWooPos.Success -> WooPosCardReaderPaymentStatus.Success + is ReturnResultToWooPos.Failure -> WooPosCardReaderPaymentStatus.Failure + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt index 936ef06809b..b6505a3ceca 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt @@ -8,6 +8,7 @@ import com.woocommerce.android.cardreader.connection.CardReader import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected import com.woocommerce.android.cardreader.connection.ReaderType import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Payment import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingChecker import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingParams import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingState @@ -15,6 +16,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.NavigateToConnection +import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnResultToWooPos import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ScopedViewModel @@ -38,12 +40,17 @@ class CardReaderStatusCheckerViewModel override val _event = SingleLiveEvent() override val event: LiveData = _event + private val CardReaderFlowParam.isPOS: Boolean + get() = this is Payment && paymentType == Payment.PaymentType.WOO_POS || + this is CardReaderFlowParam.WooPosConnection + init { launch { checkStatus() } } + @Suppress("NestedBlockDepth") private suspend fun checkStatus() { when (val param = arguments.cardReaderFlowParam) { is CardReaderFlowParam.CardReadersHub -> triggerEvent( @@ -58,12 +65,16 @@ class CardReaderStatusCheckerViewModel if (cardReaderStatus.cardReader.toCardReaderType() != arguments.cardReaderType) { handleNotSelectedReaderTypeConnected(param) } else { - triggerEvent( - StatusCheckerEvent.NavigateToPayment( - param, - cardReaderStatus.cardReader.toCardReaderType() + if (param.isPOS) { + triggerEvent(ReturnResultToWooPos.Success) + } else { + triggerEvent( + StatusCheckerEvent.NavigateToPayment( + param, + cardReaderStatus.cardReader.toCardReaderType() + ) ) - ) + } } } else { handleOnboardingStatus(param) @@ -127,5 +138,10 @@ class CardReaderStatusCheckerViewModel val cardReaderOnboardingParams: CardReaderOnboardingParams, val cardReaderType: CardReaderType, ) : MultiLiveEvent.Event() + + sealed class ReturnResultToWooPos : MultiLiveEvent.Event() { + data object Success : ReturnResultToWooPos() + data object Failure : ReturnResultToWooPos() + } } } From bcfb6515242b26728097055e8abf2d29bd9a42a6 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 21 Nov 2024 19:18:45 +0100 Subject: [PATCH 005/132] Pass reader connection result (success/failure) to POS The new result is designed both for reader "connection" and "prepare for payment" actions. --- .../CardReaderConnectDialogFragment.kt | 18 ++++++-- .../connect/CardReaderConnectEvent.kt | 5 ++- .../connect/CardReaderConnectViewModel.kt | 41 +++++++++++-------- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt index 53ffecb521f..aaf9dcacf7a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt @@ -45,6 +45,7 @@ import com.woocommerce.android.ui.payments.cardreader.connect.adapter.MultipleCa import com.woocommerce.android.ui.payments.cardreader.update.CardReaderUpdateDialogFragment import com.woocommerce.android.ui.payments.cardreader.update.CardReaderUpdateViewModel.UpdateResult import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentStatus import com.woocommerce.android.util.ChromeCustomTabUtils import com.woocommerce.android.util.LocationUtils import com.woocommerce.android.util.UiHelpers @@ -256,10 +257,15 @@ class CardReaderConnectDialogFragment : PaymentsBaseDialogFragment(R.layout.card result = event.data as Boolean, ) } - is CardReaderConnectEvent.PopBackStackForWooPOS -> { + is CardReaderConnectEvent.CardReaderPrepareForPaymentResult -> { parentFragmentManager.setFragmentResult( - WooPosCardReaderActivity.WOO_POS_CARD_CONNECTION_REQUEST_KEY, - Bundle(), + WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, + Bundle().apply { + putParcelable( + WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_RESULT_KEY, + event.asWooPosCardReaderPaymentResult(), + ) + } ) } is CardReaderConnectEvent.ShowToast -> @@ -277,6 +283,12 @@ class CardReaderConnectDialogFragment : PaymentsBaseDialogFragment(R.layout.card } } + fun CardReaderConnectEvent.CardReaderPrepareForPaymentResult.asWooPosCardReaderPaymentResult() = + when (this) { + CardReaderConnectEvent.CardReaderPrepareForPaymentResult.Failure -> WooPosCardReaderPaymentStatus.Failure + CardReaderConnectEvent.CardReaderPrepareForPaymentResult.Success -> WooPosCardReaderPaymentStatus.Success + } + private fun updateMultipleReadersFoundRecyclerView( binding: CardReaderConnectDialogBinding, viewState: CardReaderConnectViewState diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt index 87de39f23d7..5bdc9b324e5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt @@ -47,5 +47,8 @@ sealed class CardReaderConnectEvent : MultiLiveEvent.Event() { data class OpenGenericWebView(val url: String) : CardReaderConnectEvent() - data object PopBackStackForWooPOS : CardReaderConnectEvent() + sealed class CardReaderPrepareForPaymentResult : MultiLiveEvent.Event() { + data object Success : CardReaderPrepareForPaymentResult() + data object Failure : CardReaderPrepareForPaymentResult() + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt index 1abbf6ba085..50b3d3e67fb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt @@ -27,6 +27,7 @@ import com.woocommerce.android.model.UiString.UiStringRes import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.payments.cardreader.LearnMoreUrlProvider import com.woocommerce.android.ui.payments.cardreader.LearnMoreUrlProvider.LearnMoreUrlType.IN_PERSON_PAYMENTS +import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.CardReaderPrepareForPaymentResult import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.CheckBluetoothEnabled import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.CheckBluetoothPermissionsGiven import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.CheckLocationEnabled @@ -35,7 +36,6 @@ import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectE import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.OpenLocationSettings import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.OpenPermissionsSettings import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.OpenWPComWebView -import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.PopBackStackForWooPOS import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.RequestBluetoothRuntimePermissions import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.RequestEnableBluetooth import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.RequestLocationPermissions @@ -59,9 +59,7 @@ import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectV import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectViewState.MultipleExternalReadersFoundState import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectViewState.ScanningFailedState import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam -import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.CardReadersHub import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Payment -import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Refund import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingChecker import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL @@ -120,6 +118,10 @@ class CardReaderConnectViewModel @Inject constructor( val viewStateData: LiveData = viewState + private val CardReaderFlowParam.isPOS: Boolean + get() = this is Payment && paymentType == Payment.PaymentType.WOO_POS || + this is CardReaderFlowParam.WooPosConnection + init { startFlow() } @@ -527,25 +529,28 @@ class CardReaderConnectViewModel @Inject constructor( } private fun exitFlow(connected: Boolean) { - if (!connected) { - when (val param = arguments.cardReaderFlowParam) { - is CardReadersHub, is Refund -> triggerEvent(ExitWithResult(false)) - is Payment -> { - if (param.paymentType == Payment.PaymentType.WOO_POS) { - returnToWooPos() - } else { - triggerEvent(ExitWithResult(false)) - } - } - CardReaderFlowParam.WooPosConnection -> returnToWooPos() - } + val param = arguments.cardReaderFlowParam + if (param.isPOS) { + returnToWooPos(connected) + } else if (!connected) { + triggerEvent(ExitWithResult(false)) } else { - triggerEvent(ShowCardReaderTutorial(arguments.cardReaderFlowParam, arguments.cardReaderType)) + triggerEvent( + ShowCardReaderTutorial( + arguments.cardReaderFlowParam, + arguments.cardReaderType + ) + ) } } - private fun returnToWooPos() { - triggerEvent(PopBackStackForWooPOS) + private fun returnToWooPos(connected: Boolean) { + val event = if (connected) { + CardReaderPrepareForPaymentResult.Success + } else { + CardReaderPrepareForPaymentResult.Failure + } + triggerEvent(event) } private fun storeConnectedReader(cardReader: CardReader) { From 9f853ff5d89b4d38d4293a01c5f15ea425e1eae3 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 10:56:19 +0100 Subject: [PATCH 006/132] Throw `IllegalStateException` if POS uses old IPP flow --- .../ui/payments/methodselection/SelectPaymentMethodViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt index 4fd1ca6146d..7aff55357bc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt @@ -473,7 +473,7 @@ class SelectPaymentMethodViewModel @Inject constructor( SIMPLE -> NavigateBackToHub(CardReadersHub()) TRY_TAP_TO_PAY -> NavigateToTapToPaySummary(order.first()) ORDER, ORDER_CREATION -> NavigateBackToOrderList(order.first()) - WOO_POS -> ReturnResultToWooPos.Success // TODO: throw illegal state exception + WOO_POS -> error("Woo POS is expected to use CardReaderPaymentController directly") } ) } From 3593cd19c7342bc74846c898c5500149ad8b79ce Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 12:07:46 +0100 Subject: [PATCH 007/132] Update tests in `WooPosTotalsViewModelTest` --- .../home/totals/WooPosTotalsViewModel.kt | 6 +- .../home/totals/WooPosTotalsViewModelTest.kt | 112 ++++++++++++++---- .../ui/woopos/util/WooPosCoroutineTestRule.kt | 3 + 3 files changed, 95 insertions(+), 26 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 9b67ea13942..c8db15be55b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -206,7 +206,6 @@ class WooPosTotalsViewModel @Inject constructor( when (status) { is WooPosCardReaderPaymentStatus.Success -> { // ready to collect payment - Log.d("WooPosTotalsViewModel", "Ready to collect payment") val state = uiState.value check(state is WooPosTotalsViewState.Totals) val orderId = dataState.value.orderId @@ -215,8 +214,6 @@ class WooPosTotalsViewModel @Inject constructor( createCardReaderPaymentController(dataState.value.orderId) cardReaderPaymentController?.start() listenToPaymentController() -// uiState.value = WooPosTotalsViewState.PaymentSuccess(orderTotalText = state.orderTotalText) -// childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } is WooPosCardReaderPaymentStatus.Failure, is WooPosCardReaderPaymentStatus.Unknown -> Unit @@ -228,7 +225,6 @@ class WooPosTotalsViewModel @Inject constructor( private fun listenToPaymentController() { viewModelScope.launch { cardReaderPaymentController?.paymentState?.collect { paymentState -> - Log.d("WooPosTotalsViewModel", "state: ${paymentState.javaClass.simpleName}") val totalsState = uiState.value if (totalsState is WooPosTotalsViewState.Totals) { uiState.value = totalsState.copy( @@ -243,7 +239,7 @@ class WooPosTotalsViewModel @Inject constructor( } viewModelScope.launch { cardReaderPaymentController?.event?.collect { event -> - Log.d("WooPosTotalsViewModel", "event: ${event.javaClass.simpleName}") + } } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index dffaff896ea..c3755944e45 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -2,7 +2,28 @@ package com.woocommerce.android.ui.woopos.home.totals import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.AppPrefs +import com.woocommerce.android.cardreader.CardReaderManager +import com.woocommerce.android.cardreader.connection.CardReaderStatus +import com.woocommerce.android.cardreader.connection.event.BluetoothCardReaderMessages +import com.woocommerce.android.cardreader.connection.event.CardReaderBatteryStatus +import com.woocommerce.android.cardreader.payments.CardPaymentStatus import com.woocommerce.android.model.Order +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.orders.details.OrderDetailRepository +import com.woocommerce.android.ui.payments.cardreader.CardReaderCountryConfigProvider +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingChecker +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracRefundErrorMapper +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracRefundableChecker +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentStateProvider +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderTrackCanceledFlow +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare +import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper +import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentStatus import com.woocommerce.android.ui.woopos.home.ChildToParentEvent @@ -14,12 +35,15 @@ import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice +import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.ResourceProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat +import org.junit.Before import org.junit.Rule import org.mockito.kotlin.any import org.mockito.kotlin.mock @@ -27,6 +51,7 @@ import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.store.WooCommerceStore import java.math.BigDecimal import java.util.Date import kotlin.test.Test @@ -45,6 +70,25 @@ class WooPosTotalsViewModelTest { private val networkStatus: WooPosNetworkStatus = mock() private val childrenToParentEventSender: WooPosChildrenToParentEventSender = mock() + private val cardReaderManager: CardReaderManager = mock() + private val orderRepository: OrderDetailRepository = mock() + private val selectedSite: SelectedSite = mock() + private val appPrefs: AppPrefs = mock() + private val paymentCollectibilityChecker: CardReaderPaymentCollectibilityChecker = mock() + private val interacRefundableChecker: CardReaderInteracRefundableChecker = mock() + private val tracker: PaymentsFlowTracker = mock() + private val trackCanceledFlow = CardReaderTrackCanceledFlow(tracker) + private val currencyFormatter: CurrencyFormatter = mock() + private val errorMapper: CardReaderPaymentErrorMapper = mock() + private val interacRefundErrorMapper: CardReaderInteracRefundErrorMapper = mock() + private val wooStore: WooCommerceStore = mock() + private val cardReaderTrackingInfoKeeper: CardReaderTrackingInfoKeeper = mock() + private val paymentStateProvider = CardReaderPaymentStateProvider() + private val cardReaderPaymentOrderHelper: CardReaderPaymentOrderHelper = mock() + private val paymentReceiptHelper: PaymentReceiptHelper = mock() + private val cardReaderOnboardingChecker: CardReaderOnboardingChecker = mock() + private val cardReaderConfigProvider: CardReaderCountryConfigProvider = mock() + private val paymentReceiptShare: PaymentReceiptShare = mock() private fun createMockSavedStateHandle(): SavedStateHandle { return SavedStateHandle( @@ -64,6 +108,21 @@ class WooPosTotalsViewModelTest { private const val EMPTY_ORDER_ID = -1L } + @Before + fun setUp() = runTest { + whenever(cardReaderManager.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connected(mock()))) + whenever(cardReaderManager.batteryStatus).thenAnswer { flow { emit(CardReaderBatteryStatus.Unknown) } } + whenever(cardReaderManager.collectPayment(any())).thenAnswer { + flow { } + } + whenever(cardReaderManager.retryCollectPayment(any(), any())).thenAnswer { + flow { } + } + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { + flow {} + } + } + @Test fun `initial state is loading`() = runTest { // GIVEN @@ -131,13 +190,10 @@ class WooPosTotalsViewModelTest { ) // THEN - assertThat(viewModel.state.value).isEqualTo( - WooPosTotalsViewState.Totals( - orderSubtotalText = "$3.00", - orderTaxText = "$2.00", - orderTotalText = "$5.00" - ) - ) + val state = viewModel.state.value as WooPosTotalsViewState.Totals + assertThat(state.orderTotalText).isEqualTo("$5.00") + assertThat(state.orderTaxText).isEqualTo("$2.00") + assertThat(state.orderSubtotalText).isEqualTo("$3.00") verify(totalsRepository).createOrderWithProducts(productIds) } @@ -420,13 +476,10 @@ class WooPosTotalsViewModelTest { ) // THEN - assertThat(viewModel.state.value).isEqualTo( - WooPosTotalsViewState.Totals( - orderSubtotalText = "3.00$", - orderTaxText = "2.00$", - orderTotalText = "5.00$" - ) - ) + val state = viewModel.state.value as WooPosTotalsViewState.Totals + assertThat(state.orderTotalText).isEqualTo("5.00$") + assertThat(state.orderTaxText).isEqualTo("2.00$") + assertThat(state.orderSubtotalText).isEqualTo("3.00$") verify(totalsRepository).createOrderWithProducts(productIds) } @@ -466,7 +519,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment status is success, when payment flow started, then OrderSuccessfullyPaid event and update state to PaymentSuccess`() = runTest { + fun `given order creation success, when reader prepared for payment, then payment collection started`() = runTest { // GIVEN whenever(networkStatus.isConnected()).thenReturn(true) val productIds = listOf(1L, 2L, 3L) @@ -498,7 +551,6 @@ class WooPosTotalsViewModelTest { onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("$2.00") onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("$3.00") } - val paymentStatusFlow = MutableStateFlow(WooPosCardReaderPaymentStatus.Unknown) whenever(cardReaderFacade.paymentStatus).thenReturn(paymentStatusFlow) @@ -515,11 +567,9 @@ class WooPosTotalsViewModelTest { advanceUntilIdle() // THEN - val state = viewModel.state.value - assertThat(state).isEqualTo( - WooPosTotalsViewState.PaymentSuccess(orderTotalText = "$3.00") - ) - verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + val state = viewModel.state.value as WooPosTotalsViewState.Totals + assertThat(state.orderTotalText).isEqualTo("$3.00") + assertThat(state.paymentStateText).isNotNull() } @org.junit.Test @@ -639,6 +689,26 @@ class WooPosTotalsViewModelTest { priceFormat, analyticsTracker, networkStatus, + cardReaderManager = cardReaderManager, + orderRepository = orderRepository, + selectedSite = selectedSite, + appPrefs = appPrefs, + paymentCollectibilityChecker = paymentCollectibilityChecker, + interacRefundableChecker = interacRefundableChecker, + tracker = tracker, + trackCancelledFlow = trackCanceledFlow, + currencyFormatter = currencyFormatter, + errorMapper = errorMapper, + interacRefundErrorMapper = interacRefundErrorMapper, + wooStore = wooStore, + dispatchers = coroutinesTestRule.testDispatchers, + cardReaderTrackingInfoKeeper = cardReaderTrackingInfoKeeper, + paymentStateProvider = paymentStateProvider, + cardReaderPaymentOrderHelper = cardReaderPaymentOrderHelper, + paymentReceiptHelper = paymentReceiptHelper, + cardReaderOnboardingChecker = cardReaderOnboardingChecker, + cardReaderConfigProvider = cardReaderConfigProvider, + paymentReceiptShare = paymentReceiptShare, savedState ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/util/WooPosCoroutineTestRule.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/util/WooPosCoroutineTestRule.kt index 2e4d364de1d..588b4db8702 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/util/WooPosCoroutineTestRule.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/util/WooPosCoroutineTestRule.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.woopos.util +import com.woocommerce.android.util.CoroutineDispatchers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestDispatcher @@ -11,6 +12,8 @@ import org.junit.runner.Description @ExperimentalCoroutinesApi class WooPosCoroutineTestRule(val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()) : TestWatcher() { + val testDispatchers = CoroutineDispatchers(testDispatcher, testDispatcher, testDispatcher) + override fun starting(description: Description?) { super.starting(description) Dispatchers.setMain(testDispatcher) From 05248bfa73dad6671000a04fe568d205d41022d6 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 12:13:49 +0100 Subject: [PATCH 008/132] Satisfy detekt's complaints --- .../home/totals/WooPosTotalsViewModel.kt | 12 ++++--- .../CardReaderPaymentControllerTest.kt | 31 ++++++++++++------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index c8db15be55b..8b209596c76 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -1,7 +1,6 @@ package com.woocommerce.android.ui.woopos.home.totals import android.os.Parcelable -import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -140,7 +139,10 @@ class WooPosTotalsViewModel @Inject constructor( cardReaderOnboardingChecker = cardReaderOnboardingChecker, cardReaderConfigProvider = cardReaderConfigProvider, paymentReceiptShare = paymentReceiptShare, - paymentOrRefund = PaymentOrRefund.Payment(orderId = orderId, paymentType = PaymentOrRefund.Payment.PaymentType.WOO_POS), + paymentOrRefund = PaymentOrRefund.Payment( + orderId = orderId, + paymentType = PaymentOrRefund.Payment.PaymentType.WOO_POS + ), cardReaderType = CardReaderType.EXTERNAL, isTTPPaymentInProgress = ::isTTPPaymentInProgress, ) @@ -232,14 +234,16 @@ class WooPosTotalsViewModel @Inject constructor( ) } if (paymentState is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentSuccessful) { - uiState.value = WooPosTotalsViewState.PaymentSuccess(orderTotalText = paymentState.amountWithCurrencyLabel) + uiState.value = + WooPosTotalsViewState.PaymentSuccess( + orderTotalText = paymentState.amountWithCurrencyLabel + ) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } } } viewModelScope.launch { cardReaderPaymentController?.event?.collect { event -> - } } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt index fbdfe0823d7..09d946ddeb1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt @@ -51,12 +51,9 @@ import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentO import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError.AmountTooSmall import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError.Unknown -import com.woocommerce.android.ui.payments.cardreader.payment.PrintReceipt -import com.woocommerce.android.ui.payments.cardreader.payment.ViewState.ExternalReaderPaymentSuccessfulState import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentEvent.PlaySuccessfulPaymentSound import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentEvent.ShowErrorMessage import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState -import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.* import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper @@ -412,7 +409,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { controller.start() assertThat(controller.paymentState.value) - .isInstanceOf(ExternalReaderFailedPayment::class.java) + .isInstanceOf(CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment::class.java) } @Test @@ -425,7 +422,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { controller.start() assertThat(controller.paymentState.value) - .isInstanceOf(BuiltInReaderFailedPayment::class.java) + .isInstanceOf(CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment::class.java) } @Test @@ -1450,7 +1447,9 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { controller.onPrintResult(CANCELLED) - assertThat(controller.paymentState.value).isInstanceOf(CardReaderPaymentState.PaymentSuccessful.ExternalReaderPaymentSuccessful::class.java) + assertThat( + controller.paymentState.value + ).isInstanceOf(CardReaderPaymentState.PaymentSuccessful.ExternalReaderPaymentSuccessful::class.java) } @Test @@ -1467,7 +1466,9 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { controller.onPrintResult(CANCELLED) - assertThat(controller.paymentState.value).isInstanceOf(CardReaderPaymentState.PaymentSuccessful.BuiltInReaderPaymentSuccessful::class.java) + assertThat( + controller.paymentState.value + ).isInstanceOf(CardReaderPaymentState.PaymentSuccessful.BuiltInReaderPaymentSuccessful::class.java) } @Test @@ -1483,7 +1484,9 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { controller.onPrintResult(CANCELLED) assertThat(controller.paymentState.value) - .isInstanceOf(CardReaderPaymentState.PaymentSuccessful.ExternalReaderPaymentSuccessfulReceiptSentAutomatically::class.java) + .isInstanceOf( + CardReaderPaymentState.PaymentSuccessful.ExternalReaderPaymentSuccessfulReceiptSentAutomatically::class.java + ) } @Test @@ -1502,7 +1505,9 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { controller.onPrintResult(CANCELLED) assertThat(controller.paymentState.value) - .isInstanceOf(CardReaderPaymentState.PaymentSuccessful.BuiltInReaderPaymentSuccessfulReceiptSentAutomatically::class.java) + .isInstanceOf( + CardReaderPaymentState.PaymentSuccessful.BuiltInReaderPaymentSuccessfulReceiptSentAutomatically::class.java + ) } @Test @@ -1631,7 +1636,9 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { } // THEN - assertThat((events.last() as CardReaderPaymentEvent.ShowErrorMessage).message).isEqualTo(R.string.receipt_fetching_error) + assertThat( + (events.last() as CardReaderPaymentEvent.ShowErrorMessage).message + ).isEqualTo(R.string.receipt_fetching_error) } @Test @@ -1651,7 +1658,9 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { } // THEN assertThat((events.last() as CardReaderPaymentEvent.PrintReceiptTapped).receiptUrl).isEqualTo(receiptUrl) - assertThat((events.last() as CardReaderPaymentEvent.PrintReceiptTapped).documentName).isEqualTo("receipt-order-1") + assertThat( + (events.last() as CardReaderPaymentEvent.PrintReceiptTapped).documentName + ).isEqualTo("receipt-order-1") } companion object { From d4b7b7a5ee37a1ea10a8641e715a3411d9775759 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 12:48:13 +0100 Subject: [PATCH 009/132] Start POS reader connection process in `ReaderStatusChecker` * There is no need to open `SelectPaymentMethod` we can start directly in `CardReaderStatusChecker` both for reader connection and preparation for payment. --- .../cardreader/WooPosCardReaderActivity.kt | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt index 35cdbbd4551..b542fd0c8f2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt @@ -10,7 +10,6 @@ import androidx.navigation.fragment.NavHostFragment import com.woocommerce.android.R import com.woocommerce.android.extensions.parcelable import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerDialogFragmentArgs -import com.woocommerce.android.ui.payments.methodselection.SelectPaymentMethodFragmentArgs import com.woocommerce.android.util.WooLog import dagger.hilt.android.AndroidEntryPoint @@ -66,31 +65,19 @@ class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_car } private fun setupNavGraph(navHostFragment: NavHostFragment) { - when (val mode = viewModel.cardReaderMode) { - is WooPosCardReaderMode.Payment -> { - val navController = navHostFragment.navController - val graph = navController.navInflater.inflate(R.navigation.nav_graph_payment_flow) - navController.setGraph( - graph, - SelectPaymentMethodFragmentArgs(cardReaderFlowParam = mode.cardReaderFlowParam).toBundle() - ) + val mode = viewModel.cardReaderMode + val navController = navHostFragment.navController + val graph = + navController.navInflater.inflate(R.navigation.nav_graph_payment_flow).apply { + setStartDestination(R.id.cardReaderStatusCheckerDialogFragment) } - - WooPosCardReaderMode.Connection -> { - val navController = navHostFragment.navController - val graph = - navController.navInflater.inflate(R.navigation.nav_graph_payment_flow).apply { - setStartDestination(R.id.cardReaderStatusCheckerDialogFragment) - } - navController.setGraph( - graph, - CardReaderStatusCheckerDialogFragmentArgs( - cardReaderFlowParam = mode.cardReaderFlowParam, - cardReaderType = mode.cardReaderType, - ).toBundle() - ) - } - } + navController.setGraph( + graph, + CardReaderStatusCheckerDialogFragmentArgs( + cardReaderFlowParam = mode.cardReaderFlowParam, + cardReaderType = mode.cardReaderType, + ).toBundle() + ) } private fun logResultListenerError(requestKey: String) { From 641f39eec043853c3672c6d74ddf873256342ddc Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 12:48:44 +0100 Subject: [PATCH 010/132] Remove POS-specific code in IPP that's unused now --- .../tutorial/CardReaderTutorialDialogFragment.kt | 7 +------ .../methodselection/SelectPaymentMethodEvent.kt | 5 ----- .../methodselection/SelectPaymentMethodFragment.kt | 12 ------------ .../methodselection/SelectPaymentMethodViewModel.kt | 6 +----- 4 files changed, 2 insertions(+), 28 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt index 6222ed49b7c..c354a61a5a7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt @@ -60,12 +60,7 @@ class CardReaderTutorialDialogFragment : PaymentsBaseDialogFragment(R.layout.car private fun navigateNext() { when (val param = args.cardReaderFlowParam) { is CardReaderFlowParam.CardReadersHub -> findNavController().popBackStack() - is CardReaderFlowParam.WooPosConnection -> { - parentFragmentManager.setFragmentResult( - WooPosCardReaderActivity.WOO_POS_CARD_CONNECTION_REQUEST_KEY, - Bundle(), - ) - } + is CardReaderFlowParam.WooPosConnection -> error("Not supported param: $param") is CardReaderFlowParam.PaymentOrRefund -> { val action = CardReaderTutorialDialogFragmentDirections .actionCardReaderTutorialDialogFragmentToCardReaderPaymentDialogFragment(param, args.cardReaderType) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt index 950dadcece2..ff7ae546a86 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt @@ -23,11 +23,6 @@ data class NavigateToCardReaderPaymentFlow( val cardReaderType: CardReaderType ) : MultiLiveEvent.Event() -data class SkipScreenInPosAndNavigateToCardReaderPaymentFlow( - val cardReaderFlowParam: CardReaderFlowParam.PaymentOrRefund.Payment, - val cardReaderType: CardReaderType -) : MultiLiveEvent.Event() - data class NavigateToCardReaderRefundFlow( val cardReaderFlowParam: CardReaderFlowParam.PaymentOrRefund.Refund, val cardReaderType: CardReaderType diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt index 7731e34a0ef..e1485a8b953 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt @@ -209,18 +209,6 @@ class SelectPaymentMethodFragment : BaseFragment(R.layout.fragment_select_paymen findNavController().navigate(action) } - is SkipScreenInPosAndNavigateToCardReaderPaymentFlow -> { - if (findNavController().currentDestination?.id == R.id.selectPaymentMethodFragment) { - findNavController().navigate( - SelectPaymentMethodFragmentDirections - .actionSelectPaymentMethodFragmentToCardReaderPaymentFlow( - event.cardReaderFlowParam, - event.cardReaderType - ) - ) - } - } - is NavigateToCardReaderHubFlow -> { val action = SelectPaymentMethodFragmentDirections.actionSelectPaymentMethodFragmentToCardReaderHubFlow( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt index 7aff55357bc..58b6fc4688d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt @@ -120,7 +120,7 @@ class SelectPaymentMethodViewModel @Inject constructor( when (param.paymentType) { SIMPLE, ORDER, ORDER_CREATION, TRY_TAP_TO_PAY -> showPaymentState() - WOO_POS -> skipScreenDuringPosFlow() + WOO_POS -> error("Unsupported card reader flow param: $param") } } Unit @@ -306,10 +306,6 @@ class SelectPaymentMethodViewModel @Inject constructor( } } - private fun skipScreenDuringPosFlow() { - triggerEvent(SkipScreenInPosAndNavigateToCardReaderPaymentFlow(cardReaderPaymentFlowParam, EXTERNAL)) - } - fun onTapToPayClicked() { launch { trackPaymentMethodSelection(VALUE_SIMPLE_PAYMENTS_COLLECT_CARD, VALUE_CARD_READER_TYPE_BUILT_IN) From 75aa4c00d59f706a2a1aeac94d99faac5be7ffd6 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 13:08:29 +0100 Subject: [PATCH 011/132] Remove POS-specific unavailable code --- .../woopos/cardreader/WooPosCardReaderActivity.kt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt index b542fd0c8f2..ba826f876f0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt @@ -49,19 +49,6 @@ class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_car else -> logResultListenerError(requestKey) } } - - navHostFragment.childFragmentManager.setFragmentResultListener( - WOO_POS_CARD_CONNECTION_REQUEST_KEY, - this - ) { requestKey, _ -> - when (requestKey) { - WOO_POS_CARD_CONNECTION_REQUEST_KEY -> { - finish() - } - - else -> logResultListenerError(requestKey) - } - } } private fun setupNavGraph(navHostFragment: NavHostFragment) { @@ -88,7 +75,6 @@ class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_car companion object { const val WOO_POS_CARD_PAYMENT_REQUEST_KEY = "woo_pos_card_payment_request" - const val WOO_POS_CARD_CONNECTION_REQUEST_KEY = "woo_pos_card_connection_request" const val WOO_POS_CARD_PAYMENT_RESULT_KEY = "woo_pos_card_payment_result" internal const val WOO_POS_CARD_READER_MODE_KEY = "card_reader_connection_mode" From c67d7e2303c8068b2592e372b6e991587f29b790 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 13:11:27 +0100 Subject: [PATCH 012/132] Rename reader status to `WooPosCardReaderConnectionStatus` --- .../connect/CardReaderConnectDialogFragment.kt | 6 +++--- .../CardReaderStatusCheckerDialogFragment.kt | 6 +++--- .../methodselection/SelectPaymentMethodFragment.kt | 6 +++--- .../woopos/cardreader/WooPosCardReaderActivity.kt | 2 +- .../cardreader/WooPosCardReaderConnectionStatus.kt | 11 +++++++++++ .../ui/woopos/cardreader/WooPosCardReaderFacade.kt | 14 +++++++------- .../cardreader/WooPosCardReaderPaymentStatus.kt | 11 ----------- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 8 ++++---- .../home/totals/WooPosTotalsViewModelTest.kt | 8 ++++---- 9 files changed, 36 insertions(+), 36 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderConnectionStatus.kt delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderPaymentStatus.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt index aaf9dcacf7a..4c4e6ce0533 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt @@ -45,7 +45,7 @@ import com.woocommerce.android.ui.payments.cardreader.connect.adapter.MultipleCa import com.woocommerce.android.ui.payments.cardreader.update.CardReaderUpdateDialogFragment import com.woocommerce.android.ui.payments.cardreader.update.CardReaderUpdateViewModel.UpdateResult import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentStatus +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import com.woocommerce.android.util.ChromeCustomTabUtils import com.woocommerce.android.util.LocationUtils import com.woocommerce.android.util.UiHelpers @@ -285,8 +285,8 @@ class CardReaderConnectDialogFragment : PaymentsBaseDialogFragment(R.layout.card fun CardReaderConnectEvent.CardReaderPrepareForPaymentResult.asWooPosCardReaderPaymentResult() = when (this) { - CardReaderConnectEvent.CardReaderPrepareForPaymentResult.Failure -> WooPosCardReaderPaymentStatus.Failure - CardReaderConnectEvent.CardReaderPrepareForPaymentResult.Success -> WooPosCardReaderPaymentStatus.Success + CardReaderConnectEvent.CardReaderPrepareForPaymentResult.Failure -> WooPosCardReaderConnectionStatus.Failure + CardReaderConnectEvent.CardReaderPrepareForPaymentResult.Success -> WooPosCardReaderConnectionStatus.Success } private fun updateMultipleReadersFoundRecyclerView( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt index f07070a1150..4fc5c301d2d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt @@ -10,7 +10,7 @@ import com.woocommerce.android.R import com.woocommerce.android.ui.payments.PaymentsBaseDialogFragment import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnResultToWooPos import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentStatus +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -87,7 +87,7 @@ class CardReaderStatusCheckerDialogFragment : PaymentsBaseDialogFragment(R.layou fun ReturnResultToWooPos.asWooPosCardReaderPaymentResult() = when (this) { - is ReturnResultToWooPos.Success -> WooPosCardReaderPaymentStatus.Success - is ReturnResultToWooPos.Failure -> WooPosCardReaderPaymentStatus.Failure + is ReturnResultToWooPos.Success -> WooPosCardReaderConnectionStatus.Success + is ReturnResultToWooPos.Failure -> WooPosCardReaderConnectionStatus.Failure } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt index e1485a8b953..1373ab51c1a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt @@ -35,7 +35,7 @@ import com.woocommerce.android.ui.payments.methodselection.SelectPaymentMethodVi import com.woocommerce.android.ui.payments.scantopay.ScanToPayDialogFragment import com.woocommerce.android.ui.payments.taptopay.summary.TapToPaySummaryFragment import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentStatus +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import com.woocommerce.android.util.ChromeCustomTabUtils import com.woocommerce.android.util.UiHelpers import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog @@ -295,8 +295,8 @@ class SelectPaymentMethodFragment : BaseFragment(R.layout.fragment_select_paymen private fun ReturnResultToWooPos.asWooPosCardReaderPaymentResult() = when (this) { - is ReturnResultToWooPos.Success -> WooPosCardReaderPaymentStatus.Success - else -> WooPosCardReaderPaymentStatus.Failure + is ReturnResultToWooPos.Success -> WooPosCardReaderConnectionStatus.Success + else -> WooPosCardReaderConnectionStatus.Failure } private fun setupResultHandlers() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt index ba826f876f0..fd7dbc8918f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt @@ -36,7 +36,7 @@ class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_car ) { requestKey, bundle -> when (requestKey) { WOO_POS_CARD_PAYMENT_REQUEST_KEY -> { - val result = bundle.parcelable( + val result = bundle.parcelable( WOO_POS_CARD_PAYMENT_RESULT_KEY ) setResult( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderConnectionStatus.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderConnectionStatus.kt new file mode 100644 index 00000000000..2e37eb11460 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderConnectionStatus.kt @@ -0,0 +1,11 @@ +package com.woocommerce.android.ui.woopos.cardreader + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +sealed class WooPosCardReaderConnectionStatus : Parcelable { + data object Success : WooPosCardReaderConnectionStatus() + data object Failure : WooPosCardReaderConnectionStatus() + data object Unknown : WooPosCardReaderConnectionStatus() +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt index cd70d76a586..2a192bf2a62 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt @@ -24,10 +24,10 @@ class WooPosCardReaderFacade @Inject constructor( val readerStatus: Flow = cardReaderManager.readerStatus - private val _paymentStatus = MutableStateFlow( - WooPosCardReaderPaymentStatus.Unknown + private val _paymentStatus = MutableStateFlow( + WooPosCardReaderConnectionStatus.Unknown ) - val paymentStatus: Flow = _paymentStatus + val paymentStatus: Flow = _paymentStatus override fun onCreate(owner: LifecycleOwner) { activity = owner as AppCompatActivity @@ -35,14 +35,14 @@ class WooPosCardReaderFacade @Inject constructor( ActivityResultContracts.StartActivityForResult() ) { result -> val paymentResult = if (result.data != null && result.resultCode == AppCompatActivity.RESULT_OK) { - result.data!!.parcelable( + result.data!!.parcelable( WOO_POS_CARD_PAYMENT_RESULT_KEY ) } else { - WooPosCardReaderPaymentStatus.Failure + WooPosCardReaderConnectionStatus.Failure } _paymentStatus.value = paymentResult!! - _paymentStatus.value = WooPosCardReaderPaymentStatus.Unknown + _paymentStatus.value = WooPosCardReaderConnectionStatus.Unknown } } @@ -59,7 +59,7 @@ class WooPosCardReaderFacade @Inject constructor( } fun collectPayment(orderId: Long) { - _paymentStatus.value = WooPosCardReaderPaymentStatus.Unknown + _paymentStatus.value = WooPosCardReaderConnectionStatus.Unknown val intent = WooPosCardReaderActivity.buildIntentForPayment(activity!!, orderId).apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderPaymentStatus.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderPaymentStatus.kt deleted file mode 100644 index a20fe929155..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderPaymentStatus.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.woocommerce.android.ui.woopos.cardreader - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -sealed class WooPosCardReaderPaymentStatus : Parcelable { - data object Success : WooPosCardReaderPaymentStatus() - data object Failure : WooPosCardReaderPaymentStatus() - data object Unknown : WooPosCardReaderPaymentStatus() -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 8b209596c76..c845930f089 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -28,7 +28,7 @@ import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentStatus +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender @@ -206,7 +206,7 @@ class WooPosTotalsViewModel @Inject constructor( viewModelScope.launch { cardReaderFacade.paymentStatus.collect { status -> when (status) { - is WooPosCardReaderPaymentStatus.Success -> { + is WooPosCardReaderConnectionStatus.Success -> { // ready to collect payment val state = uiState.value check(state is WooPosTotalsViewState.Totals) @@ -217,8 +217,8 @@ class WooPosTotalsViewModel @Inject constructor( cardReaderPaymentController?.start() listenToPaymentController() } - is WooPosCardReaderPaymentStatus.Failure, - is WooPosCardReaderPaymentStatus.Unknown -> Unit + is WooPosCardReaderConnectionStatus.Failure, + is WooPosCardReaderConnectionStatus.Unknown -> Unit } } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index c3755944e45..29f60d12a2d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -25,7 +25,7 @@ import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderPaymentStatus +import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender @@ -100,7 +100,7 @@ class WooPosTotalsViewModelTest { } private val cardReaderFacade: WooPosCardReaderFacade = mock { - on { paymentStatus }.thenReturn(MutableStateFlow(WooPosCardReaderPaymentStatus.Unknown)) + on { paymentStatus }.thenReturn(MutableStateFlow(WooPosCardReaderConnectionStatus.Unknown)) } private val analyticsTracker: WooPosAnalyticsTracker = mock() @@ -551,7 +551,7 @@ class WooPosTotalsViewModelTest { onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("$2.00") onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("$3.00") } - val paymentStatusFlow = MutableStateFlow(WooPosCardReaderPaymentStatus.Unknown) + val paymentStatusFlow = MutableStateFlow(WooPosCardReaderConnectionStatus.Unknown) whenever(cardReaderFacade.paymentStatus).thenReturn(paymentStatusFlow) val viewModel = createViewModel( @@ -563,7 +563,7 @@ class WooPosTotalsViewModelTest { // WHEN viewModel.onUIEvent(WooPosTotalsUIEvent.CollectPaymentClicked) - paymentStatusFlow.value = WooPosCardReaderPaymentStatus.Success + paymentStatusFlow.value = WooPosCardReaderConnectionStatus.Success advanceUntilIdle() // THEN From 94532ca970c843c13b632ca74359e2cdeb0abca3 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 15:41:40 +0100 Subject: [PATCH 013/132] Get rid of `WooPosCardReaderFacade.collectPayment()` flow The only requirement for reader is to be connected. There's no need to pass any other intent to the IPP flow. Once reader connects we can engage Payment Controller to collect payment. --- .../cardreader/WooPosCardReaderActivity.kt | 25 ++----- .../cardreader/WooPosCardReaderFacade.kt | 37 +---------- .../woopos/cardreader/WooPosCardReaderMode.kt | 9 --- .../cardreader/WooPosCardReaderViewModel.kt | 37 ----------- .../home/totals/WooPosTotalsViewModel.kt | 39 ++++------- .../toolbar/WooPosToolbarViewModelTest.kt | 20 +++--- .../home/totals/WooPosTotalsViewModelTest.kt | 65 ++----------------- 7 files changed, 34 insertions(+), 198 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderViewModel.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt index fd7dbc8918f..7039683a15d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt @@ -4,18 +4,17 @@ import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo import android.os.Bundle -import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.navigation.fragment.NavHostFragment import com.woocommerce.android.R -import com.woocommerce.android.extensions.parcelable +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerDialogFragmentArgs import com.woocommerce.android.util.WooLog import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_card_reader) { - val viewModel: WooPosCardReaderViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -36,13 +35,6 @@ class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_car ) { requestKey, bundle -> when (requestKey) { WOO_POS_CARD_PAYMENT_REQUEST_KEY -> { - val result = bundle.parcelable( - WOO_POS_CARD_PAYMENT_RESULT_KEY - ) - setResult( - RESULT_OK, - Intent().apply { putExtra(WOO_POS_CARD_PAYMENT_RESULT_KEY, result) } - ) finish() } @@ -52,17 +44,15 @@ class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_car } private fun setupNavGraph(navHostFragment: NavHostFragment) { - val mode = viewModel.cardReaderMode val navController = navHostFragment.navController - val graph = - navController.navInflater.inflate(R.navigation.nav_graph_payment_flow).apply { + val graph = navController.navInflater.inflate(R.navigation.nav_graph_payment_flow).apply { setStartDestination(R.id.cardReaderStatusCheckerDialogFragment) } navController.setGraph( graph, CardReaderStatusCheckerDialogFragmentArgs( - cardReaderFlowParam = mode.cardReaderFlowParam, - cardReaderType = mode.cardReaderType, + cardReaderFlowParam = CardReaderFlowParam.WooPosConnection, + cardReaderType = CardReaderType.EXTERNAL, ).toBundle() ) } @@ -82,10 +72,5 @@ class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_car Intent(context, WooPosCardReaderActivity::class.java).apply { putExtra(WOO_POS_CARD_READER_MODE_KEY, WooPosCardReaderMode.Connection) } - - fun buildIntentForPayment(context: Context, orderId: Long) = - Intent(context, WooPosCardReaderActivity::class.java).apply { - putExtra(WOO_POS_CARD_READER_MODE_KEY, WooPosCardReaderMode.Payment(orderId)) - } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt index 2a192bf2a62..d35be04dcb2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderFacade.kt @@ -1,17 +1,12 @@ package com.woocommerce.android.ui.woopos.cardreader import android.content.Intent -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import com.woocommerce.android.cardreader.CardReaderManager import com.woocommerce.android.cardreader.connection.CardReaderStatus -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity.Companion.WOO_POS_CARD_PAYMENT_RESULT_KEY -import com.woocommerce.android.util.parcelable -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject import javax.inject.Singleton @@ -19,36 +14,16 @@ import javax.inject.Singleton class WooPosCardReaderFacade @Inject constructor( private val cardReaderManager: CardReaderManager ) : DefaultLifecycleObserver { - private var paymentResultLauncher: ActivityResultLauncher? = null private var activity: AppCompatActivity? = null - val readerStatus: Flow = cardReaderManager.readerStatus - - private val _paymentStatus = MutableStateFlow( - WooPosCardReaderConnectionStatus.Unknown - ) - val paymentStatus: Flow = _paymentStatus + val readerStatus: StateFlow = cardReaderManager.readerStatus override fun onCreate(owner: LifecycleOwner) { activity = owner as AppCompatActivity - paymentResultLauncher = activity!!.registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - val paymentResult = if (result.data != null && result.resultCode == AppCompatActivity.RESULT_OK) { - result.data!!.parcelable( - WOO_POS_CARD_PAYMENT_RESULT_KEY - ) - } else { - WooPosCardReaderConnectionStatus.Failure - } - _paymentStatus.value = paymentResult!! - _paymentStatus.value = WooPosCardReaderConnectionStatus.Unknown - } } override fun onDestroy(owner: LifecycleOwner) { activity = null - paymentResultLauncher = null } fun connectToReader() { @@ -58,14 +33,6 @@ class WooPosCardReaderFacade @Inject constructor( activity!!.startActivity(intent) } - fun collectPayment(orderId: Long) { - _paymentStatus.value = WooPosCardReaderConnectionStatus.Unknown - val intent = WooPosCardReaderActivity.buildIntentForPayment(activity!!, orderId).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) - } - paymentResultLauncher!!.launch(intent) - } - suspend fun disconnectFromReader() { cardReaderManager.disconnectReader() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt index d1aa133e212..c322fd0c540 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt @@ -15,13 +15,4 @@ sealed class WooPosCardReaderMode( cardReaderFlowParam = CardReaderFlowParam.WooPosConnection, cardReaderType = CardReaderType.EXTERNAL ) - - @Parcelize - data class Payment(val orderId: Long) : WooPosCardReaderMode( - cardReaderFlowParam = CardReaderFlowParam.PaymentOrRefund.Payment( - orderId = orderId, - paymentType = CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType.WOO_POS - ), - cardReaderType = CardReaderType.EXTERNAL - ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderViewModel.kt deleted file mode 100644 index 45a69dac1a1..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderViewModel.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.woocommerce.android.ui.woopos.cardreader - -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity.Companion.WOO_POS_CARD_READER_MODE_KEY -import com.woocommerce.android.util.WooLog -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class WooPosCardReaderViewModel @Inject constructor(val savedStateHandle: SavedStateHandle) : - ViewModel() { - val cardReaderMode: WooPosCardReaderMode - get() = savedStateHandle.get(WOO_POS_CARD_READER_MODE_KEY).run { - when (this) { - is WooPosCardReaderMode.Connection -> { - WooPosCardReaderMode.Connection - } - - is WooPosCardReaderMode.Payment -> { - if (orderId != -1L) { - WooPosCardReaderMode.Payment(orderId) - } else { - val errorMessage = "Tried collecting payment with invalid orderId" - WooLog.e(WooLog.T.POS, "Error in WooPosCardReaderViewModel - $errorMessage") - error(errorMessage) - } - } - - null -> { - val errorMessage = "WooPosCardReaderMode not found in savedStateHandle" - WooLog.e(WooLog.T.POS, "Error in WooPosCardReaderViewModel - $errorMessage") - error(errorMessage) - } - } - } -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index c845930f089..00d8b6e0ecf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope import com.woocommerce.android.AppPrefs import com.woocommerce.android.R import com.woocommerce.android.cardreader.CardReaderManager +import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected import com.woocommerce.android.model.Order import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.details.OrderDetailRepository @@ -28,7 +29,6 @@ import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender @@ -63,7 +63,6 @@ class WooPosTotalsViewModel @Inject constructor( private val priceFormat: WooPosFormatPrice, private val analyticsTracker: WooPosAnalyticsTracker, private val networkStatus: WooPosNetworkStatus, - private val cardReaderManager: CardReaderManager, private val orderRepository: OrderDetailRepository, private val selectedSite: SelectedSite, @@ -150,7 +149,6 @@ class WooPosTotalsViewModel @Inject constructor( init { listenUpEvents() - listenToPaymentsStatus() } fun onUIEvent(event: WooPosTotalsUIEvent) { @@ -177,7 +175,18 @@ class WooPosTotalsViewModel @Inject constructor( } else { val orderId = dataState.value.orderId check(orderId != EMPTY_ORDER_ID) - cardReaderFacade.collectPayment(orderId) + if (cardReaderFacade.readerStatus.value is Connected) { + val state = uiState.value + check(state is WooPosTotalsViewState.Totals) + val orderId = dataState.value.orderId + check(orderId != EMPTY_ORDER_ID) + check(uiState.value is WooPosTotalsViewState.Totals) + createCardReaderPaymentController(dataState.value.orderId) + cardReaderPaymentController?.start() + listenToPaymentController() + } else { + // TODO: Update view state to ask user to connect card reader. Once connected, proceed with payment. + } } } @@ -202,28 +211,6 @@ class WooPosTotalsViewModel @Inject constructor( } } - private fun listenToPaymentsStatus() { - viewModelScope.launch { - cardReaderFacade.paymentStatus.collect { status -> - when (status) { - is WooPosCardReaderConnectionStatus.Success -> { - // ready to collect payment - val state = uiState.value - check(state is WooPosTotalsViewState.Totals) - val orderId = dataState.value.orderId - check(orderId != EMPTY_ORDER_ID) - check(uiState.value is WooPosTotalsViewState.Totals) - createCardReaderPaymentController(dataState.value.orderId) - cardReaderPaymentController?.start() - listenToPaymentController() - } - is WooPosCardReaderConnectionStatus.Failure, - is WooPosCardReaderConnectionStatus.Unknown -> Unit - } - } - } - } - private fun listenToPaymentController() { viewModelScope.launch { cardReaderPaymentController?.paymentState?.collect { paymentState -> diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModelTest.kt index dee426e3e59..ffe575d65ef 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModelTest.kt @@ -9,7 +9,7 @@ import com.woocommerce.android.ui.woopos.support.WooPosGetSupportFacade import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Rule @@ -25,7 +25,7 @@ class WooPosToolbarViewModelTest { @JvmField val coroutinesTestRule = WooPosCoroutineTestRule() private val cardReaderFacade: WooPosCardReaderFacade = mock { - onBlocking { readerStatus }.thenReturn(flowOf(CardReaderStatus.NotConnected())) + onBlocking { readerStatus }.thenReturn(MutableStateFlow(CardReaderStatus.NotConnected())) } private val getSupportFacade: WooPosGetSupportFacade = mock() private val childrenToParentEventSender: WooPosChildrenToParentEventSender = mock() @@ -34,7 +34,7 @@ class WooPosToolbarViewModelTest { @Test fun `given card reader status is NotConnected, when initialized, then state should be NotConnected`() = runTest { // GIVEN - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.NotConnected())) val viewModel = createViewModel() // THEN @@ -45,7 +45,7 @@ class WooPosToolbarViewModelTest { @Test fun `given card reader status is Connected, when initialized, then state should be Connected`() = runTest { // GIVEN - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.Connected(mock()))) + whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connected(mock()))) val viewModel = createViewModel() // THEN @@ -56,7 +56,7 @@ class WooPosToolbarViewModelTest { @Test fun `given card reader status is Connecting, when initialized, then state should be NotConnected`() = runTest { // GIVEN - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.Connecting)) + whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connecting)) val viewModel = createViewModel() // THEN @@ -107,7 +107,7 @@ class WooPosToolbarViewModelTest { @Test fun `when ConnectToAReaderClicked passed, then connect to reader should be called`() = runTest { // GIVEN - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.NotConnected())) whenever(networkStatus.isConnected()).thenReturn(true) val viewModel = createViewModel() @@ -139,7 +139,7 @@ class WooPosToolbarViewModelTest { fun `given card reader status is Connected, when OnCardReaderStatusClicked, then disconnect from reader should be called`() = runTest { // GIVEN - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.Connected(mock()))) + whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connected(mock()))) val viewModel = createViewModel() // WHEN @@ -153,7 +153,7 @@ class WooPosToolbarViewModelTest { fun `given card reader status is NotConnected, when OnCardReaderStatusClicked, then connect to reader should be called`() = runTest { // GIVEN - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.NotConnected())) whenever(networkStatus.isConnected()).thenReturn(true) val viewModel = createViewModel() @@ -184,7 +184,7 @@ class WooPosToolbarViewModelTest { fun `given there is no internet, when trying to connect card reader, then trigger proper event`() = runTest { // GIVEN whenever(networkStatus.isConnected()).thenReturn(false) - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.NotConnected())) // WHEN val viewModel = createViewModel() @@ -198,7 +198,7 @@ class WooPosToolbarViewModelTest { fun `given there is no internet, when trying to connect card reader, then connect card reader method is not called`() = runTest { // GIVEN whenever(networkStatus.isConnected()).thenReturn(false) - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.NotConnected())) // WHEN val viewModel = createViewModel() diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 29f60d12a2d..d27d8cc84fc 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -25,7 +25,6 @@ import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender @@ -48,7 +47,6 @@ import org.junit.Rule import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.never -import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.wordpress.android.fluxc.store.WooCommerceStore @@ -99,9 +97,7 @@ class WooPosTotalsViewModelTest { ) } - private val cardReaderFacade: WooPosCardReaderFacade = mock { - on { paymentStatus }.thenReturn(MutableStateFlow(WooPosCardReaderConnectionStatus.Unknown)) - } + private val cardReaderFacade: WooPosCardReaderFacade = mock() private val analyticsTracker: WooPosAnalyticsTracker = mock() private companion object { @@ -377,57 +373,6 @@ class WooPosTotalsViewModelTest { assertThat(state.orderSubtotalText).isEqualTo("$3.00") } - @Test - fun `when CollectPaymentClicked is emitted, then should collect payment`() = runTest { - // GIVEN - whenever(networkStatus.isConnected()).thenReturn(true) - val productIds = listOf(1L, 2L, 3L) - val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) - val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { - on { events }.thenReturn(parentToChildrenEventFlow) - } - val order = Order.getEmptyOrder( - dateCreated = Date(), - dateModified = Date() - ).copy( - totalTax = BigDecimal("2.00"), - items = listOf( - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ), - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ), - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ) - ), - productsTotal = BigDecimal("3.00"), - total = BigDecimal("5.00"), - ) - val totalsRepository: WooPosTotalsRepository = mock { - onBlocking { createOrderWithProducts(productIds = productIds) }.thenReturn( - Result.success(order) - ) - } - val priceFormat: WooPosFormatPrice = mock { - onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("2.00$") - onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("3.00$") - onBlocking { invoke(BigDecimal("5.00")) }.thenReturn("5.00$") - } - - // WHEN - val viewModel = createViewModel( - parentToChildrenEventReceiver = parentToChildrenEventReceiver, - totalsRepository = totalsRepository, - priceFormat = priceFormat, - ) - viewModel.onUIEvent(WooPosTotalsUIEvent.CollectPaymentClicked) - - // THEN - verify(cardReaderFacade, times(1)).collectPayment(any()) - } - @Test fun `when order is created, then should track order creation success`() = runTest { // GIVEN @@ -519,7 +464,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given order creation success, when reader prepared for payment, then payment collection started`() = runTest { + fun `given reader connected, when order created, then payment collection started`() = runTest { // GIVEN whenever(networkStatus.isConnected()).thenReturn(true) val productIds = listOf(1L, 2L, 3L) @@ -551,8 +496,7 @@ class WooPosTotalsViewModelTest { onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("$2.00") onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("$3.00") } - val paymentStatusFlow = MutableStateFlow(WooPosCardReaderConnectionStatus.Unknown) - whenever(cardReaderFacade.paymentStatus).thenReturn(paymentStatusFlow) + whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connected(mock()))) val viewModel = createViewModel( savedState = savedState, @@ -563,7 +507,6 @@ class WooPosTotalsViewModelTest { // WHEN viewModel.onUIEvent(WooPosTotalsUIEvent.CollectPaymentClicked) - paymentStatusFlow.value = WooPosCardReaderConnectionStatus.Success advanceUntilIdle() // THEN @@ -671,7 +614,7 @@ class WooPosTotalsViewModelTest { viewModel.onUIEvent(WooPosTotalsUIEvent.CollectPaymentClicked) // THEN - verify(cardReaderFacade, never()).collectPayment(any()) + verify(cardReaderManager, never()).collectPayment(any()) } private fun createViewModel( From 1024363e528125727e2ffe8a5cdca0e730a67de6 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 15:54:34 +0100 Subject: [PATCH 014/132] Get rid of `WooPosCardReaderFacade.collectPayment()` flow The only requirement for reader is to be connected. There's no need to pass any other intent to the IPP flow. Once reader connects we can engage Payment Controller to collect payment. --- .../cardreader/WooPosCardReaderActivity.kt | 4 +- .../WooPosCardReaderConnectionStatus.kt | 11 ---- .../home/totals/WooPosTotalsViewModel.kt | 2 +- .../CardReaderPaymentViewModelTest.kt | 52 ------------------- .../CardReaderPaymentControllerTest.kt | 2 + 5 files changed, 5 insertions(+), 66 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderConnectionStatus.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt index 7039683a15d..4990dc5bea7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt @@ -46,8 +46,8 @@ class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_car private fun setupNavGraph(navHostFragment: NavHostFragment) { val navController = navHostFragment.navController val graph = navController.navInflater.inflate(R.navigation.nav_graph_payment_flow).apply { - setStartDestination(R.id.cardReaderStatusCheckerDialogFragment) - } + setStartDestination(R.id.cardReaderStatusCheckerDialogFragment) + } navController.setGraph( graph, CardReaderStatusCheckerDialogFragmentArgs( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderConnectionStatus.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderConnectionStatus.kt deleted file mode 100644 index 2e37eb11460..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderConnectionStatus.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.woocommerce.android.ui.woopos.cardreader - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -sealed class WooPosCardReaderConnectionStatus : Parcelable { - data object Success : WooPosCardReaderConnectionStatus() - data object Failure : WooPosCardReaderConnectionStatus() - data object Unknown : WooPosCardReaderConnectionStatus() -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 00d8b6e0ecf..51bb7cbd301 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -185,7 +185,7 @@ class WooPosTotalsViewModel @Inject constructor( cardReaderPaymentController?.start() listenToPaymentController() } else { - // TODO: Update view state to ask user to connect card reader. Once connected, proceed with payment. + // TODO: Update view state to ask user to connect card reader. Once connected, proceed with payment. } } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index 904bdcd947e..b7f35e6bdce 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -3904,58 +3904,6 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { verify(cardReaderManager, never()).collectPayment(any()) } - @Test - fun `given point of sale, when payment captured, then should not show success state`() { - testBlocking { - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(PaymentCompleted("")) } - } - - initViewModel( - readerType = EXTERNAL, - cardReaderFlowParam = CardReaderFlowParam.PaymentOrRefund.Payment( - orderId = ORDER_ID, - paymentType = CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType.WOO_POS - ) - ) - - viewModel.start() - - assertThat(viewModel.viewStateData.value).isNotInstanceOfAny( - BuiltInReaderPaymentSuccessfulState::class.java, - ExternalReaderPaymentSuccessfulState::class.java, - ExternalReaderPaymentSuccessfulReceiptSentAutomaticallyState::class.java, - BuiltInReaderPaymentSuccessfulReceiptSentAutomaticallyState::class.java, - ) - } - } - - @Test - fun `given point of sale, when payment captured, then should exit`() { - testBlocking { - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(PaymentCompleted("")) } - } - - initViewModel( - readerType = EXTERNAL, - cardReaderFlowParam = CardReaderFlowParam.PaymentOrRefund.Payment( - orderId = ORDER_ID, - paymentType = CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType.WOO_POS - ) - ) - - val events = mutableListOf() - viewModel.event.observeForever { - events.add(it) - } - - viewModel.start() - - assertThat(events[0]).isInstanceOf(Exit::class.java) - } - } - private suspend fun simulateFetchOrderJobState(inProgress: Boolean) { if (inProgress) { whenever(orderRepository.fetchOrderById(any())).doSuspendableAnswer { diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt index 09d946ddeb1..c83662f870a 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt @@ -54,6 +54,8 @@ import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError.U import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentEvent.PlaySuccessfulPaymentSound import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentEvent.ShowErrorMessage import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper From d390dd44d83687d61c5525f55b6c87f2046fba89 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 15:55:01 +0100 Subject: [PATCH 015/132] Remove redundant code --- .../connect/CardReaderConnectDialogFragment.kt | 14 +------------- .../CardReaderStatusCheckerDialogFragment.kt | 14 +------------- .../tutorial/CardReaderTutorialDialogFragment.kt | 1 - .../methodselection/SelectPaymentMethodFragment.kt | 14 +------------- 4 files changed, 3 insertions(+), 40 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt index 4c4e6ce0533..21aecf10d40 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt @@ -45,7 +45,6 @@ import com.woocommerce.android.ui.payments.cardreader.connect.adapter.MultipleCa import com.woocommerce.android.ui.payments.cardreader.update.CardReaderUpdateDialogFragment import com.woocommerce.android.ui.payments.cardreader.update.CardReaderUpdateViewModel.UpdateResult import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import com.woocommerce.android.util.ChromeCustomTabUtils import com.woocommerce.android.util.LocationUtils import com.woocommerce.android.util.UiHelpers @@ -260,12 +259,7 @@ class CardReaderConnectDialogFragment : PaymentsBaseDialogFragment(R.layout.card is CardReaderConnectEvent.CardReaderPrepareForPaymentResult -> { parentFragmentManager.setFragmentResult( WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, - Bundle().apply { - putParcelable( - WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_RESULT_KEY, - event.asWooPosCardReaderPaymentResult(), - ) - } + Bundle() ) } is CardReaderConnectEvent.ShowToast -> @@ -283,12 +277,6 @@ class CardReaderConnectDialogFragment : PaymentsBaseDialogFragment(R.layout.card } } - fun CardReaderConnectEvent.CardReaderPrepareForPaymentResult.asWooPosCardReaderPaymentResult() = - when (this) { - CardReaderConnectEvent.CardReaderPrepareForPaymentResult.Failure -> WooPosCardReaderConnectionStatus.Failure - CardReaderConnectEvent.CardReaderPrepareForPaymentResult.Success -> WooPosCardReaderConnectionStatus.Success - } - private fun updateMultipleReadersFoundRecyclerView( binding: CardReaderConnectDialogBinding, viewState: CardReaderConnectViewState diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt index 4fc5c301d2d..7a7891480ef 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt @@ -10,7 +10,6 @@ import com.woocommerce.android.R import com.woocommerce.android.ui.payments.PaymentsBaseDialogFragment import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnResultToWooPos import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -72,22 +71,11 @@ class CardReaderStatusCheckerDialogFragment : PaymentsBaseDialogFragment(R.layou is ReturnResultToWooPos -> { parentFragmentManager.setFragmentResult( WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, - Bundle().apply { - putParcelable( - WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_RESULT_KEY, - event.asWooPosCardReaderPaymentResult(), - ) - } + Bundle() ) } else -> event.isHandled = false } } } - - fun ReturnResultToWooPos.asWooPosCardReaderPaymentResult() = - when (this) { - is ReturnResultToWooPos.Success -> WooPosCardReaderConnectionStatus.Success - is ReturnResultToWooPos.Failure -> WooPosCardReaderConnectionStatus.Failure - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt index c354a61a5a7..c3f0925f84a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/tutorial/CardReaderTutorialDialogFragment.kt @@ -15,7 +15,6 @@ import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.payments.PaymentsBaseDialogFragment import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt index 1373ab51c1a..c762fafe5e0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt @@ -35,7 +35,6 @@ import com.woocommerce.android.ui.payments.methodselection.SelectPaymentMethodVi import com.woocommerce.android.ui.payments.scantopay.ScanToPayDialogFragment import com.woocommerce.android.ui.payments.taptopay.summary.TapToPaySummaryFragment import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderConnectionStatus import com.woocommerce.android.util.ChromeCustomTabUtils import com.woocommerce.android.util.UiHelpers import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog @@ -281,24 +280,13 @@ class SelectPaymentMethodFragment : BaseFragment(R.layout.fragment_select_paymen is ReturnResultToWooPos -> { parentFragmentManager.setFragmentResult( WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, - Bundle().apply { - putParcelable( - WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_RESULT_KEY, - event.asWooPosCardReaderPaymentResult(), - ) - } + Bundle() ) } } } } - private fun ReturnResultToWooPos.asWooPosCardReaderPaymentResult() = - when (this) { - is ReturnResultToWooPos.Success -> WooPosCardReaderConnectionStatus.Success - else -> WooPosCardReaderConnectionStatus.Failure - } - private fun setupResultHandlers() { handleDialogResult( key = CardReaderConnectDialogFragment.KEY_CONNECT_TO_READER_RESULT, From 9b78db455d1afa20cab7c472fd65aec19d514d9e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 15:58:02 +0100 Subject: [PATCH 016/132] Remove redundant code --- .../cardreader/WooPosCardReaderActivity.kt | 6 +----- .../woopos/cardreader/WooPosCardReaderMode.kt | 18 ------------------ 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt index 4990dc5bea7..ffc3bbbf901 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderActivity.kt @@ -65,12 +65,8 @@ class WooPosCardReaderActivity : AppCompatActivity(R.layout.activity_woo_pos_car companion object { const val WOO_POS_CARD_PAYMENT_REQUEST_KEY = "woo_pos_card_payment_request" - const val WOO_POS_CARD_PAYMENT_RESULT_KEY = "woo_pos_card_payment_result" - internal const val WOO_POS_CARD_READER_MODE_KEY = "card_reader_connection_mode" fun buildIntentForCardReaderConnection(context: Context) = - Intent(context, WooPosCardReaderActivity::class.java).apply { - putExtra(WOO_POS_CARD_READER_MODE_KEY, WooPosCardReaderMode.Connection) - } + Intent(context, WooPosCardReaderActivity::class.java) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt deleted file mode 100644 index c322fd0c540..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/WooPosCardReaderMode.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.woocommerce.android.ui.woopos.cardreader - -import android.os.Parcelable -import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam -import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType -import kotlinx.parcelize.Parcelize - -@Parcelize -sealed class WooPosCardReaderMode( - val cardReaderFlowParam: CardReaderFlowParam, - val cardReaderType: CardReaderType -) : Parcelable { - @Parcelize - data object Connection : WooPosCardReaderMode( - cardReaderFlowParam = CardReaderFlowParam.WooPosConnection, - cardReaderType = CardReaderType.EXTERNAL - ) -} From 49c4d1bf0d8018ca7236cc5e8ff60d300f71120d Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 19:58:32 +0100 Subject: [PATCH 017/132] Simplify POS event names and models in IPP --- .../connect/CardReaderConnectDialogFragment.kt | 2 +- .../cardreader/connect/CardReaderConnectEvent.kt | 5 +---- .../connect/CardReaderConnectViewModel.kt | 13 ++++--------- .../CardReaderStatusCheckerDialogFragment.kt | 4 ++-- .../CardReaderStatusCheckerViewModel.kt | 9 +++------ 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt index 21aecf10d40..715dc75c44b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectDialogFragment.kt @@ -256,7 +256,7 @@ class CardReaderConnectDialogFragment : PaymentsBaseDialogFragment(R.layout.card result = event.data as Boolean, ) } - is CardReaderConnectEvent.CardReaderPrepareForPaymentResult -> { + is CardReaderConnectEvent.ReturnToWooPos -> { parentFragmentManager.setFragmentResult( WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, Bundle() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt index 5bdc9b324e5..0201052f3ad 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectEvent.kt @@ -47,8 +47,5 @@ sealed class CardReaderConnectEvent : MultiLiveEvent.Event() { data class OpenGenericWebView(val url: String) : CardReaderConnectEvent() - sealed class CardReaderPrepareForPaymentResult : MultiLiveEvent.Event() { - data object Success : CardReaderPrepareForPaymentResult() - data object Failure : CardReaderPrepareForPaymentResult() - } + data object ReturnToWooPos : MultiLiveEvent.Event() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt index 50b3d3e67fb..7b8c4cbc71b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt @@ -27,7 +27,6 @@ import com.woocommerce.android.model.UiString.UiStringRes import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.payments.cardreader.LearnMoreUrlProvider import com.woocommerce.android.ui.payments.cardreader.LearnMoreUrlProvider.LearnMoreUrlType.IN_PERSON_PAYMENTS -import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.CardReaderPrepareForPaymentResult import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.CheckBluetoothEnabled import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.CheckBluetoothPermissionsGiven import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.CheckLocationEnabled @@ -39,6 +38,7 @@ import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectE import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.RequestBluetoothRuntimePermissions import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.RequestEnableBluetooth import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.RequestLocationPermissions +import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.ReturnToWooPos import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.ShowCardReaderTutorial import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.ShowToast import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectEvent.ShowToastString @@ -531,7 +531,7 @@ class CardReaderConnectViewModel @Inject constructor( private fun exitFlow(connected: Boolean) { val param = arguments.cardReaderFlowParam if (param.isPOS) { - returnToWooPos(connected) + returnToWooPos() } else if (!connected) { triggerEvent(ExitWithResult(false)) } else { @@ -544,13 +544,8 @@ class CardReaderConnectViewModel @Inject constructor( } } - private fun returnToWooPos(connected: Boolean) { - val event = if (connected) { - CardReaderPrepareForPaymentResult.Success - } else { - CardReaderPrepareForPaymentResult.Failure - } - triggerEvent(event) + private fun returnToWooPos() { + triggerEvent(ReturnToWooPos) } private fun storeConnectedReader(cardReader: CardReader) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt index 7a7891480ef..6a14f03220d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt @@ -8,7 +8,7 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.woocommerce.android.R import com.woocommerce.android.ui.payments.PaymentsBaseDialogFragment -import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnResultToWooPos +import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnToWooPos import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity import dagger.hilt.android.AndroidEntryPoint @@ -68,7 +68,7 @@ class CardReaderStatusCheckerDialogFragment : PaymentsBaseDialogFragment(R.layou ) ) } - is ReturnResultToWooPos -> { + is ReturnToWooPos -> { parentFragmentManager.setFragmentResult( WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, Bundle() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt index b6505a3ceca..a84317d824f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt @@ -16,7 +16,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.NavigateToConnection -import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnResultToWooPos +import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnToWooPos import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ScopedViewModel @@ -66,7 +66,7 @@ class CardReaderStatusCheckerViewModel handleNotSelectedReaderTypeConnected(param) } else { if (param.isPOS) { - triggerEvent(ReturnResultToWooPos.Success) + triggerEvent(ReturnToWooPos) } else { triggerEvent( StatusCheckerEvent.NavigateToPayment( @@ -139,9 +139,6 @@ class CardReaderStatusCheckerViewModel val cardReaderType: CardReaderType, ) : MultiLiveEvent.Event() - sealed class ReturnResultToWooPos : MultiLiveEvent.Event() { - data object Success : ReturnResultToWooPos() - data object Failure : ReturnResultToWooPos() - } + data object ReturnToWooPos : MultiLiveEvent.Event() } } From 84c687bd99fcd442f4d8591c99297c3b163bbf67 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 20:00:30 +0100 Subject: [PATCH 018/132] Remove redundant code --- .../cardreader/connect/CardReaderConnectViewModel.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt index 7b8c4cbc71b..457786ae1a7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt @@ -118,10 +118,6 @@ class CardReaderConnectViewModel @Inject constructor( val viewStateData: LiveData = viewState - private val CardReaderFlowParam.isPOS: Boolean - get() = this is Payment && paymentType == Payment.PaymentType.WOO_POS || - this is CardReaderFlowParam.WooPosConnection - init { startFlow() } @@ -530,7 +526,7 @@ class CardReaderConnectViewModel @Inject constructor( private fun exitFlow(connected: Boolean) { val param = arguments.cardReaderFlowParam - if (param.isPOS) { + if (param is CardReaderFlowParam.WooPosConnection) { returnToWooPos() } else if (!connected) { triggerEvent(ExitWithResult(false)) From 5c70adaac2bf56e965954c7505531e8310e70c69 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 20:08:46 +0100 Subject: [PATCH 019/132] Remove unused import --- .../ui/payments/cardreader/connect/CardReaderConnectViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt index 457786ae1a7..f4d3f74b86e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt @@ -59,7 +59,6 @@ import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectV import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectViewState.MultipleExternalReadersFoundState import com.woocommerce.android.ui.payments.cardreader.connect.CardReaderConnectViewState.ScanningFailedState import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam -import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Payment import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingChecker import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL From e73ba2b0dc937072dd7a4a11b1ee18de792686d2 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 20:09:05 +0100 Subject: [PATCH 020/132] Simplify code --- .../CardReaderStatusCheckerViewModel.kt | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt index a84317d824f..9e628791ad7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt @@ -8,7 +8,6 @@ import com.woocommerce.android.cardreader.connection.CardReader import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected import com.woocommerce.android.cardreader.connection.ReaderType import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam -import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Payment import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingChecker import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingParams import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingState @@ -16,7 +15,6 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.NavigateToConnection -import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnToWooPos import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ScopedViewModel @@ -40,17 +38,12 @@ class CardReaderStatusCheckerViewModel override val _event = SingleLiveEvent() override val event: LiveData = _event - private val CardReaderFlowParam.isPOS: Boolean - get() = this is Payment && paymentType == Payment.PaymentType.WOO_POS || - this is CardReaderFlowParam.WooPosConnection - init { launch { checkStatus() } } - @Suppress("NestedBlockDepth") private suspend fun checkStatus() { when (val param = arguments.cardReaderFlowParam) { is CardReaderFlowParam.CardReadersHub -> triggerEvent( @@ -65,16 +58,12 @@ class CardReaderStatusCheckerViewModel if (cardReaderStatus.cardReader.toCardReaderType() != arguments.cardReaderType) { handleNotSelectedReaderTypeConnected(param) } else { - if (param.isPOS) { - triggerEvent(ReturnToWooPos) - } else { - triggerEvent( - StatusCheckerEvent.NavigateToPayment( - param, - cardReaderStatus.cardReader.toCardReaderType() - ) + triggerEvent( + StatusCheckerEvent.NavigateToPayment( + param, + cardReaderStatus.cardReader.toCardReaderType() ) - } + ) } } else { handleOnboardingStatus(param) From 8488745b40cb6bc5d8f5dc844e18bc3d9f611585 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 20:15:14 +0100 Subject: [PATCH 021/132] Remove no more accessed code --- .../payments/methodselection/SelectPaymentMethodEvent.kt | 5 ----- .../methodselection/SelectPaymentMethodFragment.kt | 8 -------- .../methodselection/SelectPaymentMethodViewModel.kt | 4 ---- 3 files changed, 17 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt index ff7ae546a86..a984704284b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodEvent.kt @@ -36,11 +36,6 @@ data class NavigateToOrderDetails( val orderId: Long ) : MultiLiveEvent.Event() -sealed class ReturnResultToWooPos : MultiLiveEvent.Event() { - data object Success : ReturnResultToWooPos() - data object Failure : ReturnResultToWooPos() -} - data class NavigateToTapToPaySummary( val order: Order ) : MultiLiveEvent.Event() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt index c762fafe5e0..17333b63d31 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt @@ -34,7 +34,6 @@ import com.woocommerce.android.ui.payments.methodselection.SelectPaymentMethodVi import com.woocommerce.android.ui.payments.methodselection.SelectPaymentMethodViewState.Success import com.woocommerce.android.ui.payments.scantopay.ScanToPayDialogFragment import com.woocommerce.android.ui.payments.taptopay.summary.TapToPaySummaryFragment -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity import com.woocommerce.android.util.ChromeCustomTabUtils import com.woocommerce.android.util.UiHelpers import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowDialog @@ -276,13 +275,6 @@ class SelectPaymentMethodFragment : BaseFragment(R.layout.fragment_select_paymen ) ) } - - is ReturnResultToWooPos -> { - parentFragmentManager.setFragmentResult( - WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, - Bundle() - ) - } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt index 58b6fc4688d..c78a037ccdc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodViewModel.kt @@ -321,10 +321,6 @@ class SelectPaymentMethodViewModel @Inject constructor( flow = cardReaderPaymentFlowParam.toAnalyticsFlowName(), ) } - if (cardReaderPaymentFlowParam.paymentType == WOO_POS) { - val result = if (connected) ReturnResultToWooPos.Success else ReturnResultToWooPos.Failure - triggerEvent(result) - } } fun onCardReaderPaymentCompleted() { From 7183b09929767749f0136b3e03021ad135a4a376 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 22 Nov 2024 20:22:45 +0100 Subject: [PATCH 022/132] Remove unused code --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 51bb7cbd301..37b9fcd6729 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -183,7 +183,7 @@ class WooPosTotalsViewModel @Inject constructor( check(uiState.value is WooPosTotalsViewState.Totals) createCardReaderPaymentController(dataState.value.orderId) cardReaderPaymentController?.start() - listenToPaymentController() + listenToPaymentState() } else { // TODO: Update view state to ask user to connect card reader. Once connected, proceed with payment. } @@ -211,7 +211,7 @@ class WooPosTotalsViewModel @Inject constructor( } } - private fun listenToPaymentController() { + private fun listenToPaymentState() { viewModelScope.launch { cardReaderPaymentController?.paymentState?.collect { paymentState -> val totalsState = uiState.value @@ -229,10 +229,6 @@ class WooPosTotalsViewModel @Inject constructor( } } } - viewModelScope.launch { - cardReaderPaymentController?.event?.collect { event -> - } - } } private fun createOrderDraft(productIds: List) { From 41e778bd209ab4d64e59c26fbdbbdc3321c00511 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 26 Nov 2024 17:51:45 +0100 Subject: [PATCH 023/132] Simplify code --- .../cardreader/connect/CardReaderConnectViewModel.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt index f4d3f74b86e..e2696fbc309 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/connect/CardReaderConnectViewModel.kt @@ -525,12 +525,10 @@ class CardReaderConnectViewModel @Inject constructor( private fun exitFlow(connected: Boolean) { val param = arguments.cardReaderFlowParam - if (param is CardReaderFlowParam.WooPosConnection) { - returnToWooPos() - } else if (!connected) { - triggerEvent(ExitWithResult(false)) - } else { - triggerEvent( + when { + param is CardReaderFlowParam.WooPosConnection -> returnToWooPos() + !connected -> triggerEvent(ExitWithResult(false)) + else -> triggerEvent( ShowCardReaderTutorial( arguments.cardReaderFlowParam, arguments.cardReaderType From e67149f6a072ed12a9c1e0cc0ab595f8cf02ceab Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 26 Nov 2024 18:13:14 +0100 Subject: [PATCH 024/132] Remove unused code --- .../CardReaderStatusCheckerDialogFragment.kt | 8 -------- .../statuschecker/CardReaderStatusCheckerViewModel.kt | 2 -- 2 files changed, 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt index 6a14f03220d..df97bdf96d1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerDialogFragment.kt @@ -8,8 +8,6 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.woocommerce.android.R import com.woocommerce.android.ui.payments.PaymentsBaseDialogFragment -import com.woocommerce.android.ui.payments.cardreader.statuschecker.CardReaderStatusCheckerViewModel.StatusCheckerEvent.ReturnToWooPos -import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderActivity import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -68,12 +66,6 @@ class CardReaderStatusCheckerDialogFragment : PaymentsBaseDialogFragment(R.layou ) ) } - is ReturnToWooPos -> { - parentFragmentManager.setFragmentResult( - WooPosCardReaderActivity.WOO_POS_CARD_PAYMENT_REQUEST_KEY, - Bundle() - ) - } else -> event.isHandled = false } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt index 9e628791ad7..936ef06809b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/statuschecker/CardReaderStatusCheckerViewModel.kt @@ -127,7 +127,5 @@ class CardReaderStatusCheckerViewModel val cardReaderOnboardingParams: CardReaderOnboardingParams, val cardReaderType: CardReaderType, ) : MultiLiveEvent.Event() - - data object ReturnToWooPos : MultiLiveEvent.Event() } } From 77984aa13806c6f02ba8369ca0b4e02071136891 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 26 Nov 2024 19:02:29 +0100 Subject: [PATCH 025/132] Extract payment controller creation to factory class --- .../CardReaderPaymentControllerFactory.kt | 84 +++++++++++++++++++ .../home/totals/WooPosTotalsViewModel.kt | 74 ++-------------- .../home/totals/WooPosTotalsViewModelTest.kt | 47 ++++++----- 3 files changed, 116 insertions(+), 89 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt new file mode 100644 index 00000000000..e142ab2fa84 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt @@ -0,0 +1,84 @@ +package com.woocommerce.android.ui.payments.cardreader.payment.controller + +import com.woocommerce.android.AppPrefs +import com.woocommerce.android.cardreader.CardReaderManager +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.orders.details.OrderDetailRepository +import com.woocommerce.android.ui.payments.cardreader.CardReaderCountryConfigProvider +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund.Payment.PaymentType +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingChecker +import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracRefundErrorMapper +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracRefundableChecker +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper +import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper +import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare +import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper +import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker +import com.woocommerce.android.util.CoroutineDispatchers +import com.woocommerce.android.util.CurrencyFormatter +import kotlinx.coroutines.CoroutineScope +import org.wordpress.android.fluxc.store.WooCommerceStore +import javax.inject.Inject +import kotlin.reflect.KMutableProperty0 + +class CardReaderPaymentControllerFactory @Inject constructor( + private val cardReaderManager: CardReaderManager, + private val orderRepository: OrderDetailRepository, + private val selectedSite: SelectedSite, + private val appPrefs: AppPrefs = AppPrefs, + private val paymentCollectibilityChecker: CardReaderPaymentCollectibilityChecker, + private val interacRefundableChecker: CardReaderInteracRefundableChecker, + private val tracker: PaymentsFlowTracker, + private val trackCancelledFlow: CardReaderTrackCanceledFlowAction, + private val currencyFormatter: CurrencyFormatter, + private val errorMapper: CardReaderPaymentErrorMapper, + private val interacRefundErrorMapper: CardReaderInteracRefundErrorMapper, + private val wooStore: WooCommerceStore, + private val dispatchers: CoroutineDispatchers, + private val cardReaderTrackingInfoKeeper: CardReaderTrackingInfoKeeper, + private val paymentStateProvider: CardReaderPaymentStateProvider, + private val cardReaderPaymentOrderHelper: CardReaderPaymentOrderHelper, + private val paymentReceiptHelper: PaymentReceiptHelper, + private val cardReaderOnboardingChecker: CardReaderOnboardingChecker, + private val cardReaderConfigProvider: CardReaderCountryConfigProvider, + private val paymentReceiptShare: PaymentReceiptShare, +) { + fun create( + orderId: Long, + paymentType: PaymentType, + scoppe: CoroutineScope, + isTTPPaymentInProgress: KMutableProperty0, + ): CardReaderPaymentController = CardReaderPaymentController( + scope = scoppe, + cardReaderManager = cardReaderManager, + orderRepository = orderRepository, + selectedSite = selectedSite, + appPrefs = appPrefs, + paymentCollectibilityChecker = paymentCollectibilityChecker, + interacRefundableChecker = interacRefundableChecker, + tracker = tracker, + trackCancelledFlow = trackCancelledFlow, + currencyFormatter = currencyFormatter, + errorMapper = errorMapper, + interacRefundErrorMapper = interacRefundErrorMapper, + wooStore = wooStore, + dispatchers = dispatchers, + cardReaderTrackingInfoKeeper = cardReaderTrackingInfoKeeper, + paymentStateProvider = paymentStateProvider, + cardReaderPaymentOrderHelper = cardReaderPaymentOrderHelper, + paymentReceiptHelper = paymentReceiptHelper, + cardReaderOnboardingChecker = cardReaderOnboardingChecker, + cardReaderConfigProvider = cardReaderConfigProvider, + paymentReceiptShare = paymentReceiptShare, + paymentOrRefund = PaymentOrRefund.Payment( + orderId = orderId, + paymentType = paymentType + ), + cardReaderType = CardReaderType.EXTERNAL, + isTTPPaymentInProgress = isTTPPaymentInProgress, + ) +} \ No newline at end of file diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 41063104250..b9c7ebb37ab 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -4,30 +4,13 @@ import android.os.Parcelable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.woocommerce.android.AppPrefs import com.woocommerce.android.R -import com.woocommerce.android.cardreader.CardReaderManager import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected import com.woocommerce.android.model.Order -import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.ui.orders.details.OrderDetailRepository -import com.woocommerce.android.ui.payments.cardreader.CardReaderCountryConfigProvider import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund -import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderOnboardingChecker -import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType -import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracRefundErrorMapper -import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracRefundableChecker -import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker -import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper -import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentControllerFactory import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState -import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentStateProvider -import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderTrackCanceledFlowAction -import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper -import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare -import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper -import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent @@ -37,8 +20,6 @@ import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice -import com.woocommerce.android.util.CoroutineDispatchers -import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.WooLog import com.woocommerce.android.util.WooLog.T import com.woocommerce.android.viewmodel.ResourceProvider @@ -48,7 +29,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize -import org.wordpress.android.fluxc.store.WooCommerceStore import javax.inject.Inject private const val KEY_TTP_PAYMENT_IN_PROGRESS = "ttp_payment_in_progress" @@ -63,26 +43,7 @@ class WooPosTotalsViewModel @Inject constructor( private val priceFormat: WooPosFormatPrice, private val analyticsTracker: WooPosAnalyticsTracker, private val networkStatus: WooPosNetworkStatus, - private val cardReaderManager: CardReaderManager, - private val orderRepository: OrderDetailRepository, - private val selectedSite: SelectedSite, - private val appPrefs: AppPrefs = AppPrefs, - private val paymentCollectibilityChecker: CardReaderPaymentCollectibilityChecker, - private val interacRefundableChecker: CardReaderInteracRefundableChecker, - private val tracker: PaymentsFlowTracker, - private val trackCancelledFlow: CardReaderTrackCanceledFlowAction, - private val currencyFormatter: CurrencyFormatter, - private val errorMapper: CardReaderPaymentErrorMapper, - private val interacRefundErrorMapper: CardReaderInteracRefundErrorMapper, - private val wooStore: WooCommerceStore, - private val dispatchers: CoroutineDispatchers, - private val cardReaderTrackingInfoKeeper: CardReaderTrackingInfoKeeper, - private val paymentStateProvider: CardReaderPaymentStateProvider, - private val cardReaderPaymentOrderHelper: CardReaderPaymentOrderHelper, - private val paymentReceiptHelper: PaymentReceiptHelper, - private val cardReaderOnboardingChecker: CardReaderOnboardingChecker, - private val cardReaderConfigProvider: CardReaderCountryConfigProvider, - private val paymentReceiptShare: PaymentReceiptShare, + private val cardReaderPaymentControllerFactory: CardReaderPaymentControllerFactory, private val savedState: SavedStateHandle, ) : ViewModel() { @@ -115,33 +76,10 @@ class WooPosTotalsViewModel @Inject constructor( private var cardReaderPaymentController: CardReaderPaymentController? = null private fun createCardReaderPaymentController(orderId: Long) { - cardReaderPaymentController = CardReaderPaymentController( - scope = viewModelScope, - cardReaderManager = cardReaderManager, - orderRepository = orderRepository, - selectedSite = selectedSite, - appPrefs = appPrefs, - paymentCollectibilityChecker = paymentCollectibilityChecker, - interacRefundableChecker = interacRefundableChecker, - tracker = tracker, - trackCancelledFlow = trackCancelledFlow, - currencyFormatter = currencyFormatter, - errorMapper = errorMapper, - interacRefundErrorMapper = interacRefundErrorMapper, - wooStore = wooStore, - dispatchers = dispatchers, - cardReaderTrackingInfoKeeper = cardReaderTrackingInfoKeeper, - paymentStateProvider = paymentStateProvider, - cardReaderPaymentOrderHelper = cardReaderPaymentOrderHelper, - paymentReceiptHelper = paymentReceiptHelper, - cardReaderOnboardingChecker = cardReaderOnboardingChecker, - cardReaderConfigProvider = cardReaderConfigProvider, - paymentReceiptShare = paymentReceiptShare, - paymentOrRefund = PaymentOrRefund.Payment( - orderId = orderId, - paymentType = PaymentOrRefund.Payment.PaymentType.WOO_POS - ), - cardReaderType = CardReaderType.EXTERNAL, + cardReaderPaymentController = cardReaderPaymentControllerFactory.create( + orderId = orderId, + paymentType = PaymentOrRefund.Payment.PaymentType.WOO_POS, + scoppe = viewModelScope, isTTPPaymentInProgress = ::isTTPPaymentInProgress, ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 6bf11ffb79d..db407479959 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -18,6 +18,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracR import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentControllerFactory import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentStateProvider import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderTrackCanceledFlowAction import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper @@ -68,6 +69,7 @@ class WooPosTotalsViewModelTest { private val networkStatus: WooPosNetworkStatus = mock() private val childrenToParentEventSender: WooPosChildrenToParentEventSender = mock() + private val cardReaderManager: CardReaderManager = mock() private val orderRepository: OrderDetailRepository = mock() private val selectedSite: SelectedSite = mock() @@ -87,6 +89,28 @@ class WooPosTotalsViewModelTest { private val cardReaderOnboardingChecker: CardReaderOnboardingChecker = mock() private val cardReaderConfigProvider: CardReaderCountryConfigProvider = mock() private val paymentReceiptShare: PaymentReceiptShare = mock() + private val paymentControllerFactory = CardReaderPaymentControllerFactory( + cardReaderManager = cardReaderManager, + orderRepository = orderRepository, + selectedSite = selectedSite, + appPrefs = appPrefs, + paymentCollectibilityChecker = paymentCollectibilityChecker, + interacRefundableChecker = interacRefundableChecker, + tracker = tracker, + trackCancelledFlow = trackCanceledFlow, + currencyFormatter = currencyFormatter, + errorMapper = errorMapper, + interacRefundErrorMapper = interacRefundErrorMapper, + wooStore = wooStore, + dispatchers = coroutinesTestRule.testDispatchers, + cardReaderTrackingInfoKeeper = cardReaderTrackingInfoKeeper, + paymentStateProvider = paymentStateProvider, + cardReaderPaymentOrderHelper = cardReaderPaymentOrderHelper, + paymentReceiptHelper = paymentReceiptHelper, + cardReaderOnboardingChecker = cardReaderOnboardingChecker, + cardReaderConfigProvider = cardReaderConfigProvider, + paymentReceiptShare = paymentReceiptShare, + ) private fun createMockSavedStateHandle(): SavedStateHandle { return SavedStateHandle( @@ -632,26 +656,7 @@ class WooPosTotalsViewModelTest { priceFormat, analyticsTracker, networkStatus, - cardReaderManager = cardReaderManager, - orderRepository = orderRepository, - selectedSite = selectedSite, - appPrefs = appPrefs, - paymentCollectibilityChecker = paymentCollectibilityChecker, - interacRefundableChecker = interacRefundableChecker, - tracker = tracker, - trackCancelledFlow = trackCanceledFlow, - currencyFormatter = currencyFormatter, - errorMapper = errorMapper, - interacRefundErrorMapper = interacRefundErrorMapper, - wooStore = wooStore, - dispatchers = coroutinesTestRule.testDispatchers, - cardReaderTrackingInfoKeeper = cardReaderTrackingInfoKeeper, - paymentStateProvider = paymentStateProvider, - cardReaderPaymentOrderHelper = cardReaderPaymentOrderHelper, - paymentReceiptHelper = paymentReceiptHelper, - cardReaderOnboardingChecker = cardReaderOnboardingChecker, - cardReaderConfigProvider = cardReaderConfigProvider, - paymentReceiptShare = paymentReceiptShare, - savedState + cardReaderPaymentControllerFactory = paymentControllerFactory, + savedState, ) } From 43c467585407aa190fa35a847265f5256fcffcd4 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 27 Nov 2024 10:34:44 +0100 Subject: [PATCH 026/132] Satisfy detekt's complaints --- .../payment/controller/CardReaderPaymentControllerFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt index e142ab2fa84..6c7c63ec38e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt @@ -81,4 +81,4 @@ class CardReaderPaymentControllerFactory @Inject constructor( cardReaderType = CardReaderType.EXTERNAL, isTTPPaymentInProgress = isTTPPaymentInProgress, ) -} \ No newline at end of file +} From 1bd998114d7ae64cdda37f3f46001af8394d9473 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 27 Nov 2024 10:35:25 +0100 Subject: [PATCH 027/132] Fix typo --- .../payment/controller/CardReaderPaymentControllerFactory.kt | 4 ++-- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt index 6c7c63ec38e..023dd18a4ca 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt @@ -50,10 +50,10 @@ class CardReaderPaymentControllerFactory @Inject constructor( fun create( orderId: Long, paymentType: PaymentType, - scoppe: CoroutineScope, + coroutineScope: CoroutineScope, isTTPPaymentInProgress: KMutableProperty0, ): CardReaderPaymentController = CardReaderPaymentController( - scope = scoppe, + scope = coroutineScope, cardReaderManager = cardReaderManager, orderRepository = orderRepository, selectedSite = selectedSite, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index b9c7ebb37ab..e847d39027c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -79,7 +79,7 @@ class WooPosTotalsViewModel @Inject constructor( cardReaderPaymentController = cardReaderPaymentControllerFactory.create( orderId = orderId, paymentType = PaymentOrRefund.Payment.PaymentType.WOO_POS, - scoppe = viewModelScope, + coroutineScope = viewModelScope, isTTPPaymentInProgress = ::isTTPPaymentInProgress, ) } From 622dc065e3b8c5a84fb31b715ff5ac1724ab6ce2 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Sat, 30 Nov 2024 17:48:24 +0100 Subject: [PATCH 028/132] Remove "collect payment" button Start the flow automatically --- .../woopos/home/totals/WooPosTotalsScreen.kt | 29 +------------------ .../woopos/home/totals/WooPosTotalsUIEvent.kt | 1 - .../home/totals/WooPosTotalsViewModel.kt | 2 +- .../home/totals/WooPosTotalsViewModelTest.kt | 9 ++---- 4 files changed, 5 insertions(+), 36 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index ac48ec755ed..0be6d34a9ad 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -24,12 +23,7 @@ import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -42,12 +36,10 @@ import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.component.Button -import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButtonLarge import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosErrorScreen import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.totals.payment.success.WooPosPaymentSuccessScreen -import kotlinx.coroutines.delay @Composable fun WooPosTotalsScreen(modifier: Modifier = Modifier) { @@ -65,7 +57,7 @@ private fun WooPosTotalsScreen( Box(modifier = modifier) { StateChangeAnimated(visible = state is WooPosTotalsViewState.Totals) { if (state is WooPosTotalsViewState.Totals) { - TotalsLoaded(state = state, onUIEvent = onUIEvent) + TotalsLoaded(state = state) } } @@ -109,15 +101,7 @@ private fun StateChangeAnimated( @Composable private fun TotalsLoaded( state: WooPosTotalsViewState.Totals, - onUIEvent: (WooPosTotalsUIEvent) -> Unit ) { - var isButtonVisible by remember { mutableStateOf(false) } - - LaunchedEffect(Unit) { - delay(300) - isButtonVisible = true - } - Column( modifier = Modifier .fillMaxSize() @@ -144,17 +128,6 @@ private fun TotalsLoaded( style = MaterialTheme.typography.body1, ) } - - AnimatedVisibility(visible = isButtonVisible) { - WooPosButtonLarge( - text = stringResource(R.string.woopos_payment_collect_payment_label), - onClick = { onUIEvent(WooPosTotalsUIEvent.CollectPaymentClicked) }, - modifier = Modifier - .animateEnterExit( - enter = slideInVertically { it }, - ) - ) - } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt index 7719dec6dd7..e0841c61cc9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt @@ -1,7 +1,6 @@ package com.woocommerce.android.ui.woopos.home.totals sealed class WooPosTotalsUIEvent { - data object CollectPaymentClicked : WooPosTotalsUIEvent() data object OnNewTransactionClicked : WooPosTotalsUIEvent() data object RetryOrderCreationClicked : WooPosTotalsUIEvent() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index e847d39027c..05b3ffaf3b9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -90,7 +90,6 @@ class WooPosTotalsViewModel @Inject constructor( fun onUIEvent(event: WooPosTotalsUIEvent) { when (event) { - is WooPosTotalsUIEvent.CollectPaymentClicked -> collectPayment() is WooPosTotalsUIEvent.OnNewTransactionClicked -> { viewModelScope.launch { childrenToParentEventSender.sendToParent( @@ -176,6 +175,7 @@ class WooPosTotalsViewModel @Inject constructor( dataState.value = dataState.value.copy(orderId = order.id) uiState.value = buildWooPosTotalsViewState(order) analyticsTracker.track(WooPosAnalyticsEvent.Event.OrderCreationSuccess) + collectPayment() }, onFailure = { error -> WooLog.e(T.POS, "Order creation failed - $error") diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index db407479959..6585856d7e3 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -530,7 +530,6 @@ class WooPosTotalsViewModelTest { ) // WHEN - viewModel.onUIEvent(WooPosTotalsUIEvent.CollectPaymentClicked) advanceUntilIdle() // THEN @@ -540,7 +539,7 @@ class WooPosTotalsViewModelTest { } @org.junit.Test - fun `given there is no internet, when trying to complete payment, then trigger proper event`() = runTest { + fun `given there is no internet, then trigger proper event`() = runTest { // GIVEN whenever(networkStatus.isConnected()).thenReturn(false) val productIds = listOf(1L, 2L, 3L) @@ -579,19 +578,18 @@ class WooPosTotalsViewModelTest { } // WHEN - val viewModel = createViewModel( + createViewModel( parentToChildrenEventReceiver = parentToChildrenEventReceiver, totalsRepository = totalsRepository, priceFormat = priceFormat, ) - viewModel.onUIEvent(WooPosTotalsUIEvent.CollectPaymentClicked) // THEN verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.NoInternet) } @org.junit.Test - fun `given there is no internet, when trying to complete payment, then collect payment method is not called`() = runTest { + fun `given there is no internet, then collect payment method is not called`() = runTest { // GIVEN whenever(networkStatus.isConnected()).thenReturn(false) val productIds = listOf(1L, 2L, 3L) @@ -635,7 +633,6 @@ class WooPosTotalsViewModelTest { totalsRepository = totalsRepository, priceFormat = priceFormat, ) - viewModel.onUIEvent(WooPosTotalsUIEvent.CollectPaymentClicked) // THEN verify(cardReaderManager, never()).collectPayment(any()) From b2c023193950ed549ab8f69ad975d8d2fc62549b Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 27 Nov 2024 17:26:45 +0100 Subject: [PATCH 029/132] Restrict cancelable states by common interface To be able to filter them using when expression. --- .../CardReaderPaymentOrRefundState.kt | 29 ++++++++++++------- .../CardReaderPaymentStateProvider.kt | 5 ++-- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentOrRefundState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentOrRefundState.kt index f88e355a018..709df9aa23c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentOrRefundState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentOrRefundState.kt @@ -6,7 +6,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError sealed class CardReaderPaymentOrRefundState { sealed class CardReaderPaymentState : CardReaderPaymentOrRefundState() { - data class LoadingData(val onCancel: () -> Unit) : CardReaderPaymentState() + data class LoadingData(override val onCancel: () -> Unit) : CardReaderPaymentState(), Cancelable data object ReFetchingOrder : CardReaderPaymentState() @@ -22,8 +22,8 @@ sealed class CardReaderPaymentOrRefundState { data class ExternalReaderCollectPaymentState( override val amountWithCurrencyLabel: String, override val cardReaderHint: Int? = null, - val onCancel: (() -> Unit) - ) : CollectingPayment(amountWithCurrencyLabel, cardReaderHint) + override val onCancel: (() -> Unit) + ) : CollectingPayment(amountWithCurrencyLabel, cardReaderHint), Cancelable } sealed class ProcessingPayment( @@ -34,8 +34,8 @@ sealed class CardReaderPaymentOrRefundState { data class ExternalReaderProcessingPayment( override val amountWithCurrencyLabel: String, - val onCancel: () -> Unit - ) : ProcessingPayment(amountWithCurrencyLabel) + override val onCancel: () -> Unit + ) : ProcessingPayment(amountWithCurrencyLabel), Cancelable } data class PrintingReceipt(val amountWithCurrencyLabel: String) : CardReaderPaymentState() @@ -113,7 +113,8 @@ sealed class CardReaderPaymentOrRefundState { onCancel = onCancel, onRetry = onRetry, cta = cta, - ) + ), + CardReaderPaymentOrRefundState.Cancelable data class NonCancelable( override val errorType: PaymentFlowError, override val onRetry: (() -> Unit), @@ -152,7 +153,8 @@ sealed class CardReaderPaymentOrRefundState { onCancel = onCancel, onRetry = onRetry, cta = cta, - ) + ), + CardReaderPaymentOrRefundState.Cancelable data class NonCancelable( override val errorType: PaymentFlowError, override val onRetry: (() -> Unit), @@ -171,13 +173,13 @@ sealed class CardReaderPaymentOrRefundState { } sealed class CardReaderInteracRefundState : CardReaderPaymentOrRefundState() { - data class LoadingData(val onCancel: () -> Unit) : CardReaderInteracRefundState() + data class LoadingData(override val onCancel: () -> Unit) : CardReaderInteracRefundState(), Cancelable data class CollectingInteracRefund( val amountWithCurrencyLabel: String, - val onCancel: () -> Unit, + override val onCancel: () -> Unit, @StringRes val cardReaderHint: Int? = null, - ) : CardReaderInteracRefundState() + ) : CardReaderInteracRefundState(), Cancelable data class ProcessingInteracRefund( val amountWithCurrencyLabel: String, @@ -202,7 +204,8 @@ sealed class CardReaderPaymentOrRefundState { onRetry = onRetry, onCancel = onCancel, cta = cta, - ) + ), + CardReaderPaymentOrRefundState.Cancelable data class NonCancelable( override val errorType: InteracRefundFlowError, override val onRetry: (() -> Unit), @@ -225,4 +228,8 @@ sealed class CardReaderPaymentOrRefundState { @StringRes val label: Int, val onCallToActionTapped: () -> Unit, ) + + interface Cancelable { + val onCancel: () -> Unit + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentStateProvider.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentStateProvider.kt index a69ae3e45e1..89f040734c0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentStateProvider.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentStateProvider.kt @@ -4,6 +4,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState.CollectingPayment import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentCapturing import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment @@ -20,7 +21,7 @@ class CardReaderPaymentStateProvider @Inject constructor() { onCancel: (() -> Unit), onRetry: (() -> Unit)? = null, cta: CardReaderPaymentOrRefundState.CallToAction? = null, - ) = when (cardReaderType) { + ): CardReaderPaymentState.PaymentFailed = when (cardReaderType) { BUILT_IN -> BuiltInReaderFailedPayment.Cancelable( errorType = errorType, amountWithCurrencyLabel = amountWithCurrencyLabel, @@ -42,7 +43,7 @@ class CardReaderPaymentStateProvider @Inject constructor() { errorType: PaymentFlowError, onRetry: (() -> Unit), cta: CardReaderPaymentOrRefundState.CallToAction? = null, - ) = when (cardReaderType) { + ): CardReaderPaymentState.PaymentFailed = when (cardReaderType) { BUILT_IN -> BuiltInReaderFailedPayment.NonCancelable( errorType = errorType, onRetry = onRetry, From d1d6e76e0bb415bcaeacc1c906bbae098ae3513b Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 28 Nov 2024 11:38:29 +0100 Subject: [PATCH 030/132] Attempt to cancel a payment on back press --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 05b3ffaf3b9..60ec87fca32 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -25,6 +25,7 @@ import com.woocommerce.android.util.WooLog.T import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.getStateFlow import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -135,6 +136,7 @@ class WooPosTotalsViewModel @Inject constructor( is ParentToChildrenEvent.BackFromCheckoutToCartClicked -> { cardReaderPaymentController?.onBackPressed() + cardReaderPaymentController?.onCleared() uiState.value = InitialState } From 347b3b0d9ecb26bc3297302acdb20aa84bd7bc32 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 28 Nov 2024 13:26:23 +0100 Subject: [PATCH 031/132] Revert "Restrict cancelable states by common interface" This reverts commit a02047d10024b546a79410a8a2445422514cac5b. --- .../CardReaderPaymentOrRefundState.kt | 29 +++++++------------ .../CardReaderPaymentStateProvider.kt | 5 ++-- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentOrRefundState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentOrRefundState.kt index 709df9aa23c..f88e355a018 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentOrRefundState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentOrRefundState.kt @@ -6,7 +6,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError sealed class CardReaderPaymentOrRefundState { sealed class CardReaderPaymentState : CardReaderPaymentOrRefundState() { - data class LoadingData(override val onCancel: () -> Unit) : CardReaderPaymentState(), Cancelable + data class LoadingData(val onCancel: () -> Unit) : CardReaderPaymentState() data object ReFetchingOrder : CardReaderPaymentState() @@ -22,8 +22,8 @@ sealed class CardReaderPaymentOrRefundState { data class ExternalReaderCollectPaymentState( override val amountWithCurrencyLabel: String, override val cardReaderHint: Int? = null, - override val onCancel: (() -> Unit) - ) : CollectingPayment(amountWithCurrencyLabel, cardReaderHint), Cancelable + val onCancel: (() -> Unit) + ) : CollectingPayment(amountWithCurrencyLabel, cardReaderHint) } sealed class ProcessingPayment( @@ -34,8 +34,8 @@ sealed class CardReaderPaymentOrRefundState { data class ExternalReaderProcessingPayment( override val amountWithCurrencyLabel: String, - override val onCancel: () -> Unit - ) : ProcessingPayment(amountWithCurrencyLabel), Cancelable + val onCancel: () -> Unit + ) : ProcessingPayment(amountWithCurrencyLabel) } data class PrintingReceipt(val amountWithCurrencyLabel: String) : CardReaderPaymentState() @@ -113,8 +113,7 @@ sealed class CardReaderPaymentOrRefundState { onCancel = onCancel, onRetry = onRetry, cta = cta, - ), - CardReaderPaymentOrRefundState.Cancelable + ) data class NonCancelable( override val errorType: PaymentFlowError, override val onRetry: (() -> Unit), @@ -153,8 +152,7 @@ sealed class CardReaderPaymentOrRefundState { onCancel = onCancel, onRetry = onRetry, cta = cta, - ), - CardReaderPaymentOrRefundState.Cancelable + ) data class NonCancelable( override val errorType: PaymentFlowError, override val onRetry: (() -> Unit), @@ -173,13 +171,13 @@ sealed class CardReaderPaymentOrRefundState { } sealed class CardReaderInteracRefundState : CardReaderPaymentOrRefundState() { - data class LoadingData(override val onCancel: () -> Unit) : CardReaderInteracRefundState(), Cancelable + data class LoadingData(val onCancel: () -> Unit) : CardReaderInteracRefundState() data class CollectingInteracRefund( val amountWithCurrencyLabel: String, - override val onCancel: () -> Unit, + val onCancel: () -> Unit, @StringRes val cardReaderHint: Int? = null, - ) : CardReaderInteracRefundState(), Cancelable + ) : CardReaderInteracRefundState() data class ProcessingInteracRefund( val amountWithCurrencyLabel: String, @@ -204,8 +202,7 @@ sealed class CardReaderPaymentOrRefundState { onRetry = onRetry, onCancel = onCancel, cta = cta, - ), - CardReaderPaymentOrRefundState.Cancelable + ) data class NonCancelable( override val errorType: InteracRefundFlowError, override val onRetry: (() -> Unit), @@ -228,8 +225,4 @@ sealed class CardReaderPaymentOrRefundState { @StringRes val label: Int, val onCallToActionTapped: () -> Unit, ) - - interface Cancelable { - val onCancel: () -> Unit - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentStateProvider.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentStateProvider.kt index 89f040734c0..a69ae3e45e1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentStateProvider.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentStateProvider.kt @@ -4,7 +4,6 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.BUILT_IN import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError -import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState.CollectingPayment import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentCapturing import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment @@ -21,7 +20,7 @@ class CardReaderPaymentStateProvider @Inject constructor() { onCancel: (() -> Unit), onRetry: (() -> Unit)? = null, cta: CardReaderPaymentOrRefundState.CallToAction? = null, - ): CardReaderPaymentState.PaymentFailed = when (cardReaderType) { + ) = when (cardReaderType) { BUILT_IN -> BuiltInReaderFailedPayment.Cancelable( errorType = errorType, amountWithCurrencyLabel = amountWithCurrencyLabel, @@ -43,7 +42,7 @@ class CardReaderPaymentStateProvider @Inject constructor() { errorType: PaymentFlowError, onRetry: (() -> Unit), cta: CardReaderPaymentOrRefundState.CallToAction? = null, - ): CardReaderPaymentState.PaymentFailed = when (cardReaderType) { + ) = when (cardReaderType) { BUILT_IN -> BuiltInReaderFailedPayment.NonCancelable( errorType = errorType, onRetry = onRetry, From 1356746ddb1e12e756eecb8d6905e936d36da103 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Sat, 30 Nov 2024 18:18:04 +0100 Subject: [PATCH 032/132] Use custom, cancelable payment scope --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 60ec87fca32..4e63b231dd9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.woopos.home.totals import android.os.Parcelable +import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -25,9 +26,14 @@ import com.woocommerce.android.util.WooLog.T import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.getStateFlow import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import javax.inject.Inject @@ -74,13 +80,15 @@ class WooPosTotalsViewModel @Inject constructor( savedState[KEY_TTP_PAYMENT_IN_PROGRESS] = value } + private var paymentScope: CoroutineScope? = null private var cardReaderPaymentController: CardReaderPaymentController? = null private fun createCardReaderPaymentController(orderId: Long) { + paymentScope = CoroutineScope(viewModelScope.coroutineContext + SupervisorJob() + Dispatchers.Main.immediate) cardReaderPaymentController = cardReaderPaymentControllerFactory.create( orderId = orderId, paymentType = PaymentOrRefund.Payment.PaymentType.WOO_POS, - coroutineScope = viewModelScope, + coroutineScope = paymentScope!!, isTTPPaymentInProgress = ::isTTPPaymentInProgress, ) } @@ -137,6 +145,7 @@ class WooPosTotalsViewModel @Inject constructor( is ParentToChildrenEvent.BackFromCheckoutToCartClicked -> { cardReaderPaymentController?.onBackPressed() cardReaderPaymentController?.onCleared() + paymentScope?.cancel() uiState.value = InitialState } @@ -210,6 +219,7 @@ class WooPosTotalsViewModel @Inject constructor( } override fun onCleared() { + paymentScope?.cancel() cardReaderPaymentController?.onCleared() } From 587a1908e330389ca5a39a7dd83343ffd55d04d0 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 11:55:44 +0100 Subject: [PATCH 033/132] Update paymentScope to inherit from viewModel scope --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 4e63b231dd9..77e0f704bcb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -28,6 +28,7 @@ import com.woocommerce.android.viewmodel.getStateFlow import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.delay @@ -37,6 +38,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import javax.inject.Inject +import kotlin.text.get private const val KEY_TTP_PAYMENT_IN_PROGRESS = "ttp_payment_in_progress" @@ -84,7 +86,7 @@ class WooPosTotalsViewModel @Inject constructor( private var cardReaderPaymentController: CardReaderPaymentController? = null private fun createCardReaderPaymentController(orderId: Long) { - paymentScope = CoroutineScope(viewModelScope.coroutineContext + SupervisorJob() + Dispatchers.Main.immediate) + paymentScope = CoroutineScope(SupervisorJob(viewModelScope.coroutineContext[Job])) cardReaderPaymentController = cardReaderPaymentControllerFactory.create( orderId = orderId, paymentType = PaymentOrRefund.Payment.PaymentType.WOO_POS, From 608e19f5ab3f878f71b6c4d854a028c1b367cb7e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 11:58:01 +0100 Subject: [PATCH 034/132] Remove unused imports --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 77e0f704bcb..4a939dd1e77 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -1,7 +1,6 @@ package com.woocommerce.android.ui.woopos.home.totals import android.os.Parcelable -import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -27,18 +26,14 @@ import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.getStateFlow import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import javax.inject.Inject -import kotlin.text.get private const val KEY_TTP_PAYMENT_IN_PROGRESS = "ttp_payment_in_progress" From 3248e1d862661421c62579788d48b9105a95fd11 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 13:08:16 +0100 Subject: [PATCH 035/132] Remove redundant `onClear` implementation --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 4a939dd1e77..433f792e522 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -215,11 +215,6 @@ class WooPosTotalsViewModel @Inject constructor( ) } - override fun onCleared() { - paymentScope?.cancel() - cardReaderPaymentController?.onCleared() - } - @Parcelize private data class TotalsDataState( val orderId: Long = EMPTY_ORDER_ID, From c289e15f489c3a2d15404676ba1903a9846cf34e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 16:48:59 +0100 Subject: [PATCH 036/132] Emit "reader not connected" error in ui state --- .../home/totals/WooPosTotalsViewModel.kt | 41 ++++++++++++++++--- .../home/totals/WooPosTotalsViewState.kt | 11 ++++- WooCommerce/src/main/res/values/strings.xml | 4 ++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 433f792e522..340033f2906 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.woocommerce.android.R +import com.woocommerce.android.cardreader.connection.CardReaderStatus import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected import com.woocommerce.android.model.Order import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund @@ -57,11 +58,12 @@ class WooPosTotalsViewModel @Inject constructor( private val InitialState = WooPosTotalsViewState.Loading } - private val uiState = savedState.getStateFlow( - scope = viewModelScope, - initialValue = InitialState, - key = "woo_pos_totals_view_state" - ) + private val uiState: MutableStateFlow = + savedState.getStateFlow( + scope = viewModelScope, + initialValue = InitialState, + key = "woo_pos_totals_view_state" + ) val state: StateFlow = uiState @@ -92,6 +94,35 @@ class WooPosTotalsViewModel @Inject constructor( init { listenUpEvents() + observeCardReaderStatus() + } + + private fun observeCardReaderStatus() { + viewModelScope.launch { + cardReaderFacade.readerStatus.collect { status -> + when (status) { + is CardReaderStatus.NotConnected -> { + val state = uiState.value + if (state !is WooPosTotalsViewState.Totals) return@collect + uiState.value = state.copy( + error = WooPosTotalsViewState.Totals.Error( + title = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title), + subtitle = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle), + actionButonLable = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label), + onAction = { cardReaderFacade.connectToReader() } + ) + ) + } + else -> { + val state = uiState.value + if (state !is WooPosTotalsViewState.Totals) return@collect + uiState.value = state.copy(error = null) + // now if order is created, collect payment + // TODO: @samiuelson + } + } + } + } } fun onUIEvent(event: WooPosTotalsUIEvent) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index 04cf8b9b710..ee08adb1c3b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -12,7 +12,16 @@ sealed class WooPosTotalsViewState : Parcelable { val orderTaxText: String, val orderTotalText: String, val paymentStateText: String, - ) : WooPosTotalsViewState() + val error: Error? = null, + ) : WooPosTotalsViewState() { + @Parcelize + data class Error( + val title: String, + val subtitle: String, + val actionButonLable: String, + val onAction: () -> Unit, + ): Parcelable + } data class PaymentSuccess(var orderTotalText: String) : WooPosTotalsViewState() diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index c951fb10530..3214c9088b5 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4297,6 +4297,10 @@ Couldn\'t create order A payment of %1$s was successfully made + Reader not connected + To process this payment, please connect your reader. + Connect to reader + Dimmed background. Tap to close the menu. Card reader connected Card reader not connected. Double tap to connect From 09b3d60e49ea6f8ba06aa90c9639158cb21ea1d9 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 18:30:00 +0100 Subject: [PATCH 037/132] Add unit test: given reader not connected, when VM checkout clicked, then should show error --- .../home/totals/WooPosTotalsViewModelTest.kt | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 6585856d7e3..36902c5055b 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.woopos.home.totals import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.AppPrefs +import com.woocommerce.android.R import com.woocommerce.android.cardreader.CardReaderManager import com.woocommerce.android.cardreader.connection.CardReaderStatus import com.woocommerce.android.cardreader.connection.event.BluetoothCardReaderMessages @@ -39,6 +40,7 @@ import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.ResourceProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest @@ -69,7 +71,7 @@ class WooPosTotalsViewModelTest { private val networkStatus: WooPosNetworkStatus = mock() private val childrenToParentEventSender: WooPosChildrenToParentEventSender = mock() - + private val resourceProvider: ResourceProvider = mock() private val cardReaderManager: CardReaderManager = mock() private val orderRepository: OrderDetailRepository = mock() private val selectedSite: SelectedSite = mock() @@ -141,6 +143,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { flow {} } + whenever(cardReaderFacade.readerStatus).thenAnswer { cardReaderManager.readerStatus } } @Test @@ -154,7 +157,7 @@ class WooPosTotalsViewModelTest { // WHEN val viewModel = createViewModel( savedState = savedState, - parentToChildrenEventReceiver = parentToChildrenEventReceiver + parentToChildrenEventReceiver = parentToChildrenEventReceiver, ) // THEN @@ -638,8 +641,74 @@ class WooPosTotalsViewModelTest { verify(cardReaderManager, never()).collectPayment(any()) } + @Test + fun `given reader not connected, when VM checkout clicked, then should show error`() = runTest { + // GIVEN + val readerStatus: StateFlow = + MutableStateFlow(CardReaderStatus.NotConnected()) + whenever(cardReaderManager.readerStatus).thenReturn(readerStatus) + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) + .thenReturn("Reader not connected") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle)) + .thenReturn("To process this payment, please connect your reader.") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label)) + .thenReturn("Connect to reader") + + val productIds = listOf(1L, 2L, 3L) + val order = Order.getEmptyOrder( + dateCreated = Date(), + dateModified = Date() + ).copy( + totalTax = BigDecimal("2.00"), + items = listOf( + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ) + ), + productsTotal = BigDecimal("3.00"), + total = BigDecimal("5.00"), + ) + val totalsRepository: WooPosTotalsRepository = mock { + onBlocking { + createOrderWithProducts(productIds = productIds) + }.thenReturn(Result.success(order)) + } + val priceFormat: WooPosFormatPrice = mock { + onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("2.00$") + onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("3.00$") + onBlocking { invoke(BigDecimal("5.00")) }.thenReturn("5.00$") + } + val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) + val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { + on { events }.thenReturn(parentToChildrenEventFlow) + } + + // WHEN + val viewModel = createViewModel( + totalsRepository = totalsRepository, + priceFormat = priceFormat, + parentToChildrenEventReceiver = parentToChildrenEventReceiver, + ) + + // THEN + assertThat(viewModel.state.value).isInstanceOf(WooPosTotalsViewState.Totals::class.java) + val state = viewModel.state.value as WooPosTotalsViewState.Totals + assertThat(state.error).isNotNull() + with(state.error!!) { + assertThat(title).isEqualTo("Reader not connected") + assertThat(subtitle).isEqualTo("To process this payment, please connect your reader.") + assertThat(actionButonLable).isEqualTo("Connect to reader") + } + } + private fun createViewModel( - resourceProvider: ResourceProvider = mock(), + resourceProvider: ResourceProvider = this.resourceProvider, parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock(), totalsRepository: WooPosTotalsRepository = mock(), priceFormat: WooPosFormatPrice = mock(), From 0c6d3d51bc89821e149791253c96d33982716887 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 18:30:19 +0100 Subject: [PATCH 038/132] Fix code formatting --- .../android/ui/woopos/home/totals/WooPosTotalsViewState.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index ee08adb1c3b..a3ec7e2cbbe 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -20,7 +20,7 @@ sealed class WooPosTotalsViewState : Parcelable { val subtitle: String, val actionButonLable: String, val onAction: () -> Unit, - ): Parcelable + ) : Parcelable } data class PaymentSuccess(var orderTotalText: String) : WooPosTotalsViewState() From 15dfcc1cd119877bb92e4083ffb7fa080c4ad633 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 18:30:34 +0100 Subject: [PATCH 039/132] Remove redundant val --- .../android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 36902c5055b..5fd09feeac6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -631,7 +631,7 @@ class WooPosTotalsViewModelTest { } // WHEN - val viewModel = createViewModel( + createViewModel( parentToChildrenEventReceiver = parentToChildrenEventReceiver, totalsRepository = totalsRepository, priceFormat = priceFormat, From e627ac8271a42f17ac0f2f39bc651175af6223f7 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 18:37:53 +0100 Subject: [PATCH 040/132] Add test: given reader connected, when checkout clicked, then should hide error --- .../home/totals/WooPosTotalsViewModelTest.kt | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 5fd09feeac6..55d7e2fb0bc 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -642,7 +642,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given reader not connected, when VM checkout clicked, then should show error`() = runTest { + fun `given reader not connected, when checkout clicked, then should show error`() = runTest { // GIVEN val readerStatus: StateFlow = MutableStateFlow(CardReaderStatus.NotConnected()) @@ -707,6 +707,67 @@ class WooPosTotalsViewModelTest { } } + @Test + fun `given reader connected, when checkout clicked, then should hide error`() { + // GIVEN + val readerStatus: StateFlow = + MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderManager.readerStatus).thenReturn(readerStatus) + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) + .thenReturn("Reader not connected") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle)) + .thenReturn("To process this payment, please connect your reader.") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label)) + .thenReturn("Connect to reader") + + val productIds = listOf(1L, 2L, 3L) + val order = Order.getEmptyOrder( + dateCreated = Date(), + dateModified = Date() + ).copy( + totalTax = BigDecimal("2.00"), + items = listOf( + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ) + ), + productsTotal = BigDecimal("3.00"), + total = BigDecimal("5.00"), + ) + val totalsRepository: WooPosTotalsRepository = mock { + onBlocking { + createOrderWithProducts(productIds = productIds) + }.thenReturn(Result.success(order)) + } + val priceFormat: WooPosFormatPrice = mock { + onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("2.00$") + onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("3.00$") + onBlocking { invoke(BigDecimal("5.00")) }.thenReturn("5.00$") + } + val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) + val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { + on { events }.thenReturn(parentToChildrenEventFlow) + } + + // WHEN + val viewModel = createViewModel( + totalsRepository = totalsRepository, + priceFormat = priceFormat, + parentToChildrenEventReceiver = parentToChildrenEventReceiver, + ) + + // THEN + assertThat(viewModel.state.value).isInstanceOf(WooPosTotalsViewState.Totals::class.java) + val state = viewModel.state.value as WooPosTotalsViewState.Totals + assertThat(state.error).isNull() + } + private fun createViewModel( resourceProvider: ResourceProvider = this.resourceProvider, parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock(), From edf771844c021f2947891a6dd30f556ac1670097 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 18:41:14 +0100 Subject: [PATCH 041/132] Fix typo --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 2 +- .../android/ui/woopos/home/totals/WooPosTotalsViewState.kt | 2 +- .../android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 340033f2906..f84e41a4979 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -108,7 +108,7 @@ class WooPosTotalsViewModel @Inject constructor( error = WooPosTotalsViewState.Totals.Error( title = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title), subtitle = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle), - actionButonLable = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label), + actionButonLabel = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label), onAction = { cardReaderFacade.connectToReader() } ) ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index a3ec7e2cbbe..ca5cb98029b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -18,7 +18,7 @@ sealed class WooPosTotalsViewState : Parcelable { data class Error( val title: String, val subtitle: String, - val actionButonLable: String, + val actionButonLabel: String, val onAction: () -> Unit, ) : Parcelable } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 55d7e2fb0bc..f75792fc4bb 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -703,7 +703,7 @@ class WooPosTotalsViewModelTest { with(state.error!!) { assertThat(title).isEqualTo("Reader not connected") assertThat(subtitle).isEqualTo("To process this payment, please connect your reader.") - assertThat(actionButonLable).isEqualTo("Connect to reader") + assertThat(actionButonLabel).isEqualTo("Connect to reader") } } From e896a1367c01ad11f15f46f49a62fef376762763 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 19:09:18 +0100 Subject: [PATCH 042/132] Add test: given reader not connected, when checkout clicked and error CTA clicked, then should try connecting to reader --- .../home/totals/WooPosTotalsViewModelTest.kt | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index f75792fc4bb..31237b37a35 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -768,6 +768,66 @@ class WooPosTotalsViewModelTest { assertThat(state.error).isNull() } + @Test + fun `given reader not connected, when checkout clicked and error CTA clicked, then should try connecting to reader`() = runTest { + // GIVEN + val readerStatus: StateFlow = + MutableStateFlow(CardReaderStatus.NotConnected()) + whenever(cardReaderManager.readerStatus).thenReturn(readerStatus) + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) + .thenReturn("Reader not connected") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle)) + .thenReturn("To process this payment, please connect your reader.") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label)) + .thenReturn("Connect to reader") + + val productIds = listOf(1L, 2L, 3L) + val order = Order.getEmptyOrder( + dateCreated = Date(), + dateModified = Date() + ).copy( + totalTax = BigDecimal("2.00"), + items = listOf( + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ) + ), + productsTotal = BigDecimal("3.00"), + total = BigDecimal("5.00"), + ) + val totalsRepository: WooPosTotalsRepository = mock { + onBlocking { + createOrderWithProducts(productIds = productIds) + }.thenReturn(Result.success(order)) + } + val priceFormat: WooPosFormatPrice = mock { + onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("2.00$") + onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("3.00$") + onBlocking { invoke(BigDecimal("5.00")) }.thenReturn("5.00$") + } + val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) + val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { + on { events }.thenReturn(parentToChildrenEventFlow) + } + + // WHEN + val viewModel = createViewModel( + totalsRepository = totalsRepository, + priceFormat = priceFormat, + parentToChildrenEventReceiver = parentToChildrenEventReceiver, + ) + (viewModel.state.value as WooPosTotalsViewState.Totals).error!!.onAction() + + // THEN + verify(cardReaderFacade).connectToReader() + } + private fun createViewModel( resourceProvider: ResourceProvider = this.resourceProvider, parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock(), From 742dfb64862252e89f48e82d6ffef648d1097a29 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 19:10:03 +0100 Subject: [PATCH 043/132] Build reader not connected error after order created --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index f84e41a4979..ffb3c8fe7f1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -237,12 +237,22 @@ class WooPosTotalsViewModel @Inject constructor( val subtotalAmount = order.productsTotal val taxAmount = order.totalTax val totalAmount = order.total + val error = when (cardReaderFacade.readerStatus.value) { + is Connected -> null + else -> WooPosTotalsViewState.Totals.Error( + title = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title), + subtitle = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle), + actionButonLabel = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label), + onAction = { cardReaderFacade.connectToReader() } + ) + } return WooPosTotalsViewState.Totals( orderSubtotalText = priceFormat(subtotalAmount), orderTaxText = priceFormat(taxAmount), orderTotalText = priceFormat(totalAmount), - paymentStateText = "" + paymentStateText = "", + error = error ) } From 92fb64bd627bac348f8492dde0fcfe931538a26f Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 19:17:51 +0100 Subject: [PATCH 044/132] Render error UI --- .../woopos/home/totals/WooPosTotalsScreen.kt | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 0be6d34a9ad..d8bf333207b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -117,11 +117,22 @@ private fun TotalsLoaded( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Spacer(modifier = Modifier.weight(1f)) - - TotalsGrid(state) + val error = state.error + if (error != null) { + Spacer(modifier = Modifier.weight(1f)) + WooPosErrorScreen( + modifier = Modifier.weight(1f), + message = error.title, + reason = error.subtitle, + primaryButton = Button( + text = error.actionButonLabel, + click = error.onAction + ), + adaptToScreenHeight = true, + ) + } - Spacer(modifier = Modifier.weight(1f)) + TotalsGrid(modifier = Modifier.weight(1f), state = state) Text( text = state.paymentStateText, @@ -132,9 +143,10 @@ private fun TotalsLoaded( } @Composable -private fun TotalsGrid(state: WooPosTotalsViewState.Totals) { +private fun TotalsGrid(modifier: Modifier = Modifier, + state: WooPosTotalsViewState.Totals) { Column( - modifier = Modifier + modifier = modifier .padding(24.dp.toAdaptivePadding()) .width(382.dp) ) { @@ -266,6 +278,29 @@ fun WooPosTotalsScreenPreview(modifier: Modifier = Modifier) { } } +@Composable +@WooPosPreview +fun WooPosTotalsScreenPreviewReaderNotConnected(modifier: Modifier = Modifier) { + WooPosTheme { + WooPosTotalsScreen( + modifier = modifier, + state = WooPosTotalsViewState.Totals( + orderSubtotalText = "$420.00", + orderTotalText = "$462.00", + orderTaxText = "$42.00", + paymentStateText = "Payment state", + error = WooPosTotalsViewState.Totals.Error( + title = "Reader not connected", + subtitle = "To process this payment, please connect your reader.", + actionButonLabel = "Connect to a reader", + onAction = {} + ) + ), + onUIEvent = {} + ) + } +} + @Composable @WooPosPreview fun WooPosTotalsScreenLoadingPreview() { From c98f31a675dc5c13b93b479b36abecd5eba48b23 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 21:40:04 +0100 Subject: [PATCH 045/132] Update totals screen UI --- .../ui/woopos/common/composeui/WooPosTheme.kt | 3 + .../woopos/home/totals/WooPosTotalsScreen.kt | 109 +++++++++++++----- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index 9150f76ceab..ad82ef482b3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -18,6 +18,7 @@ data class CustomColors( val border: Color, val success: Color, val error: Color, + val errorBackground: Color, val paymentSuccessBackground: Color, val paymentSuccessText: Color, val paymentSuccessIcon: Color, @@ -186,6 +187,7 @@ private val DarkCustomColors = CustomColors( border = WooPosColors.oldGrayMedium, success = WooPosColors.greenNotFromPalette, error = WooPosColors.darkCustomColorsError, + errorBackground = WooPosColors.Black90Alpha020, paymentSuccessBackground = WooPosColors.darkCustomColorsHomeBackground, paymentSuccessText = WooPosColors.oldGrayLight, paymentSuccessIcon = WooPosColors.darkCustomColorsHomeBackground, @@ -197,6 +199,7 @@ private val LightCustomColors = CustomColors( border = WooPosColors.lightCustomColorsBorder, success = WooPosColors.greenNotFromPalette, error = WooPosColors.lightCustomColorsError, + errorBackground = WooPosColors.WhiteAlpha060, paymentSuccessBackground = WooPosColors.White, paymentSuccessText = WooPosColors.Purple90, paymentSuccessIcon = Color.White, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index d8bf333207b..6790951c536 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -14,12 +15,14 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Divider +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -27,6 +30,8 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight @@ -36,6 +41,7 @@ import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.component.Button +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosErrorScreen import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding @@ -105,40 +111,66 @@ private fun TotalsLoaded( Column( modifier = Modifier .fillMaxSize() - .padding(16.dp.toAdaptivePadding()), + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Column( - modifier = Modifier - .weight(1f) - .fillMaxWidth() - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - val error = state.error - if (error != null) { - Spacer(modifier = Modifier.weight(1f)) - WooPosErrorScreen( - modifier = Modifier.weight(1f), - message = error.title, - reason = error.subtitle, - primaryButton = Button( - text = error.actionButonLabel, - click = error.onAction - ), - adaptToScreenHeight = true, - ) + val error = state.error + if (error != null) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1.1f) + .background(WooPosTheme.colors.errorBackground) + ) { + TotalsError(modifier = Modifier, error = error) } + } + TotalsGrid(modifier = Modifier.weight(1f), state = state) + } +} - TotalsGrid(modifier = Modifier.weight(1f), state = state) +@Composable +private fun TotalsError( + modifier: Modifier = Modifier, + error: WooPosTotalsViewState.Totals.Error +) { + Column( + modifier = modifier.padding(40.dp.toAdaptivePadding()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly, + ) { + Spacer(modifier = Modifier.height(56.dp.toAdaptivePadding())) - Text( - text = state.paymentStateText, - style = MaterialTheme.typography.body1, - ) - } + Icon( + modifier = Modifier.size(64.dp), + painter = painterResource(id = R.drawable.woo_pos_ic_error), + contentDescription = stringResource(id = R.string.woopos_error_icon_content_description), + tint = Color.Unspecified, + ) + + Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) + + Text( + text = error.title, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.SemiBold + ) + + Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) + + Text( + text = error.subtitle, + style = MaterialTheme.typography.h6 + ) + Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) + WooPosButton( + text = error.actionButonLabel, + onClick = error.onAction, + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + ) } } @@ -148,7 +180,9 @@ private fun TotalsGrid(modifier: Modifier = Modifier, Column( modifier = modifier .padding(24.dp.toAdaptivePadding()) - .width(382.dp) + .width(382.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { TotalsGridRow( textOne = stringResource(R.string.woopos_payment_subtotal_label), @@ -176,6 +210,10 @@ private fun TotalsGrid(modifier: Modifier = Modifier, fontWeightOne = FontWeight.Medium, fontWeightTwo = FontWeight.Bold, ) + Text( + text = state.paymentStateText, + style = MaterialTheme.typography.body1, + ) } } @@ -300,6 +338,19 @@ fun WooPosTotalsScreenPreviewReaderNotConnected(modifier: Modifier = Modifier) { ) } } +@Composable +@WooPosPreview +fun TotalsErrorPreview() { + val error = WooPosTotalsViewState.Totals.Error( + title = "Reader not connected", + subtitle = "To process this payment, please connect your reader.", + actionButonLabel = "Connect to a reader", + onAction = {} + ) + WooPosTheme { + TotalsError(modifier = Modifier, error = error) + } +} @Composable @WooPosPreview From 210de3413c88ad1e135fed6d877a43d5d101214f Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 22:18:01 +0100 Subject: [PATCH 046/132] Clean up code --- .../woopos/home/totals/WooPosTotalsScreen.kt | 7 ++++-- .../home/totals/WooPosTotalsViewModel.kt | 25 ++++++++----------- .../home/totals/WooPosTotalsViewModelTest.kt | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 6790951c536..74fe24027c2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -175,8 +175,10 @@ private fun TotalsError( } @Composable -private fun TotalsGrid(modifier: Modifier = Modifier, - state: WooPosTotalsViewState.Totals) { +private fun TotalsGrid( + modifier: Modifier = Modifier, + state: WooPosTotalsViewState.Totals +) { Column( modifier = modifier .padding(24.dp.toAdaptivePadding()) @@ -338,6 +340,7 @@ fun WooPosTotalsScreenPreviewReaderNotConnected(modifier: Modifier = Modifier) { ) } } + @Composable @WooPosPreview fun TotalsErrorPreview() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index ffb3c8fe7f1..5643bf37ed8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -104,14 +104,7 @@ class WooPosTotalsViewModel @Inject constructor( is CardReaderStatus.NotConnected -> { val state = uiState.value if (state !is WooPosTotalsViewState.Totals) return@collect - uiState.value = state.copy( - error = WooPosTotalsViewState.Totals.Error( - title = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title), - subtitle = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle), - actionButonLabel = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label), - onAction = { cardReaderFacade.connectToReader() } - ) - ) + uiState.value = state.copy(error = buildTotalsReaderNotConnectedError()) } else -> { val state = uiState.value @@ -239,12 +232,7 @@ class WooPosTotalsViewModel @Inject constructor( val totalAmount = order.total val error = when (cardReaderFacade.readerStatus.value) { is Connected -> null - else -> WooPosTotalsViewState.Totals.Error( - title = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title), - subtitle = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle), - actionButonLabel = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label), - onAction = { cardReaderFacade.connectToReader() } - ) + else -> buildTotalsReaderNotConnectedError() } return WooPosTotalsViewState.Totals( @@ -256,6 +244,15 @@ class WooPosTotalsViewModel @Inject constructor( ) } + private fun buildTotalsReaderNotConnectedError(): WooPosTotalsViewState.Totals.Error = WooPosTotalsViewState.Totals.Error( + title = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title), + subtitle = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle), + actionButonLabel = resourceProvider.getString( + R.string.woopos_success_totals_error_reader_not_connected_cta_button_label + ), + onAction = { cardReaderFacade.connectToReader() } + ) + @Parcelize private data class TotalsDataState( val orderId: Long = EMPTY_ORDER_ID, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 31237b37a35..d7dc4d01981 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -825,7 +825,7 @@ class WooPosTotalsViewModelTest { (viewModel.state.value as WooPosTotalsViewState.Totals).error!!.onAction() // THEN - verify(cardReaderFacade).connectToReader() + verify(cardReaderFacade).connectToReader() } private fun createViewModel( From ffe29f84c68a844683936e15aeb3bbd7e61a4633 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 22:27:30 +0100 Subject: [PATCH 047/132] Collect payment automatically after reader connects --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 5643bf37ed8..8952460cc04 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -7,6 +7,8 @@ import androidx.lifecycle.viewModelScope import com.woocommerce.android.R import com.woocommerce.android.cardreader.connection.CardReaderStatus import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected +import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connecting +import com.woocommerce.android.cardreader.connection.CardReaderStatus.NotConnected import com.woocommerce.android.model.Order import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController @@ -32,6 +34,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import javax.inject.Inject @@ -99,19 +102,20 @@ class WooPosTotalsViewModel @Inject constructor( private fun observeCardReaderStatus() { viewModelScope.launch { - cardReaderFacade.readerStatus.collect { status -> + cardReaderFacade.readerStatus.combine(dataState){status, data -> Pair(status, data)}.collect { (status, data) -> when (status) { - is CardReaderStatus.NotConnected -> { + is NotConnected, is Connecting -> { val state = uiState.value if (state !is WooPosTotalsViewState.Totals) return@collect uiState.value = state.copy(error = buildTotalsReaderNotConnectedError()) } - else -> { + is Connected -> { val state = uiState.value if (state !is WooPosTotalsViewState.Totals) return@collect uiState.value = state.copy(error = null) - // now if order is created, collect payment - // TODO: @samiuelson + if (data.orderId != EMPTY_ORDER_ID) { + collectPayment() + } } } } From 259e98e77ae7853a78fb005788b5ba124ebd2eab Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 2 Dec 2024 22:28:53 +0100 Subject: [PATCH 048/132] Satisfy detekt's complaints --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 8952460cc04..77c24461ffe 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.woocommerce.android.R -import com.woocommerce.android.cardreader.connection.CardReaderStatus import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connecting import com.woocommerce.android.cardreader.connection.CardReaderStatus.NotConnected @@ -102,7 +101,9 @@ class WooPosTotalsViewModel @Inject constructor( private fun observeCardReaderStatus() { viewModelScope.launch { - cardReaderFacade.readerStatus.combine(dataState){status, data -> Pair(status, data)}.collect { (status, data) -> + cardReaderFacade.readerStatus.combine( + dataState + ) { status, data -> Pair(status, data) }.collect { (status, data) -> when (status) { is NotConnected, is Connecting -> { val state = uiState.value From 9584d894e815da8746759b6cc5a3e9660f4cc2cd Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 12:23:43 +0100 Subject: [PATCH 049/132] Add unit test: given order draft created, when reader connects, then start payment automatically --- .../home/totals/WooPosTotalsViewModelTest.kt | 89 ++++++++++++++++--- 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index d7dc4d01981..e2afe965dd7 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -19,6 +19,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracR import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentControllerFactory import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentStateProvider import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderTrackCanceledFlowAction @@ -828,22 +829,90 @@ class WooPosTotalsViewModelTest { verify(cardReaderFacade).connectToReader() } + @Test + fun `given order draft created, when reader connects, then start payment automatically`() = runTest { + // GIVEN + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.NotConnected()) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) + .thenReturn("Reader not connected") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle)) + .thenReturn("To process this payment, please connect your reader.") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label)) + .thenReturn("Connect to reader") + + val productIds = listOf(1L, 2L, 3L) + val orderId = 23L + val order = Order.getEmptyOrder( + dateCreated = Date(), + dateModified = Date() + ).copy( + id = orderId, + totalTax = BigDecimal("2.00"), + items = listOf( + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ) + ), + productsTotal = BigDecimal("3.00"), + total = BigDecimal("5.00"), + ) + val totalsRepository: WooPosTotalsRepository = mock { + onBlocking { + createOrderWithProducts(productIds = productIds) + }.thenReturn(Result.success(order)) + } + val priceFormat: WooPosFormatPrice = mock { + onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("2.00$") + onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("3.00$") + onBlocking { invoke(BigDecimal("5.00")) }.thenReturn("5.00$") + } + val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) + val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { + on { events }.thenReturn(parentToChildrenEventFlow) + } + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + + // WHEN + val vm = createViewModel( + totalsRepository = totalsRepository, + priceFormat = priceFormat, + parentToChildrenEventReceiver = parentToChildrenEventReceiver, + cardReaderPaymentControllerFactory = factory, + ) + + readerStatus.value = CardReaderStatus.Connected(mock()) + + // THEN + verify(mockCardReaderPaymentController).start() + } + private fun createViewModel( resourceProvider: ResourceProvider = this.resourceProvider, parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock(), totalsRepository: WooPosTotalsRepository = mock(), priceFormat: WooPosFormatPrice = mock(), savedState: SavedStateHandle = SavedStateHandle(), + cardReaderPaymentControllerFactory: CardReaderPaymentControllerFactory = paymentControllerFactory, ) = WooPosTotalsViewModel( - resourceProvider, - parentToChildrenEventReceiver, - childrenToParentEventSender, - cardReaderFacade, - totalsRepository, - priceFormat, - analyticsTracker, - networkStatus, - cardReaderPaymentControllerFactory = paymentControllerFactory, - savedState, + resourceProvider = resourceProvider, + parentToChildrenEventReceiver = parentToChildrenEventReceiver, + childrenToParentEventSender = childrenToParentEventSender, + cardReaderFacade = cardReaderFacade, + totalsRepository = totalsRepository, + priceFormat = priceFormat, + analyticsTracker = analyticsTracker, + networkStatus = networkStatus, + cardReaderPaymentControllerFactory = cardReaderPaymentControllerFactory, + savedState = savedState, ) } From b29d02bbef333f491578ba945373e4282a37237a Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 12:35:38 +0100 Subject: [PATCH 050/132] Add unit tests: 1. given order draft created, when reader connects, then start payment automatically 2. given order draft created, when reader disconnects, then should abort payment action --- .../home/totals/WooPosTotalsViewModel.kt | 16 +++-- .../home/totals/WooPosTotalsViewModelTest.kt | 69 +++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 77c24461ffe..0c0bedbbd9e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -1,6 +1,8 @@ package com.woocommerce.android.ui.woopos.home.totals import android.os.Parcelable +import androidx.annotation.VisibleForTesting +import androidx.annotation.VisibleForTesting.Companion.PRIVATE import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -81,7 +83,8 @@ class WooPosTotalsViewModel @Inject constructor( savedState[KEY_TTP_PAYMENT_IN_PROGRESS] = value } - private var paymentScope: CoroutineScope? = null + @VisibleForTesting(otherwise = PRIVATE) + internal var paymentScope: CoroutineScope? = null private var cardReaderPaymentController: CardReaderPaymentController? = null private fun createCardReaderPaymentController(orderId: Long) { @@ -109,6 +112,7 @@ class WooPosTotalsViewModel @Inject constructor( val state = uiState.value if (state !is WooPosTotalsViewState.Totals) return@collect uiState.value = state.copy(error = buildTotalsReaderNotConnectedError()) + cancelPaymentAction() } is Connected -> { val state = uiState.value @@ -123,6 +127,12 @@ class WooPosTotalsViewModel @Inject constructor( } } + private fun cancelPaymentAction() { + cardReaderPaymentController?.onCleared() + cardReaderPaymentController?.onBackPressed() + paymentScope?.cancel() + } + fun onUIEvent(event: WooPosTotalsUIEvent) { when (event) { is WooPosTotalsUIEvent.OnNewTransactionClicked -> { @@ -169,9 +179,7 @@ class WooPosTotalsViewModel @Inject constructor( } is ParentToChildrenEvent.BackFromCheckoutToCartClicked -> { - cardReaderPaymentController?.onBackPressed() - cardReaderPaymentController?.onCleared() - paymentScope?.cancel() + cancelPaymentAction() uiState.value = InitialState } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index e2afe965dd7..b69fea981d1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -43,6 +43,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.isActive import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat @@ -896,6 +897,74 @@ class WooPosTotalsViewModelTest { verify(mockCardReaderPaymentController).start() } + @Test + fun `given order draft created, when reader disconnects, then should abort payment action`() = runTest { + // GIVEN + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) + .thenReturn("Reader not connected") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle)) + .thenReturn("To process this payment, please connect your reader.") + whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label)) + .thenReturn("Connect to reader") + + val productIds = listOf(1L, 2L, 3L) + val orderId = 23L + val order = Order.getEmptyOrder( + dateCreated = Date(), + dateModified = Date() + ).copy( + id = orderId, + totalTax = BigDecimal("2.00"), + items = listOf( + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ), + Order.Item.EMPTY.copy( + subtotal = BigDecimal("1.00"), + ) + ), + productsTotal = BigDecimal("3.00"), + total = BigDecimal("5.00"), + ) + val totalsRepository: WooPosTotalsRepository = mock { + onBlocking { + createOrderWithProducts(productIds = productIds) + }.thenReturn(Result.success(order)) + } + val priceFormat: WooPosFormatPrice = mock { + onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("2.00$") + onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("3.00$") + onBlocking { invoke(BigDecimal("5.00")) }.thenReturn("5.00$") + } + val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) + val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { + on { events }.thenReturn(parentToChildrenEventFlow) + } + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + + // WHEN + val vm = createViewModel( + totalsRepository = totalsRepository, + priceFormat = priceFormat, + parentToChildrenEventReceiver = parentToChildrenEventReceiver, + cardReaderPaymentControllerFactory = factory, + ) + readerStatus.value = CardReaderStatus.NotConnected() + + // THEN + verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).onBackPressed() + assertThat(vm.paymentScope!!.isActive).isFalse + } + private fun createViewModel( resourceProvider: ResourceProvider = this.resourceProvider, parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock(), From d0dfeb5bd434db8d486ba201e9704f88ff8f2be7 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 13:41:05 +0100 Subject: [PATCH 051/132] Clean up code --- .../home/totals/WooPosTotalsViewModel.kt | 17 +-- .../home/totals/WooPosTotalsViewModelTest.kt | 132 +++--------------- 2 files changed, 32 insertions(+), 117 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 0c0bedbbd9e..c2daa167249 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -257,14 +257,15 @@ class WooPosTotalsViewModel @Inject constructor( ) } - private fun buildTotalsReaderNotConnectedError(): WooPosTotalsViewState.Totals.Error = WooPosTotalsViewState.Totals.Error( - title = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title), - subtitle = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle), - actionButonLabel = resourceProvider.getString( - R.string.woopos_success_totals_error_reader_not_connected_cta_button_label - ), - onAction = { cardReaderFacade.connectToReader() } - ) + private fun buildTotalsReaderNotConnectedError(): WooPosTotalsViewState.Totals.Error = + WooPosTotalsViewState.Totals.Error( + title = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title), + subtitle = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle), + actionButonLabel = resourceProvider.getString( + R.string.woopos_success_totals_error_reader_not_connected_cta_button_label + ), + onAction = { cardReaderFacade.connectToReader() } + ) @Parcelize private data class TotalsDataState( diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index b69fea981d1..94362494929 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -776,54 +776,10 @@ class WooPosTotalsViewModelTest { val readerStatus: StateFlow = MutableStateFlow(CardReaderStatus.NotConnected()) whenever(cardReaderManager.readerStatus).thenReturn(readerStatus) - whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) - .thenReturn("Reader not connected") - whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle)) - .thenReturn("To process this payment, please connect your reader.") - whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label)) - .thenReturn("Connect to reader") - - val productIds = listOf(1L, 2L, 3L) - val order = Order.getEmptyOrder( - dateCreated = Date(), - dateModified = Date() - ).copy( - totalTax = BigDecimal("2.00"), - items = listOf( - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ), - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ), - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ) - ), - productsTotal = BigDecimal("3.00"), - total = BigDecimal("5.00"), - ) - val totalsRepository: WooPosTotalsRepository = mock { - onBlocking { - createOrderWithProducts(productIds = productIds) - }.thenReturn(Result.success(order)) - } - val priceFormat: WooPosFormatPrice = mock { - onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("2.00$") - onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("3.00$") - onBlocking { invoke(BigDecimal("5.00")) }.thenReturn("5.00$") - } - val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) - val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { - on { events }.thenReturn(parentToChildrenEventFlow) - } // WHEN - val viewModel = createViewModel( - totalsRepository = totalsRepository, - priceFormat = priceFormat, - parentToChildrenEventReceiver = parentToChildrenEventReceiver, - ) + val viewModel = createViewModelAndSetupForSuccessfulOrderCreation() + (viewModel.state.value as WooPosTotalsViewState.Totals).error!!.onAction() // THEN @@ -836,60 +792,11 @@ class WooPosTotalsViewModelTest { whenever(networkStatus.isConnected()).thenReturn(true) val readerStatus = MutableStateFlow(CardReaderStatus.NotConnected()) whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) - whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) - .thenReturn("Reader not connected") - whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle)) - .thenReturn("To process this payment, please connect your reader.") - whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label)) - .thenReturn("Connect to reader") - val productIds = listOf(1L, 2L, 3L) - val orderId = 23L - val order = Order.getEmptyOrder( - dateCreated = Date(), - dateModified = Date() - ).copy( - id = orderId, - totalTax = BigDecimal("2.00"), - items = listOf( - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ), - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ), - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ) - ), - productsTotal = BigDecimal("3.00"), - total = BigDecimal("5.00"), - ) - val totalsRepository: WooPosTotalsRepository = mock { - onBlocking { - createOrderWithProducts(productIds = productIds) - }.thenReturn(Result.success(order)) - } - val priceFormat: WooPosFormatPrice = mock { - onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("2.00$") - onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("3.00$") - onBlocking { invoke(BigDecimal("5.00")) }.thenReturn("5.00$") - } - val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) - val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { - on { events }.thenReturn(parentToChildrenEventFlow) - } val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) - - // WHEN - val vm = createViewModel( - totalsRepository = totalsRepository, - priceFormat = priceFormat, - parentToChildrenEventReceiver = parentToChildrenEventReceiver, - cardReaderPaymentControllerFactory = factory, - ) + createViewModelAndSetupForSuccessfulOrderCreation(paymentControllerFactory = factory) readerStatus.value = CardReaderStatus.Connected(mock()) @@ -903,6 +810,24 @@ class WooPosTotalsViewModelTest { whenever(networkStatus.isConnected()).thenReturn(true) val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(paymentControllerFactory = factory) + + // WHEN + readerStatus.value = CardReaderStatus.NotConnected() + + // THEN + verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).onBackPressed() + assertThat(vm.paymentScope!!.isActive).isFalse + } + + private fun createViewModelAndSetupForSuccessfulOrderCreation( + paymentControllerFactory: CardReaderPaymentControllerFactory = this@WooPosTotalsViewModelTest.paymentControllerFactory + ): WooPosTotalsViewModel { whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) .thenReturn("Reader not connected") whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle)) @@ -946,23 +871,12 @@ class WooPosTotalsViewModelTest { val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { on { events }.thenReturn(parentToChildrenEventFlow) } - val mockCardReaderPaymentController: CardReaderPaymentController = mock() - val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) - - // WHEN - val vm = createViewModel( + return createViewModel( totalsRepository = totalsRepository, priceFormat = priceFormat, parentToChildrenEventReceiver = parentToChildrenEventReceiver, - cardReaderPaymentControllerFactory = factory, + cardReaderPaymentControllerFactory = paymentControllerFactory, ) - readerStatus.value = CardReaderStatus.NotConnected() - - // THEN - verify(mockCardReaderPaymentController).onCleared() - verify(mockCardReaderPaymentController).onBackPressed() - assertThat(vm.paymentScope!!.isActive).isFalse } private fun createViewModel( From 6db93e275daa88b21933fad0cc42bc53e89f476b Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 15:39:02 +0100 Subject: [PATCH 052/132] Clean up code --- .../home/totals/WooPosTotalsViewModelTest.kt | 53 ++++--------------- 1 file changed, 9 insertions(+), 44 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 94362494929..89f2e96a15c 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -50,6 +50,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Rule import org.mockito.kotlin.any +import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -547,50 +548,14 @@ class WooPosTotalsViewModelTest { fun `given there is no internet, then trigger proper event`() = runTest { // GIVEN whenever(networkStatus.isConnected()).thenReturn(false) - val productIds = listOf(1L, 2L, 3L) - val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) - val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { - on { events }.thenReturn(parentToChildrenEventFlow) - } - val order = Order.getEmptyOrder( - dateCreated = Date(), - dateModified = Date() - ).copy( - totalTax = BigDecimal("2.00"), - items = listOf( - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ), - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ), - Order.Item.EMPTY.copy( - subtotal = BigDecimal("1.00"), - ) - ), - productsTotal = BigDecimal("3.00"), - total = BigDecimal("5.00"), - ) - val totalsRepository: WooPosTotalsRepository = mock { - onBlocking { createOrderWithProducts(productIds = productIds) }.thenReturn( - Result.success(order) - ) - } - val priceFormat: WooPosFormatPrice = mock { - onBlocking { invoke(BigDecimal("2.00")) }.thenReturn("2.00$") - onBlocking { invoke(BigDecimal("3.00")) }.thenReturn("3.00$") - onBlocking { invoke(BigDecimal("5.00")) }.thenReturn("5.00$") - } + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) // WHEN - createViewModel( - parentToChildrenEventReceiver = parentToChildrenEventReceiver, - totalsRepository = totalsRepository, - priceFormat = priceFormat, - ) + createViewModelAndSetupForSuccessfulOrderCreation() // THEN - verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.NoInternet) + verify(childrenToParentEventSender, atLeastOnce()).sendToParent(ChildToParentEvent.NoInternet) } @org.junit.Test @@ -796,7 +761,7 @@ class WooPosTotalsViewModelTest { val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) - createViewModelAndSetupForSuccessfulOrderCreation(paymentControllerFactory = factory) + createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) readerStatus.value = CardReaderStatus.Connected(mock()) @@ -814,7 +779,7 @@ class WooPosTotalsViewModelTest { val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) - val vm = createViewModelAndSetupForSuccessfulOrderCreation(paymentControllerFactory = factory) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) // WHEN readerStatus.value = CardReaderStatus.NotConnected() @@ -826,7 +791,7 @@ class WooPosTotalsViewModelTest { } private fun createViewModelAndSetupForSuccessfulOrderCreation( - paymentControllerFactory: CardReaderPaymentControllerFactory = this@WooPosTotalsViewModelTest.paymentControllerFactory + controllerFactory: CardReaderPaymentControllerFactory = paymentControllerFactory ): WooPosTotalsViewModel { whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) .thenReturn("Reader not connected") @@ -875,7 +840,7 @@ class WooPosTotalsViewModelTest { totalsRepository = totalsRepository, priceFormat = priceFormat, parentToChildrenEventReceiver = parentToChildrenEventReceiver, - cardReaderPaymentControllerFactory = paymentControllerFactory, + cardReaderPaymentControllerFactory = controllerFactory, ) } From a4f61a416f4a3a49c74281dd77c189bb34b1dffd Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 16:57:06 +0100 Subject: [PATCH 053/132] Update background colors --- .../android/ui/woopos/common/composeui/WooPosTheme.kt | 11 ++++++++--- .../ui/woopos/home/totals/WooPosTotalsScreen.kt | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index ad82ef482b3..226de53dd03 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -18,7 +18,8 @@ data class CustomColors( val border: Color, val success: Color, val error: Color, - val errorBackground: Color, + val totalsBackground: Color, + val totalsErrorBackground: Color, val paymentSuccessBackground: Color, val paymentSuccessText: Color, val paymentSuccessIcon: Color, @@ -49,11 +50,13 @@ private object WooPosColors { val darkCustomColorsError = Color(0xFFBE4400) val darkCustomloadingSkeleton = Color(0xFF616161) val darkCustomColorsHomeBackground = Color(0xFF1E1E1E) + val darkQuaternaryBackground = Color(0xFF111111) // LightCustomColors val lightCustomColorsError = Color(0xFFF16618) val lightCustomColorsLoadingSkeleton = Color(0xFFE1E1E1) val lightCustomColorsBorder = Color(0xFFC6C6C8) + val lightQuaternaryBackground = Color(0x14747480) // Woo colors from here: W5OBIbzWilNI8qely8Y4OHQd-fi-144_2 val WooPurple0 = Color(0xFFF2EDFF) @@ -187,7 +190,8 @@ private val DarkCustomColors = CustomColors( border = WooPosColors.oldGrayMedium, success = WooPosColors.greenNotFromPalette, error = WooPosColors.darkCustomColorsError, - errorBackground = WooPosColors.Black90Alpha020, + totalsErrorBackground = WooPosColors.darkQuaternaryBackground, + totalsBackground = Color(0xFF1C1C1E), paymentSuccessBackground = WooPosColors.darkCustomColorsHomeBackground, paymentSuccessText = WooPosColors.oldGrayLight, paymentSuccessIcon = WooPosColors.darkCustomColorsHomeBackground, @@ -199,7 +203,8 @@ private val LightCustomColors = CustomColors( border = WooPosColors.lightCustomColorsBorder, success = WooPosColors.greenNotFromPalette, error = WooPosColors.lightCustomColorsError, - errorBackground = WooPosColors.WhiteAlpha060, + totalsErrorBackground = WooPosColors.lightQuaternaryBackground, + totalsBackground = Color(0xFFF6F7F7), paymentSuccessBackground = WooPosColors.White, paymentSuccessText = WooPosColors.Purple90, paymentSuccessIcon = Color.White, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 74fe24027c2..965ebb6130a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -110,6 +110,7 @@ private fun TotalsLoaded( ) { Column( modifier = Modifier + .background(WooPosTheme.colors.totalsBackground) .fillMaxSize() .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, @@ -121,7 +122,7 @@ private fun TotalsLoaded( modifier = Modifier .fillMaxWidth() .weight(1.1f) - .background(WooPosTheme.colors.errorBackground) + .background(WooPosTheme.colors.totalsErrorBackground) ) { TotalsError(modifier = Modifier, error = error) } From a2ee75ac4bf7932e1a4b46f6e8290ff9d8a90725 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 18:03:15 +0100 Subject: [PATCH 054/132] Show full screen "Payment processing" state --- .../ui/woopos/common/composeui/WooPosTheme.kt | 7 ++- .../WooPosHomeChildToParentCommunication.kt | 1 + .../ui/woopos/home/WooPosHomeViewModel.kt | 6 ++- .../woopos/home/totals/WooPosTotalsScreen.kt | 6 +++ .../home/totals/WooPosTotalsViewModel.kt | 44 ++++++++++++++++--- .../home/totals/WooPosTotalsViewState.kt | 12 +++++ .../WooPosTotalsPaymentProcessingScreen.kt | 33 ++++++++++++++ WooCommerce/src/main/res/values/strings.xml | 4 +- 8 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index 226de53dd03..31e2718ae0d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -21,6 +21,7 @@ data class CustomColors( val totalsBackground: Color, val totalsErrorBackground: Color, val paymentSuccessBackground: Color, + val paymentProcessingBackground: Color, val paymentSuccessText: Color, val paymentSuccessIcon: Color, val dialogSubtitleHighlightBackground: Color = Color(0x14747480), @@ -195,7 +196,8 @@ private val DarkCustomColors = CustomColors( paymentSuccessBackground = WooPosColors.darkCustomColorsHomeBackground, paymentSuccessText = WooPosColors.oldGrayLight, paymentSuccessIcon = WooPosColors.darkCustomColorsHomeBackground, - homeBackground = WooPosColors.darkCustomColorsHomeBackground + homeBackground = WooPosColors.darkCustomColorsHomeBackground, + paymentProcessingBackground = Color(0xFF533582), ) private val LightCustomColors = CustomColors( @@ -208,7 +210,8 @@ private val LightCustomColors = CustomColors( paymentSuccessBackground = WooPosColors.White, paymentSuccessText = WooPosColors.Purple90, paymentSuccessIcon = Color.White, - homeBackground = WooPosColors.Gray0 + homeBackground = WooPosColors.Gray0, + paymentProcessingBackground = Color(0xFF533582), ) private val LocalCustomColors = staticCompositionLocalOf { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index c0b8ac6c640..bb34378866e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -23,6 +23,7 @@ sealed class ChildToParentEvent { data object BackFromCheckoutToCartClicked : ChildToParentEvent() data class ItemClickedInProductSelector(val itemData: WooPosItemsViewModel.ItemClickedData) : ChildToParentEvent() data object NewTransactionClicked : ChildToParentEvent() + data object PaymentProcessing : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index b7ebdffaac7..6739d63c1dc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -110,7 +110,11 @@ class WooPosHomeViewModel @Inject constructor( ) sendEventToChildren(ParentToChildrenEvent.OrderSuccessfullyPaid) } - + is ChildToParentEvent.PaymentProcessing -> { + _state.value = _state.value.copy( + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid + ) + } is ChildToParentEvent.OrderSuccessfullyPaid -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 965ebb6130a..b2dea7bbe4f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -45,6 +45,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosErrorScreen import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding +import com.woocommerce.android.ui.woopos.home.totals.payment.processing.WooPosPaymentProcessingScreen import com.woocommerce.android.ui.woopos.home.totals.payment.success.WooPosPaymentSuccessScreen @Composable @@ -87,6 +88,11 @@ private fun WooPosTotalsScreen( ) } } + StateChangeAnimated(visible = state is WooPosTotalsViewState.PaymentProcessing) { + if (state is WooPosTotalsViewState.PaymentProcessing) { + WooPosPaymentProcessingScreen(state) + } + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index c2daa167249..0ffb4190948 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -20,6 +20,7 @@ import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceiver +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.* import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker @@ -199,12 +200,45 @@ class WooPosTotalsViewModel @Inject constructor( paymentStateText = paymentState.javaClass.simpleName ) } - if (paymentState is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentSuccessful) { - uiState.value = - WooPosTotalsViewState.PaymentSuccess( - orderTotalText = paymentState.amountWithCurrencyLabel + + when (paymentState) { + is CardReaderPaymentOrRefundState.CardReaderPaymentState.LoadingData -> { + // TODO: show loading state within the totals pane + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.CollectingPayment -> { + // TODO: show "tap or swipe" state within the totals pane + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.ProcessingPayment, + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentCapturing, + CardReaderPaymentOrRefundState.CardReaderPaymentState.ReFetchingOrder -> { + uiState.value = PaymentProcessing( + title = resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title), + subtitle = resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle) ) - childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentSuccessful -> { + uiState.value = + PaymentSuccess( + orderTotalText = paymentState.amountWithCurrencyLabel + ) + childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { + // TODO: show full screen payment failed screen + } + is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { + throw IllegalStateException("Interac refund is not supported in POS") + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment->{ + throw IllegalStateException("Built-in reader is not supported in POS") + } + is CardReaderPaymentOrRefundState.CardReaderPaymentState.PrintingReceipt -> { + throw IllegalStateException("PrintingReceipt is not supported in POS") + } + CardReaderPaymentOrRefundState.CardReaderPaymentState.SharingReceipt -> { + throw IllegalStateException("SharingReceipt is not supported in POS") + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index ca5cb98029b..fde8e3fec32 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -23,6 +23,18 @@ sealed class WooPosTotalsViewState : Parcelable { ) : Parcelable } + data class PaymentProcessing( + val title: String, + val subtitle: String, + ) : WooPosTotalsViewState() + + data class PaymentFailed( + val title: String, + val subtitle: String, + val actionButtonLabel: String, + val onAction: () -> Unit, + ) : WooPosTotalsViewState() + data class PaymentSuccess(var orderTotalText: String) : WooPosTotalsViewState() data class Error(val message: String) : WooPosTotalsViewState() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt new file mode 100644 index 00000000000..96ca350ad41 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -0,0 +1,33 @@ +package com.woocommerce.android.ui.woopos.home.totals.payment.processing + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState + +@Composable +fun WooPosPaymentProcessingScreen( + state: WooPosTotalsViewState.PaymentProcessing, +) { + Box( + modifier = Modifier + .background(color = WooPosTheme.colors.paymentProcessingBackground) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally, + verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center, + ) { + Text(text = state.title) + Text(text = state.subtitle) + } + } +} \ No newline at end of file diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 3214c9088b5..3ce55befd22 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4301,6 +4301,9 @@ To process this payment, please connect your reader. Connect to reader + Processing payment + Please wait… + Dimmed background. Tap to close the menu. Card reader connected Card reader not connected. Double tap to connect @@ -4317,7 +4320,6 @@ "Failed to load more items. Please try again." Error loading variations - Customer Orders Registration From 6aecb6ff5bd38c544e5edebf178a196d609fed56 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:25:00 +0100 Subject: [PATCH 055/132] Clean up code --- .../home/totals/WooPosTotalsViewModel.kt | 29 +++++++++++-------- .../WooPosTotalsPaymentProcessingScreen.kt | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 0ffb4190948..2aff9a33a6d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -15,6 +15,7 @@ import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowP import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentControllerFactory import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent @@ -202,41 +203,45 @@ class WooPosTotalsViewModel @Inject constructor( } when (paymentState) { - is CardReaderPaymentOrRefundState.CardReaderPaymentState.LoadingData -> { + is CardReaderPaymentState.LoadingData -> { // TODO: show loading state within the totals pane } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.CollectingPayment -> { + is CardReaderPaymentState.CollectingPayment -> { // TODO: show "tap or swipe" state within the totals pane } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.ProcessingPayment, - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentCapturing, - CardReaderPaymentOrRefundState.CardReaderPaymentState.ReFetchingOrder -> { + is CardReaderPaymentState.ProcessingPayment, + is CardReaderPaymentState.PaymentCapturing, + CardReaderPaymentState.ReFetchingOrder -> { uiState.value = PaymentProcessing( - title = resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title), - subtitle = resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle) + title = resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ), + subtitle = resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_subtitle + ) ) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentSuccessful -> { + is CardReaderPaymentState.PaymentSuccessful -> { uiState.value = PaymentSuccess( orderTotalText = paymentState.amountWithCurrencyLabel ) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { + is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { // TODO: show full screen payment failed screen } is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { throw IllegalStateException("Interac refund is not supported in POS") } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment->{ + is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment -> { throw IllegalStateException("Built-in reader is not supported in POS") } - is CardReaderPaymentOrRefundState.CardReaderPaymentState.PrintingReceipt -> { + is CardReaderPaymentState.PrintingReceipt -> { throw IllegalStateException("PrintingReceipt is not supported in POS") } - CardReaderPaymentOrRefundState.CardReaderPaymentState.SharingReceipt -> { + CardReaderPaymentState.SharingReceipt -> { throw IllegalStateException("SharingReceipt is not supported in POS") } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index 96ca350ad41..dd6c322a57c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -30,4 +30,4 @@ fun WooPosPaymentProcessingScreen( Text(text = state.subtitle) } } -} \ No newline at end of file +} From 4192a2a07c36e45593a9faaef137de1247ac32b9 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:32:26 +0100 Subject: [PATCH 056/132] Add preview --- .../WooPosTotalsPaymentProcessingScreen.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index dd6c322a57c..fe223e5365d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState @@ -31,3 +32,14 @@ fun WooPosPaymentProcessingScreen( } } } + +@WooPosPreview +@Composable +fun WooPosPaymentProcessingScreenPreview() { + WooPosPaymentProcessingScreen( + state = WooPosTotalsViewState.PaymentProcessing( + title = "Processing payment", + subtitle = "Please wait...", + ) + ) +} \ No newline at end of file From 5c325b62bbb0ac279094d244df1088375e4801e1 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:34:54 +0100 Subject: [PATCH 057/132] Clean up code --- .../processing/WooPosTotalsPaymentProcessingScreen.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index fe223e5365d..dd54013616c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.woopos.home.totals.payment.processing import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -24,8 +25,8 @@ fun WooPosPaymentProcessingScreen( ) { Column( modifier = Modifier.fillMaxSize(), - horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally, - verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { Text(text = state.title) Text(text = state.subtitle) From e9b3f20c6f169484d2ce8096aefb39632074fc76 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:37:03 +0100 Subject: [PATCH 058/132] Improve preview --- .../WooPosTotalsPaymentProcessingScreen.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index dd54013616c..82c87842904 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -37,10 +37,12 @@ fun WooPosPaymentProcessingScreen( @WooPosPreview @Composable fun WooPosPaymentProcessingScreenPreview() { - WooPosPaymentProcessingScreen( - state = WooPosTotalsViewState.PaymentProcessing( - title = "Processing payment", - subtitle = "Please wait...", + WooPosTheme { + WooPosPaymentProcessingScreen( + state = WooPosTotalsViewState.PaymentProcessing( + title = "Processing payment", + subtitle = "Please wait...", + ) ) - ) + } } \ No newline at end of file From aa1a3219a75e3dd545ecbb8b5fa492ef453509c8 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:37:27 +0100 Subject: [PATCH 059/132] Create basic payment failed screen --- .../failed/WooPosTotalsPaymentFailedScreen.kt | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt new file mode 100644 index 00000000000..266ad9f5553 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -0,0 +1,58 @@ +package com.woocommerce.android.ui.woopos.home.totals.payment.failed + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview +import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsUIEvent +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState + +@Composable +fun WooPosPaymentFailedScreen( + state: WooPosTotalsViewState.PaymentFailed, + onUIEvent: (WooPosTotalsUIEvent) -> Unit +) { + Box( + modifier = Modifier + .background(color = WooPosTheme.colors.paymentProcessingBackground) + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text(text = state.title) + Text(text = state.subtitle) + WooPosButton(text = "Try another payment method") { + onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) + } + WooPosButton(text = "Exit order") { + onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) + } + } + } +} + +@WooPosPreview +@Composable +fun WooPosPaymentFailedScreenPreview() { + WooPosTheme { + WooPosPaymentFailedScreen( + state = WooPosTotalsViewState.PaymentFailed( + title = "Payment failed", + subtitle = "Please try again", + ), + onUIEvent = {} + ) + } +} \ No newline at end of file From cde0db78821b5d0e8eaa4c7e7982c4317b41f73c Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 3 Dec 2024 19:51:00 +0100 Subject: [PATCH 060/132] Show payment failed screen --- .../woopos/home/totals/WooPosTotalsScreen.kt | 11 ++++++ .../woopos/home/totals/WooPosTotalsUIEvent.kt | 2 ++ .../home/totals/WooPosTotalsViewModel.kt | 34 +++++++++++++------ .../home/totals/WooPosTotalsViewState.kt | 2 -- WooCommerce/src/main/res/values/strings.xml | 2 ++ 5 files changed, 39 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index b2dea7bbe4f..44e4d3b2ad0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -45,6 +45,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosErrorScreen import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding +import com.woocommerce.android.ui.woopos.home.totals.payment.failed.WooPosPaymentFailedScreen import com.woocommerce.android.ui.woopos.home.totals.payment.processing.WooPosPaymentProcessingScreen import com.woocommerce.android.ui.woopos.home.totals.payment.success.WooPosPaymentSuccessScreen @@ -88,11 +89,21 @@ private fun WooPosTotalsScreen( ) } } + StateChangeAnimated(visible = state is WooPosTotalsViewState.PaymentProcessing) { if (state is WooPosTotalsViewState.PaymentProcessing) { WooPosPaymentProcessingScreen(state) } } + + StateChangeAnimated(visible = state is WooPosTotalsViewState.PaymentFailed) { + if (state is WooPosTotalsViewState.PaymentFailed) { + WooPosPaymentFailedScreen( + state = state, + onUIEvent = onUIEvent, + ) + } + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt index e0841c61cc9..5540b3b69ac 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt @@ -2,5 +2,7 @@ package com.woocommerce.android.ui.woopos.home.totals sealed class WooPosTotalsUIEvent { data object OnNewTransactionClicked : WooPosTotalsUIEvent() + data object RetryFailedTransactionClicked : WooPosTotalsUIEvent() + data object ExitOrderAfterFailedTransactionClicked : WooPosTotalsUIEvent() data object RetryOrderCreationClicked : WooPosTotalsUIEvent() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 2aff9a33a6d..3ea64566ff2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -147,6 +147,9 @@ class WooPosTotalsViewModel @Inject constructor( is WooPosTotalsUIEvent.RetryOrderCreationClicked -> { createOrderDraft(dataState.value.productIds) } + + WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> TODO() + WooPosTotalsUIEvent.RetryFailedTransactionClicked -> TODO() } } @@ -212,15 +215,8 @@ class WooPosTotalsViewModel @Inject constructor( is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, CardReaderPaymentState.ReFetchingOrder -> { - uiState.value = PaymentProcessing( - title = resourceProvider.getString( - R.string.woopos_success_totals_payment_processing_title - ), - subtitle = resourceProvider.getString( - R.string.woopos_success_totals_payment_processing_subtitle - ) - ) - childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) + uiState.value = buildPaymentProcessingState() + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentProcessing) } is CardReaderPaymentState.PaymentSuccessful -> { uiState.value = @@ -230,7 +226,7 @@ class WooPosTotalsViewModel @Inject constructor( childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { - // TODO: show full screen payment failed screen + uiState.value = buildPaymentFailedState() } is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { throw IllegalStateException("Interac refund is not supported in POS") @@ -249,6 +245,24 @@ class WooPosTotalsViewModel @Inject constructor( } } + private fun buildPaymentFailedState(): PaymentFailed = PaymentFailed( + title = resourceProvider.getString( + R.string.woopos_success_totals_payment_failed_title + ), + subtitle = resourceProvider.getString( + R.string.woopos_success_totals_payment_failed_subtitle + ) + ) + + private fun buildPaymentProcessingState(): PaymentProcessing = PaymentProcessing( + title = resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ), + subtitle = resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_subtitle + ) + ) + private fun createOrderDraft(productIds: List) { viewModelScope.launch { uiState.value = WooPosTotalsViewState.Loading diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index fde8e3fec32..9ee0747f8fa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -31,8 +31,6 @@ sealed class WooPosTotalsViewState : Parcelable { data class PaymentFailed( val title: String, val subtitle: String, - val actionButtonLabel: String, - val onAction: () -> Unit, ) : WooPosTotalsViewState() data class PaymentSuccess(var orderTotalText: String) : WooPosTotalsViewState() diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 3ce55befd22..4b725ac4ddd 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4303,6 +4303,8 @@ Processing payment Please wait… + Payment failed + Unfortunately, this payment has been declined. Dimmed background. Tap to close the menu. Card reader connected From 01b8bab1c1808cfa008cfbe00cbe69db8b6f0f41 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 09:06:50 +0100 Subject: [PATCH 061/132] Update tests --- .../ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 89f2e96a15c..55bcce17634 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -283,7 +283,12 @@ class WooPosTotalsViewModelTest { val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { on { events }.thenReturn(mock()) } + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) + .thenReturn("Unfortunately, this payment has been declined.") val savedState = createMockSavedStateHandle() + val viewModel = createViewModel( savedState = savedState, parentToChildrenEventReceiver = parentToChildrenEventReceiver, From 1c2ee968c7344c1c5721ee546402e7c6bb284f12 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 09:11:42 +0100 Subject: [PATCH 062/132] Satisfy detekt's complaints --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 4 +++- .../totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt | 2 +- .../payment/processing/WooPosTotalsPaymentProcessingScreen.kt | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 3ea64566ff2..bff49a576bb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -21,7 +21,9 @@ import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceiver -import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.* +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.PaymentFailed +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.PaymentProcessing +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.PaymentSuccess import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 266ad9f5553..2d2cb5999fc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -55,4 +55,4 @@ fun WooPosPaymentFailedScreenPreview() { onUIEvent = {} ) } -} \ No newline at end of file +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index 82c87842904..72d7af39682 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -45,4 +45,4 @@ fun WooPosPaymentProcessingScreenPreview() { ) ) } -} \ No newline at end of file +} From 502cb4e9977b5ffb654fcab0265ea3046981469a Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 09:53:24 +0100 Subject: [PATCH 063/132] Tune up payment failed composable --- .../composeui/component/WooPosButtons.kt | 33 +++++--- .../failed/WooPosTotalsPaymentFailedScreen.kt | 76 ++++++++++++++----- .../main/res/drawable/woo_pos_ic_error_x.xml | 12 +++ WooCommerce/src/main/res/values/strings.xml | 2 + 4 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 WooCommerce/src/main/res/drawable/woo_pos_ic_error_x.xml diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt index 2544188965f..61db50bac5b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosButtons.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.common.composeui.component import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -91,6 +92,26 @@ fun WooPosOutlinedButton( text: String, shape: RoundedCornerShape = RoundedCornerShape(4.dp), onClick: () -> Unit, +) = WooPosOutlinedButton( + modifier = modifier, + shape = shape, + content = { + Text( + text = text, + color = MaterialTheme.colors.primary, + style = MaterialTheme.typography.body2, + fontWeight = FontWeight.SemiBold, + ) + }, + onClick = onClick, +) + +@Composable +fun WooPosOutlinedButton( + modifier: Modifier = Modifier, + shape: RoundedCornerShape = RoundedCornerShape(4.dp), + content: @Composable RowScope.() -> Unit, + onClick: () -> Unit, ) { Button( modifier = modifier, @@ -107,15 +128,9 @@ fun WooPosOutlinedButton( disabledElevation = 0.dp, hoveredElevation = 0.dp, focusedElevation = 0.dp - ) - ) { - Text( - text = text, - color = MaterialTheme.colors.primary, - style = MaterialTheme.typography.body2, - fontWeight = FontWeight.SemiBold, - ) - } + ), + content = content + ) } @Composable diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 2d2cb5999fc..4cddfe27d6b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -4,14 +4,29 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosOutlinedButton +import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsUIEvent import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState @@ -20,26 +35,51 @@ fun WooPosPaymentFailedScreen( state: WooPosTotalsViewState.PaymentFailed, onUIEvent: (WooPosTotalsUIEvent) -> Unit ) { - Box( + Column( modifier = Modifier - .background(color = WooPosTheme.colors.paymentProcessingBackground) - .fillMaxSize(), - contentAlignment = Alignment.Center + .background(color = WooPosTheme.colors.homeBackground) + .fillMaxSize() + .padding(vertical = 96.dp.toAdaptivePadding(), horizontal = 295.dp.toAdaptivePadding()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = state.title) - Text(text = state.subtitle) - WooPosButton(text = "Try another payment method") { - onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) - } - WooPosButton(text = "Exit order") { - onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) + Spacer(modifier = Modifier.height(56.dp.toAdaptivePadding())) + Icon( + modifier = Modifier.size(64.dp), + painter = painterResource(id = R.drawable.woo_pos_ic_error_x), + contentDescription = stringResource(id = R.string.woopos_error_icon_content_description), + tint = Color.Unspecified, + ) + Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) + Text( + text = state.title, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) + Text( + text = state.subtitle, + style = MaterialTheme.typography.h6 + ) + Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) + WooPosButton( + text = stringResource(R.string.woo_pos_payment_failed_try_another_payment_method), + modifier = Modifier.height(80.dp) + ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } + Spacer(modifier = Modifier.height(24.dp.toAdaptivePadding())) + WooPosOutlinedButton( + modifier = Modifier + .fillMaxWidth() + .height(80.dp), + content = { + Text( + color = MaterialTheme.colors.primary, + style = MaterialTheme.typography.h5, + fontWeight = FontWeight.Bold, + text = stringResource(R.string.woo_pos_payment_failed_exit_order) + ) } - } + ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } } } @@ -50,7 +90,7 @@ fun WooPosPaymentFailedScreenPreview() { WooPosPaymentFailedScreen( state = WooPosTotalsViewState.PaymentFailed( title = "Payment failed", - subtitle = "Please try again", + subtitle = "Unfortunately, this payment has been declined.", ), onUIEvent = {} ) diff --git a/WooCommerce/src/main/res/drawable/woo_pos_ic_error_x.xml b/WooCommerce/src/main/res/drawable/woo_pos_ic_error_x.xml new file mode 100644 index 00000000000..a0a0665e11e --- /dev/null +++ b/WooCommerce/src/main/res/drawable/woo_pos_ic_error_x.xml @@ -0,0 +1,12 @@ + + + + diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 4b725ac4ddd..b48e3a59659 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4390,4 +4390,6 @@ Box Envelope Hmm, we can\'t find a WordPress.com account connected to this email address. + Try another payment method + Exit order From 60c7434553585b5e493934d67ade28d0388292fd Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 12:49:45 +0100 Subject: [PATCH 064/132] Tune up payment failed composable --- .../failed/WooPosTotalsPaymentFailedScreen.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 4cddfe27d6b..6855b82e819 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -39,13 +40,12 @@ fun WooPosPaymentFailedScreen( modifier = Modifier .background(color = WooPosTheme.colors.homeBackground) .fillMaxSize() - .padding(vertical = 96.dp.toAdaptivePadding(), horizontal = 295.dp.toAdaptivePadding()), + .padding(vertical = 96.dp.toAdaptivePadding()), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, ) { - Spacer(modifier = Modifier.height(56.dp.toAdaptivePadding())) + Spacer(modifier = Modifier.height(96.dp.toAdaptivePadding())) Icon( - modifier = Modifier.size(64.dp), + modifier = Modifier.size(84.dp), painter = painterResource(id = R.drawable.woo_pos_ic_error_x), contentDescription = stringResource(id = R.string.woopos_error_icon_content_description), tint = Color.Unspecified, @@ -64,13 +64,15 @@ fun WooPosPaymentFailedScreen( Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) WooPosButton( text = stringResource(R.string.woo_pos_payment_failed_try_another_payment_method), - modifier = Modifier.height(80.dp) + modifier = Modifier + .height(80.dp) + .width(604.dp) ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } Spacer(modifier = Modifier.height(24.dp.toAdaptivePadding())) WooPosOutlinedButton( modifier = Modifier - .fillMaxWidth() - .height(80.dp), + .height(80.dp) + .width(604.dp), content = { Text( color = MaterialTheme.colors.primary, @@ -80,6 +82,7 @@ fun WooPosPaymentFailedScreen( ) } ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } + Spacer(modifier = Modifier.height(80.dp.toAdaptivePadding())) } } From ebd6543b8dd48cf1de69d6235458338c6842f0d7 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 12:50:16 +0100 Subject: [PATCH 065/132] Tune up payment failed composable --- .../totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 6855b82e819..e1d77eb2599 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -1,12 +1,9 @@ package com.woocommerce.android.ui.woopos.home.totals.payment.failed import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size From e73501b75c88cc43d201c6afc18fdad5e54812dd Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 12:53:44 +0100 Subject: [PATCH 066/132] Rename states --- .../android/ui/woopos/home/WooPosHomeScreen.kt | 12 ++++++------ .../android/ui/woopos/home/WooPosHomeState.kt | 4 ++-- .../android/ui/woopos/home/WooPosHomeViewModel.kt | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt index 11972135e4c..c9a18901319 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt @@ -89,18 +89,18 @@ private fun WooPosHomeScreen( WooPosHomeState.ScreenPositionState.Cart.Hidden -> screenWidthDp is WooPosHomeState.ScreenPositionState.Cart.Visible, - WooPosHomeState.ScreenPositionState.Checkout.NotPaid -> productsWidthDp + WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals -> productsWidthDp - WooPosHomeState.ScreenPositionState.Checkout.Paid -> productsWidthDp - cartWidthDp + WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals -> productsWidthDp - cartWidthDp }, label = "productsWidthAnimatedDp" ) val totalsWidthAnimatedDp by animateDpAsState( when (state.screenPositionState) { - is WooPosHomeState.ScreenPositionState.Checkout.Paid -> screenWidthDp + is WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals -> screenWidthDp is WooPosHomeState.ScreenPositionState.Cart, - WooPosHomeState.ScreenPositionState.Checkout.NotPaid -> totalsWidthDp + WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals -> totalsWidthDp }, label = "totalsWidthAnimatedDp" ) @@ -261,7 +261,7 @@ fun WooPosHomeCheckoutScreenPreview() { WooPosTheme { WooPosHomeScreen( state = WooPosHomeState( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.NotPaid, + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals, productsInfoDialog = ProductsInfoDialog(isVisible = false), exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = false), ), @@ -277,7 +277,7 @@ fun WooPosHomeCheckoutPaidScreenPreview() { WooPosTheme { WooPosHomeScreen( state = WooPosHomeState( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid, + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals, productsInfoDialog = ProductsInfoDialog(isVisible = false), exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = false), ), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeState.kt index 177e58b10e4..10f9f309c78 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeState.kt @@ -26,10 +26,10 @@ data class WooPosHomeState( @Parcelize sealed class Checkout : ScreenPositionState() { @Parcelize - data object NotPaid : Checkout() + data object CartWithTotals : Checkout() @Parcelize - data object Paid : Checkout() + data object FullScreenTotals : Checkout() } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 6739d63c1dc..16d120a1938 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -45,14 +45,14 @@ class WooPosHomeViewModel @Inject constructor( return when (event) { WooPosHomeUIEvent.SystemBackClicked -> { when (_state.value.screenPositionState) { - WooPosHomeState.ScreenPositionState.Checkout.NotPaid -> { + WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible ) sendEventToChildren(ParentToChildrenEvent.BackFromCheckoutToCartClicked) } - WooPosHomeState.ScreenPositionState.Checkout.Paid -> { + WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible ) @@ -87,7 +87,7 @@ class WooPosHomeViewModel @Inject constructor( when (event) { is ChildToParentEvent.CheckoutClicked -> { _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.NotPaid + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals ) sendEventToChildren(ParentToChildrenEvent.CheckoutClicked(event.productIds)) } @@ -112,12 +112,12 @@ class WooPosHomeViewModel @Inject constructor( } is ChildToParentEvent.PaymentProcessing -> { _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) } is ChildToParentEvent.OrderSuccessfullyPaid -> { _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.Paid + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) } @@ -160,8 +160,8 @@ class WooPosHomeViewModel @Inject constructor( WooPosHomeState.ScreenPositionState.Cart.Visible WooPosHomeState.ScreenPositionState.Cart.Visible, - WooPosHomeState.ScreenPositionState.Checkout.NotPaid, - WooPosHomeState.ScreenPositionState.Checkout.Paid -> screenPosition + WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals, + WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals -> screenPosition } } } From 56651e2a684a356224a0d0bf3f3a823c5ae9bfb1 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 12:57:21 +0100 Subject: [PATCH 067/132] Ensure failed payment state is always full screen --- .../woopos/home/WooPosHomeChildToParentCommunication.kt | 1 + .../android/ui/woopos/home/WooPosHomeViewModel.kt | 9 +++------ .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index bb34378866e..01890044915 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -24,6 +24,7 @@ sealed class ChildToParentEvent { data class ItemClickedInProductSelector(val itemData: WooPosItemsViewModel.ItemClickedData) : ChildToParentEvent() data object NewTransactionClicked : ChildToParentEvent() data object PaymentProcessing : ChildToParentEvent() + data object PaymentFailed : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 16d120a1938..4e940c4e9ea 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -110,12 +110,9 @@ class WooPosHomeViewModel @Inject constructor( ) sendEventToChildren(ParentToChildrenEvent.OrderSuccessfullyPaid) } - is ChildToParentEvent.PaymentProcessing -> { - _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals - ) - } - is ChildToParentEvent.OrderSuccessfullyPaid -> { + is ChildToParentEvent.PaymentProcessing, + is ChildToParentEvent.OrderSuccessfullyPaid, + is ChildToParentEvent.PaymentFailed -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index bff49a576bb..305edc17db4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -229,6 +229,7 @@ class WooPosTotalsViewModel @Inject constructor( } is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { uiState.value = buildPaymentFailedState() + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed) } is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { throw IllegalStateException("Interac refund is not supported in POS") From e05fafd29c8c6643b8bde0fa92487483a6b20ff4 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 14:27:39 +0100 Subject: [PATCH 068/132] Implement failed payment "retry" action --- .../WooPosHomeChildToParentCommunication.kt | 1 + .../ui/woopos/home/WooPosHomeViewModel.kt | 6 +++++- .../home/totals/WooPosTotalsViewModel.kt | 19 +++++++++++++++++-- .../failed/WooPosTotalsPaymentFailedScreen.kt | 2 +- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index 01890044915..6e48c75ac34 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -25,6 +25,7 @@ sealed class ChildToParentEvent { data object NewTransactionClicked : ChildToParentEvent() data object PaymentProcessing : ChildToParentEvent() data object PaymentFailed : ChildToParentEvent() + data object RetryFailedPaymentClicked : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 4e940c4e9ea..be5a5a76b86 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -117,7 +117,11 @@ class WooPosHomeViewModel @Inject constructor( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) } - + is ChildToParentEvent.RetryFailedPaymentClicked -> { + _state.value = _state.value.copy( + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals + ) + } ChildToParentEvent.ExitPosClicked -> { _state.value = _state.value.copy( exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = true) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 305edc17db4..25d7c0b23ce 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -75,12 +75,14 @@ class WooPosTotalsViewModel @Inject constructor( val state: StateFlow = uiState - private var dataState: MutableStateFlow = savedState.getStateFlow( + private val dataState: MutableStateFlow = savedState.getStateFlow( scope = viewModelScope, initialValue = TotalsDataState(), key = KEY_STATE, ) + private var order: Order? = null + private var isTTPPaymentInProgress: Boolean get() = savedState.get(KEY_TTP_PAYMENT_IN_PROGRESS) == true set(value) { @@ -151,7 +153,19 @@ class WooPosTotalsViewModel @Inject constructor( } WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> TODO() - WooPosTotalsUIEvent.RetryFailedTransactionClicked -> TODO() + WooPosTotalsUIEvent.RetryFailedTransactionClicked -> { + cancelPaymentAction() + viewModelScope.launch { + childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) + if (order == null) { + uiState.value = InitialState + childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + } else { + uiState.value = buildWooPosTotalsViewState(order!!) + collectPayment() + } + } + } } } @@ -275,6 +289,7 @@ class WooPosTotalsViewModel @Inject constructor( onSuccess = { order -> dataState.value = dataState.value.copy(orderId = order.id) uiState.value = buildWooPosTotalsViewState(order) + this@WooPosTotalsViewModel.order = order analyticsTracker.track(WooPosAnalyticsEvent.Event.OrderCreationSuccess) collectPayment() }, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index e1d77eb2599..80f32ae1de1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -78,7 +78,7 @@ fun WooPosPaymentFailedScreen( text = stringResource(R.string.woo_pos_payment_failed_exit_order) ) } - ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } + ) { onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) } Spacer(modifier = Modifier.height(80.dp.toAdaptivePadding())) } } From d3160ec78fc9fc15893886ab9180768822880158 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 14:45:14 +0100 Subject: [PATCH 069/132] Implement failed payment "exit order" action --- .../WooPosHomeChildToParentCommunication.kt | 1 + .../WooPosHomeParentToChildCommunication.kt | 1 + .../ui/woopos/home/WooPosHomeViewModel.kt | 6 +++++ .../woopos/home/cart/WooPosCartViewModel.kt | 8 +++++-- .../home/totals/WooPosTotalsViewModel.kt | 24 +++++++++---------- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index 6e48c75ac34..e7197367ff4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -26,6 +26,7 @@ sealed class ChildToParentEvent { data object PaymentProcessing : ChildToParentEvent() data object PaymentFailed : ChildToParentEvent() data object RetryFailedPaymentClicked : ChildToParentEvent() + data object ExitOrderAfterFailedTransactionClicked : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt index ff7093dbf58..98af275c508 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt @@ -25,6 +25,7 @@ sealed class ParentToChildrenEvent { ) : ParentToChildrenEvent() data class CheckoutClicked(val productIds: List) : ParentToChildrenEvent() data object OrderSuccessfullyPaid : ParentToChildrenEvent() + data object OrderCardPaymentAborted : ParentToChildrenEvent() } interface WooPosParentToChildrenEventReceiver { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index be5a5a76b86..313bfa27658 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -104,6 +104,12 @@ class WooPosHomeViewModel @Inject constructor( ) } + is ChildToParentEvent.ExitOrderAfterFailedTransactionClicked -> { + _state.value = _state.value.copy( + screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible + ) + sendEventToChildren(ParentToChildrenEvent.OrderCardPaymentAborted) + } is ChildToParentEvent.NewTransactionClicked -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt index d876a51efa4..6beac3b6b17 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt @@ -117,8 +117,12 @@ class WooPosCartViewModel @Inject constructor( parentToChildrenEventReceiver.events.collect { event -> when (event) { is ParentToChildrenEvent.BackFromCheckoutToCartClicked -> handleBackFromCheckoutToCartClicked() + is ParentToChildrenEvent.ItemClickedInProductSelector -> handleItemClickedInProductSelector(event) - is ParentToChildrenEvent.OrderSuccessfullyPaid -> handleOrderSuccessfullyPaid() + + is ParentToChildrenEvent.OrderSuccessfullyPaid, + is ParentToChildrenEvent.OrderCardPaymentAborted -> clearCart() + is ParentToChildrenEvent.CheckoutClicked -> Unit } } @@ -151,7 +155,7 @@ class WooPosCartViewModel @Inject constructor( } } - private fun handleOrderSuccessfullyPaid() { + private fun clearCart() { _state.value = WooPosCartState() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 25d7c0b23ce..cf33d8369b2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -151,19 +151,18 @@ class WooPosTotalsViewModel @Inject constructor( is WooPosTotalsUIEvent.RetryOrderCreationClicked -> { createOrderDraft(dataState.value.productIds) } - - WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> TODO() - WooPosTotalsUIEvent.RetryFailedTransactionClicked -> { + WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> viewModelScope.launch { + childrenToParentEventSender.sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + } + WooPosTotalsUIEvent.RetryFailedTransactionClicked -> viewModelScope.launch { cancelPaymentAction() - viewModelScope.launch { - childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) - if (order == null) { - uiState.value = InitialState - childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) - } else { - uiState.value = buildWooPosTotalsViewState(order!!) - collectPayment() - } + childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) + if (order == null) { + uiState.value = InitialState + childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + } else { + uiState.value = buildWooPosTotalsViewState(order!!) + collectPayment() } } } @@ -205,6 +204,7 @@ class WooPosTotalsViewModel @Inject constructor( } is ParentToChildrenEvent.ItemClickedInProductSelector, + ParentToChildrenEvent.OrderCardPaymentAborted, ParentToChildrenEvent.OrderSuccessfullyPaid -> Unit } } From 3fc89e6333b62650907f901f05df64f72a6bc076 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 14:50:53 +0100 Subject: [PATCH 070/132] Move debug Totals payment state to the top --- .../woopos/home/totals/WooPosTotalsScreen.kt | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 44e4d3b2ad0..ec47e048a3c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -133,15 +133,22 @@ private fun TotalsLoaded( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - val error = state.error - if (error != null) { - Box( - modifier = Modifier - .fillMaxWidth() - .weight(1.1f) - .background(WooPosTheme.colors.totalsErrorBackground) - ) { - TotalsError(modifier = Modifier, error = error) + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .background(WooPosTheme.colors.totalsErrorBackground) + ) { + val error = state.error + when { + error != null -> TotalsError(modifier = Modifier, error = error) + else -> { + Text( + modifier = Modifier.align(Alignment.Center), + text = state.paymentStateText, + style = MaterialTheme.typography.body1, + ) + } } } TotalsGrid(modifier = Modifier.weight(1f), state = state) @@ -230,10 +237,6 @@ private fun TotalsGrid( fontWeightOne = FontWeight.Medium, fontWeightTwo = FontWeight.Bold, ) - Text( - text = state.paymentStateText, - style = MaterialTheme.typography.body1, - ) } } From 84f825faceec5cbd4a403ba6f8cacf6a83a4092e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:07:24 +0100 Subject: [PATCH 071/132] Add home VM test: give home screen is at checkout, when exit order clicked after failed payment, then should show cart --- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 002e569a42c..045ccbc9ecc 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -273,6 +273,23 @@ class WooPosHomeViewModelTest { assertTrue(viewModel.state.value.screenPositionState is WooPosHomeState.ScreenPositionState.Checkout) } + @Test + fun `give home screen is at checkout, when exit order clicked after failed payment, then should show cart`() = runTest { + // GIVEN + val events = MutableSharedFlow() + whenever(childrenToParentEventReceiver.events).thenReturn(events) + + val viewModel: WooPosHomeViewModel = createViewModel() + events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + + // WHEN + events.emit(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + + // THEN + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) + } + private fun createViewModel() = WooPosHomeViewModel( childrenToParentEventReceiver, parentToChildrenEventSender, From 447a9e83604e45e43630ce7ada29563edb9d8375 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:09:25 +0100 Subject: [PATCH 072/132] Add home VM test: give home screen is at checkout, when payment processing started, then should show full screen totals state --- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 045ccbc9ecc..fbd3a2e0b3f 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -290,6 +290,23 @@ class WooPosHomeViewModelTest { assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) } + @Test + fun `give home screen is at checkout, when payment processing started, then should show full screen totals state`() = runTest { + // GIVEN + val events = MutableSharedFlow() + whenever(childrenToParentEventReceiver.events).thenReturn(events) + + val viewModel: WooPosHomeViewModel = createViewModel() + events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + + // WHEN + events.emit(ChildToParentEvent.PaymentProcessing) + + // THEN + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + } + private fun createViewModel() = WooPosHomeViewModel( childrenToParentEventReceiver, parentToChildrenEventSender, From 7ca6439aa8b46d88784acdbfbaf4a790ec7907cc Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:11:19 +0100 Subject: [PATCH 073/132] Add home VM test: give home screen is at checkout, processing payment, when payment fails, then should show full screen totals state --- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index fbd3a2e0b3f..2cf513842d6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -307,6 +307,25 @@ class WooPosHomeViewModelTest { assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) } + @Test + fun `give home screen is at checkout, processing payment, when payment fails, then should show full screen totals state`() = runTest { + // GIVEN + val events = MutableSharedFlow() + whenever(childrenToParentEventReceiver.events).thenReturn(events) + + val viewModel: WooPosHomeViewModel = createViewModel() + events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + events.emit(ChildToParentEvent.PaymentProcessing) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + + // WHEN + events.emit(ChildToParentEvent.PaymentFailed) + + // THEN + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + } + private fun createViewModel() = WooPosHomeViewModel( childrenToParentEventReceiver, parentToChildrenEventSender, From b0d9b392188dc11b9beba6a4272f4c489154e6cc Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:12:49 +0100 Subject: [PATCH 074/132] Add home VM test: give home screen is at checkout, failed payment, when retry payment clicked, then should show cart with totals --- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 2cf513842d6..695a0a2a24d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -326,6 +326,27 @@ class WooPosHomeViewModelTest { assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) } + @Test + fun `give home screen is at checkout, failed payment, when retry payment clicked, then should show cart with totals`() = runTest { + // GIVEN + val events = MutableSharedFlow() + whenever(childrenToParentEventReceiver.events).thenReturn(events) + + val viewModel: WooPosHomeViewModel = createViewModel() + events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + events.emit(ChildToParentEvent.PaymentProcessing) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + events.emit(ChildToParentEvent.PaymentFailed) + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + + // WHEN + events.emit(ChildToParentEvent.RetryFailedPaymentClicked) + + // THEN + assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + } + private fun createViewModel() = WooPosHomeViewModel( childrenToParentEventReceiver, parentToChildrenEventSender, From 9dad874fe05950996e128720a123941763ac222c Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:14:49 +0100 Subject: [PATCH 075/132] Tweak heights of totals boxes --- .../android/ui/woopos/home/totals/WooPosTotalsScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index ec47e048a3c..f1687b010cc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -136,7 +136,7 @@ private fun TotalsLoaded( Box( modifier = Modifier .fillMaxWidth() - .weight(1f) + .weight(1.1f) .background(WooPosTheme.colors.totalsErrorBackground) ) { val error = state.error @@ -151,7 +151,7 @@ private fun TotalsLoaded( } } } - TotalsGrid(modifier = Modifier.weight(1f), state = state) + TotalsGrid(modifier = Modifier.weight(.9f), state = state) } } From 810ea0129c4775793c8cb04eb37625f3c596509a Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:17:13 +0100 Subject: [PATCH 076/132] Fix typos in test method names --- .../android/ui/woopos/home/WooPosHomeViewModelTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 695a0a2a24d..7db45a0bfee 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -274,7 +274,7 @@ class WooPosHomeViewModelTest { } @Test - fun `give home screen is at checkout, when exit order clicked after failed payment, then should show cart`() = runTest { + fun `given home screen is at checkout, when exit order clicked after failed payment, then should show cart`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) @@ -291,7 +291,7 @@ class WooPosHomeViewModelTest { } @Test - fun `give home screen is at checkout, when payment processing started, then should show full screen totals state`() = runTest { + fun `given home screen is at checkout, when payment processing started, then should show full screen totals state`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) @@ -308,7 +308,7 @@ class WooPosHomeViewModelTest { } @Test - fun `give home screen is at checkout, processing payment, when payment fails, then should show full screen totals state`() = runTest { + fun `given home screen is at checkout, processing payment, when payment fails, then should show full screen totals state`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) @@ -327,7 +327,7 @@ class WooPosHomeViewModelTest { } @Test - fun `give home screen is at checkout, failed payment, when retry payment clicked, then should show cart with totals`() = runTest { + fun `given home screen is at checkout, failed payment, when retry payment clicked, then should show cart with totals`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) From 6abc26e4d1b5f166bdcec4f6793b49e9eacb1c75 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 15:23:37 +0100 Subject: [PATCH 077/132] Add Cart VM test: given non-empty cart, when card payment is aborted, then should clear the cart --- .../home/cart/WooPosCartViewModelTest.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt index 54784e1f829..d9eb2c2e4b1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt @@ -513,6 +513,37 @@ class WooPosCartViewModelTest { assertThat(finalItem.isAppearanceAnimationPlayed).isTrue } + @Test + fun `given non-empty cart, when card payment is aborted, then should clear the cart`() = runTest { + // GIVEN + val product = ProductTestUtils.generateProduct( + productId = 23L, + productName = "title", + amount = "10.0" + ).copy(firstImageUrl = "url") + + val parentToChildrenEventsMutableFlow = MutableSharedFlow() + whenever(parentToChildrenEventReceiver.events).thenReturn(parentToChildrenEventsMutableFlow) + whenever(getProductById(eq(product.remoteId))).thenReturn(product) + val sut = createSut() + val states = sut.state.captureValues() + + parentToChildrenEventsMutableFlow.emit( + ParentToChildrenEvent.ItemClickedInProductSelector( + WooPosItemsViewModel.ItemClickedData.SimpleProduct(id = product.remoteId) + ) + ) + + // WHEN + parentToChildrenEventsMutableFlow.emit(ParentToChildrenEvent.OrderCardPaymentAborted) + + // THEN + val toolbar = states.last().toolbar + assertThat(toolbar.backIconVisible).isFalse() + assertThat(toolbar.itemsCount).isNull() + assertThat(toolbar.isClearAllButtonVisible).isFalse() + } + private fun createSut(): WooPosCartViewModel { return WooPosCartViewModel( childrenToParentEventSender, From e1eb05c1c7386f7db004445665639c1c64d90904 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 16:06:00 +0100 Subject: [PATCH 078/132] Add Totals VM test: given order draft created and reader connected, when card tapped, should show payment processing screen --- .../home/totals/WooPosTotalsViewModelTest.kt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 55bcce17634..2fc7caf2819 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -21,6 +21,8 @@ import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentE import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentControllerFactory +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState +import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState.CardReaderPaymentState import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentStateProvider import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderTrackCanceledFlowAction import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper @@ -795,6 +797,49 @@ class WooPosTotalsViewModelTest { assertThat(vm.paymentScope!!.isActive).isFalse } + @Test + fun `given order draft created and reader connected, when card tapped, should show payment processing screen`() = runTest { + // GIVEN + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ) + ).thenReturn("Processing payment") + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_subtitle + ) + ).thenReturn("Please wait…") + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + + // WHEN + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + + // THEN + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) + } + + @Test + fun `given payment failed, when retry clicked, then should retry`() { + + } + + @Test + fun `given payment failed, when exit order clicked, then should inform home about the situation`() { + + } + private fun createViewModelAndSetupForSuccessfulOrderCreation( controllerFactory: CardReaderPaymentControllerFactory = paymentControllerFactory ): WooPosTotalsViewModel { From 6737f15ec0a8a6d0a2fbdd8c981b94bdd6dbd8b1 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 16:11:00 +0100 Subject: [PATCH 079/132] Add Totals VM test: given payment failed, when retry clicked, then should retry --- .../home/totals/WooPosTotalsViewModelTest.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 2fc7caf2819..5aa0f837ebb 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -19,6 +19,7 @@ import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderInteracR import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentErrorMapper import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentOrderHelper +import com.woocommerce.android.ui.payments.cardreader.payment.PaymentFlowError import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentControllerFactory import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentOrRefundState @@ -832,7 +833,36 @@ class WooPosTotalsViewModelTest { @Test fun `given payment failed, when retry clicked, then should retry`() { + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) + .thenReturn("Unfortunately, this payment has been declined.") + + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + // WHEN + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( + errorType = PaymentFlowError.NoNetwork, {}) + + // THEN + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) } @Test From 3c3c2196bdb8fb62a6e401965d6f4e0253dd22e1 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 16:40:51 +0100 Subject: [PATCH 080/132] Add Totals VM test: given payment failed, when retry clicked, then should retry --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 5 +---- .../ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 9 ++++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index cf33d8369b2..09e3c65657f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -222,11 +222,8 @@ class WooPosTotalsViewModel @Inject constructor( } when (paymentState) { + is CardReaderPaymentState.CollectingPayment, is CardReaderPaymentState.LoadingData -> { - // TODO: show loading state within the totals pane - } - is CardReaderPaymentState.CollectingPayment -> { - // TODO: show "tap or swipe" state within the totals pane } is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 5aa0f837ebb..deed49f116d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -855,14 +855,17 @@ class WooPosTotalsViewModelTest { ) whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) - - // WHEN paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( errorType = PaymentFlowError.NoNetwork, {}) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + + // WHEN + paymentState.value = CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) // THEN - assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.Totals::class.java) } @Test From 34d84e50c481023432b007d684fb6acab52fdcf3 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 16:42:10 +0100 Subject: [PATCH 081/132] Add Totals VM test: given payment failed, when exit order clicked, then should inform home about the situation --- .../home/totals/WooPosTotalsViewModelTest.kt | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index deed49f116d..4cd3512d65f 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -869,8 +869,39 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when exit order clicked, then should inform home about the situation`() { + fun `given payment failed, when exit order clicked, then should inform home about the situation`() = runTest{ + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) + .thenReturn("Unfortunately, this payment has been declined.") + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( + errorType = PaymentFlowError.NoNetwork, {}) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + + // WHEN + vm.onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) + + // THEN + verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) } private fun createViewModelAndSetupForSuccessfulOrderCreation( From 24399972f16b6d313dc7082ec39253f50df48a1f Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 17:11:54 +0100 Subject: [PATCH 082/132] Ensure Cart is cleared in case state is restored --- .../woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 313bfa27658..ade9e241f4e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -96,6 +96,7 @@ class WooPosHomeViewModel @Inject constructor( _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible ) + sendEventToChildren(ParentToChildrenEvent.BackFromCheckoutToCartClicked) } is ChildToParentEvent.ItemClickedInProductSelector -> { From 62684c46023dc920438e575abec83876b2e7bf60 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 17:17:33 +0100 Subject: [PATCH 083/132] Clean up code --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 09e3c65657f..c018cf6e916 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -242,17 +242,11 @@ class WooPosTotalsViewModel @Inject constructor( uiState.value = buildPaymentFailedState() childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed) } - is CardReaderPaymentOrRefundState.CardReaderInteracRefundState -> { - throw IllegalStateException("Interac refund is not supported in POS") - } - is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment -> { - throw IllegalStateException("Built-in reader is not supported in POS") - } - is CardReaderPaymentState.PrintingReceipt -> { - throw IllegalStateException("PrintingReceipt is not supported in POS") - } + is CardReaderPaymentOrRefundState.CardReaderInteracRefundState, + is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment, + is CardReaderPaymentState.PrintingReceipt, CardReaderPaymentState.SharingReceipt -> { - throw IllegalStateException("SharingReceipt is not supported in POS") + throw IllegalArgumentException("Payment state: $paymentState not compatible with POS") } } } From 77f74b79f890fc056bf50036d2a504da58bdf77b Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 4 Dec 2024 17:36:31 +0100 Subject: [PATCH 084/132] Satisfy detekt's complaints --- .../ui/woopos/home/WooPosHomeViewModel.kt | 1 + .../ui/woopos/home/WooPosHomeViewModelTest.kt | 44 ++++++++++++++----- .../home/totals/WooPosTotalsViewModelTest.kt | 8 ++-- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index ade9e241f4e..d178a545bf0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -81,6 +81,7 @@ class WooPosHomeViewModel @Inject constructor( } } + @Suppress("LongMethod") private fun listenBottomEvents() { viewModelScope.launch { childrenToParentEventReceiver.events.collect { event -> diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 7db45a0bfee..a1d8d7d316b 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -281,13 +281,17 @@ class WooPosHomeViewModelTest { val viewModel: WooPosHomeViewModel = createViewModel() events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) // WHEN events.emit(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) // THEN - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) } @Test @@ -298,13 +302,17 @@ class WooPosHomeViewModelTest { val viewModel: WooPosHomeViewModel = createViewModel() events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) // WHEN events.emit(ChildToParentEvent.PaymentProcessing) // THEN - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) } @Test @@ -315,15 +323,21 @@ class WooPosHomeViewModelTest { val viewModel: WooPosHomeViewModel = createViewModel() events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) events.emit(ChildToParentEvent.PaymentProcessing) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) // WHEN events.emit(ChildToParentEvent.PaymentFailed) // THEN - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) } @Test @@ -334,17 +348,25 @@ class WooPosHomeViewModelTest { val viewModel: WooPosHomeViewModel = createViewModel() events.emit(ChildToParentEvent.CheckoutClicked(listOf(1))) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) events.emit(ChildToParentEvent.PaymentProcessing) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) events.emit(ChildToParentEvent.PaymentFailed) - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) // WHEN events.emit(ChildToParentEvent.RetryFailedPaymentClicked) // THEN - assertThat(viewModel.state.value.screenPositionState).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) + assertThat( + viewModel.state.value.screenPositionState + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) } private fun createViewModel() = WooPosHomeViewModel( diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 4cd3512d65f..743c15db356 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -857,7 +857,8 @@ class WooPosTotalsViewModelTest { val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( - errorType = PaymentFlowError.NoNetwork, {}) + errorType = PaymentFlowError.NoNetwork, {} + ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) // WHEN @@ -869,7 +870,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when exit order clicked, then should inform home about the situation`() = runTest{ + fun `given payment failed, when exit order clicked, then should inform home about the situation`() = runTest { // GIVEN whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) .thenReturn("Processing payment") @@ -894,7 +895,8 @@ class WooPosTotalsViewModelTest { val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( - errorType = PaymentFlowError.NoNetwork, {}) + errorType = PaymentFlowError.NoNetwork, {} + ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) // WHEN From e499d895a884fd4819fc49f180ca815a7c042ad5 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 13:38:43 +0100 Subject: [PATCH 085/132] Manage payment coroutine scope internally in controller --- .../payment/CardReaderPaymentViewModel.kt | 4 +--- .../controller/CardReaderPaymentController.kt | 12 ++++++++++-- .../CardReaderPaymentControllerFactory.kt | 3 --- .../woopos/home/totals/WooPosTotalsViewModel.kt | 15 ++++++--------- .../controller/CardReaderPaymentControllerTest.kt | 13 ++++++++----- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index ad4d67542a7..1615586fb0e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.map -import androidx.lifecycle.viewModelScope import com.woocommerce.android.AppPrefs import com.woocommerce.android.cardreader.CardReaderManager import com.woocommerce.android.cardreader.payments.PaymentData @@ -68,7 +67,6 @@ class CardReaderPaymentViewModel @Inject constructor( } private val paymentController = CardReaderPaymentController( - scope = viewModelScope, cardReaderManager = cardReaderManager, orderRepository = orderRepository, selectedSite = selectedSite, @@ -128,7 +126,7 @@ class CardReaderPaymentViewModel @Inject constructor( fun onPrintResult(result: PrintJobResult) = paymentController.onPrintResult(result) @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) - public override fun onCleared() = paymentController.onCleared() + public override fun onCleared() = paymentController.stop() fun onBackPressed() = paymentController.onBackPressed() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentController.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentController.kt index 9e05efaa65d..6e6e07d618d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentController.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentController.kt @@ -72,12 +72,16 @@ import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.STARTED import com.woocommerce.android.util.WooLog import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.wordpress.android.fluxc.store.WooCommerceStore @@ -88,7 +92,6 @@ private const val CANADA_FEE_FLAT_IN_CENTS = 15L @Suppress("LongParameterList", "LargeClass") class CardReaderPaymentController( - private val scope: CoroutineScope, private val cardReaderManager: CardReaderManager, private val orderRepository: OrderDetailRepository, private val selectedSite: SelectedSite, @@ -113,6 +116,8 @@ class CardReaderPaymentController( private val cardReaderType: CardReaderType, private val isTTPPaymentInProgress: KMutableProperty0, ) { + private var scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + private val _paymentState: MutableStateFlow = MutableStateFlow(CardReaderPaymentState.LoadingData(::onCancelPaymentFlow)) val paymentState: StateFlow = _paymentState @@ -131,6 +136,8 @@ class CardReaderPaymentController( } fun start() { + scope.cancel() + scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) if (cardReaderManager.readerStatus.value is CardReaderStatus.Connected) { startFlowWhenReaderConnected() } else { @@ -778,10 +785,11 @@ class CardReaderPaymentController( } } - fun onCleared() { + fun stop() { paymentDataForRetry?.let { cardReaderManager.cancelPayment(it) } + scope.cancel() } fun onBackPressed() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt index 023dd18a4ca..7dde174b3f5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerFactory.kt @@ -20,7 +20,6 @@ import com.woocommerce.android.ui.payments.tracking.CardReaderTrackingInfoKeeper import com.woocommerce.android.ui.payments.tracking.PaymentsFlowTracker import com.woocommerce.android.util.CoroutineDispatchers import com.woocommerce.android.util.CurrencyFormatter -import kotlinx.coroutines.CoroutineScope import org.wordpress.android.fluxc.store.WooCommerceStore import javax.inject.Inject import kotlin.reflect.KMutableProperty0 @@ -50,10 +49,8 @@ class CardReaderPaymentControllerFactory @Inject constructor( fun create( orderId: Long, paymentType: PaymentType, - coroutineScope: CoroutineScope, isTTPPaymentInProgress: KMutableProperty0, ): CardReaderPaymentController = CardReaderPaymentController( - scope = coroutineScope, cardReaderManager = cardReaderManager, orderRepository = orderRepository, selectedSite = selectedSite, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 433f792e522..a3b622de862 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -25,10 +25,6 @@ import com.woocommerce.android.util.WooLog.T import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.getStateFlow import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -77,15 +73,12 @@ class WooPosTotalsViewModel @Inject constructor( savedState[KEY_TTP_PAYMENT_IN_PROGRESS] = value } - private var paymentScope: CoroutineScope? = null private var cardReaderPaymentController: CardReaderPaymentController? = null private fun createCardReaderPaymentController(orderId: Long) { - paymentScope = CoroutineScope(SupervisorJob(viewModelScope.coroutineContext[Job])) cardReaderPaymentController = cardReaderPaymentControllerFactory.create( orderId = orderId, paymentType = PaymentOrRefund.Payment.PaymentType.WOO_POS, - coroutineScope = paymentScope!!, isTTPPaymentInProgress = ::isTTPPaymentInProgress, ) } @@ -141,8 +134,7 @@ class WooPosTotalsViewModel @Inject constructor( is ParentToChildrenEvent.BackFromCheckoutToCartClicked -> { cardReaderPaymentController?.onBackPressed() - cardReaderPaymentController?.onCleared() - paymentScope?.cancel() + cardReaderPaymentController?.stop() uiState.value = InitialState } @@ -173,6 +165,11 @@ class WooPosTotalsViewModel @Inject constructor( } } + override fun onCleared() { + super.onCleared() + cardReaderPaymentController?.stop() + } + private fun createOrderDraft(productIds: List) { viewModelScope.launch { uiState.value = WooPosTotalsViewState.Loading diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt index 2834c1d4dda..aef0baa2541 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt @@ -83,7 +83,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle import org.assertj.core.api.Assertions.assertThat import org.junit.Before @@ -210,7 +209,6 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { cardReaderFlowParam: CardReaderFlowParam.PaymentOrRefund = paymentParam, ) { controller = CardReaderPaymentController( - scope = TestScope(coroutinesTestRule.testDispatcher), cardReaderManager = cardReaderManager, orderRepository = orderRepository, selectedSite = selectedSite, @@ -263,6 +261,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { testBlocking { whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { flow { + delay(1) // Ensure CardReaderDisplayMessage is emitted after flow is at CollectingPayment state emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(INSERT_CARD)) } } @@ -272,6 +271,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { } controller.start() + advanceUntilIdle() assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_collect_payment_hint) @@ -282,6 +282,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { testBlocking { whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { flow { + delay(1) // Ensure CardReaderDisplayMessage is emitted after flow is at CollectingPayment state emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(INSERT_OR_SWIPE_CARD)) } } @@ -291,6 +292,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { } controller.start() + advanceUntilIdle() assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_collect_payment_hint) @@ -301,6 +303,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { testBlocking { whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { flow { + delay(1) // Ensure CardReaderDisplayMessage is emitted after flow is at CollectingPayment state emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(SWIPE_CARD)) } } @@ -310,6 +313,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { } controller.start() + advanceUntilIdle() assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_collect_payment_hint) @@ -2411,7 +2415,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { } controller.start() - controller.onCleared() + controller.stop() verify(cardReaderManager).cancelPayment(any()) } @@ -2430,7 +2434,7 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { } controller.start() - controller.onCleared() + controller.stop() verify(cardReaderManager, never()).cancelPayment(any()) } @@ -3642,7 +3646,6 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { private fun setupControllerForInteracRefund() { val param = CardReaderFlowParam.PaymentOrRefund.Refund(ORDER_ID, refundAmount = BigDecimal(10.72)) controller = CardReaderPaymentController( - scope = TestScope(coroutinesTestRule.testDispatcher), cardReaderManager = cardReaderManager, orderRepository = orderRepository, selectedSite = selectedSite, From 6046d90a6690a34b05e087a4f64a185deaa598f7 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 13:42:46 +0100 Subject: [PATCH 086/132] Clean up code --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index a3b622de862..76b0be0eb45 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -166,7 +166,6 @@ class WooPosTotalsViewModel @Inject constructor( } override fun onCleared() { - super.onCleared() cardReaderPaymentController?.stop() } From ad8397301d46b5286cb6db8dee0316ddb9664cfb Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 14:10:48 +0100 Subject: [PATCH 087/132] Remove unused code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I've checked the history, and `CardReaderPaymentViewModel::reFetchOrder` has never been invoked outside of the class. When extracting payment flow to the controller, I missed the `@VisibleForTesting` annotation and treated it as a public function when it was supposed to be treated as `private`. I'm removing this method from VM – it's supposed to be invoked internally in `CardReaderPaymentController`. --- .../payments/cardreader/payment/CardReaderPaymentViewModel.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index 1615586fb0e..f8a978cb408 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -118,9 +118,6 @@ class CardReaderPaymentViewModel @Inject constructor( fun retry(orderId: Long, billingEmail: String, paymentData: PaymentData, amountLabel: String) = paymentController.retry(orderId, billingEmail, paymentData, amountLabel) - @VisibleForTesting - fun reFetchOrder() = paymentController.reFetchOrder() - fun onViewCreated() = paymentController.onViewCreated() fun onPrintResult(result: PrintJobResult) = paymentController.onPrintResult(result) From 8f715b48bd2a0b4078aebb2ab03d7b3df3583ca2 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 17:35:59 +0100 Subject: [PATCH 088/132] Remove delay(1) from tests --- .../CardReaderPaymentControllerTest.kt | 164 +++++++----------- 1 file changed, 65 insertions(+), 99 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt index aef0baa2541..8e9089de523 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/controller/CardReaderPaymentControllerTest.kt @@ -9,10 +9,10 @@ import com.woocommerce.android.cardreader.connection.CardReader import com.woocommerce.android.cardreader.connection.CardReaderStatus import com.woocommerce.android.cardreader.connection.event.BatteryStatus import com.woocommerce.android.cardreader.connection.event.BluetoothCardReaderMessages +import com.woocommerce.android.cardreader.connection.event.BluetoothCardReaderMessages.CardReaderNoMessage import com.woocommerce.android.cardreader.connection.event.CardReaderBatteryStatus import com.woocommerce.android.cardreader.payments.CardInteracRefundStatus import com.woocommerce.android.cardreader.payments.CardPaymentStatus -import com.woocommerce.android.cardreader.payments.CardPaymentStatus.AdditionalInfoType import com.woocommerce.android.cardreader.payments.CardPaymentStatus.AdditionalInfoType.CARD_REMOVED_TOO_EARLY import com.woocommerce.android.cardreader.payments.CardPaymentStatus.AdditionalInfoType.CHECK_MOBILE_DEVICE import com.woocommerce.android.cardreader.payments.CardPaymentStatus.AdditionalInfoType.INSERT_CARD @@ -238,19 +238,15 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { @Test fun `given collect payment shown, when RETRY message received, then collect payment hint updated`() = testBlocking { - whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { - flow { - delay(1) // make sure it's run after collecting payment starts - emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(AdditionalInfoType.RETRY_CARD)) - } - } - - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(CollectingPayment) } - } + val paymentStatus = MutableStateFlow(InitializingPayment) + val readerMessages = MutableStateFlow(CardReaderNoMessage) + whenever(cardReaderManager.collectPayment(any())).thenReturn(paymentStatus) + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenReturn(readerMessages) + paymentStatus.value = CollectingPayment controller.start() - advanceUntilIdle() + + readerMessages.value = BluetoothCardReaderMessages.CardReaderDisplayMessage(RETRY_CARD) assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_retry_card_prompt) @@ -259,19 +255,15 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { @Test fun `given collect payment shown, when INSERT_CARD received, then collect payment hint updated`() = testBlocking { - whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { - flow { - delay(1) // Ensure CardReaderDisplayMessage is emitted after flow is at CollectingPayment state - emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(INSERT_CARD)) - } - } - - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(CollectingPayment) } - } + val paymentStatus = MutableStateFlow(InitializingPayment) + val readerMessages = MutableStateFlow(CardReaderNoMessage) + whenever(cardReaderManager.collectPayment(any())).thenReturn(paymentStatus) + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenReturn(readerMessages) + paymentStatus.value = CollectingPayment controller.start() - advanceUntilIdle() + + readerMessages.value = BluetoothCardReaderMessages.CardReaderDisplayMessage(INSERT_CARD) assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_collect_payment_hint) @@ -280,19 +272,15 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { @Test fun `given collect payment shown, when INSERT_OR_SWIPE_CARD received, then collect payment hint updated`() = testBlocking { - whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { - flow { - delay(1) // Ensure CardReaderDisplayMessage is emitted after flow is at CollectingPayment state - emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(INSERT_OR_SWIPE_CARD)) - } - } - - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(CollectingPayment) } - } + val paymentStatus = MutableStateFlow(InitializingPayment) + val readerMessages = MutableStateFlow(CardReaderNoMessage) + whenever(cardReaderManager.collectPayment(any())).thenReturn(paymentStatus) + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenReturn(readerMessages) + paymentStatus.value = CollectingPayment controller.start() - advanceUntilIdle() + + readerMessages.value = BluetoothCardReaderMessages.CardReaderDisplayMessage(INSERT_OR_SWIPE_CARD) assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_collect_payment_hint) @@ -301,19 +289,15 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { @Test fun `given collect payment shown, when SWIPE_CARD received, then collect payment hint updated`() = testBlocking { - whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { - flow { - delay(1) // Ensure CardReaderDisplayMessage is emitted after flow is at CollectingPayment state - emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(SWIPE_CARD)) - } - } - - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(CollectingPayment) } - } + val paymentStatus = MutableStateFlow(InitializingPayment) + val readerMessages = MutableStateFlow(CardReaderNoMessage) + whenever(cardReaderManager.collectPayment(any())).thenReturn(paymentStatus) + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenReturn(readerMessages) + paymentStatus.value = CollectingPayment controller.start() - advanceUntilIdle() + + readerMessages.value = BluetoothCardReaderMessages.CardReaderDisplayMessage(SWIPE_CARD) assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_collect_payment_hint) @@ -322,19 +306,15 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { @Test fun `given collect payment shown, when REMOVE_CARD received, then collect payment hint updated`() = testBlocking { - whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { - flow { - delay(1) // make sure it's run after collecting payment starts - emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(REMOVE_CARD)) - } - } - - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(CollectingPayment) } - } + val paymentStatus = MutableStateFlow(InitializingPayment) + val readerMessages = MutableStateFlow(CardReaderNoMessage) + whenever(cardReaderManager.collectPayment(any())).thenReturn(paymentStatus) + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenReturn(readerMessages) + paymentStatus.value = CollectingPayment controller.start() - advanceUntilIdle() + + readerMessages.value = BluetoothCardReaderMessages.CardReaderDisplayMessage(REMOVE_CARD) assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_remove_card_prompt) @@ -343,18 +323,15 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { @Test fun `given collect payment shown, when TRY_OTHER_CARD message received, then collect payment hint updated`() = testBlocking { - whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { - flow { - delay(1) // make sure it's run after collecting payment starts - emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(TRY_ANOTHER_CARD)) - } - } - - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(CollectingPayment) } - } + val paymentStatus = MutableStateFlow(InitializingPayment) + val readerMessages = MutableStateFlow(CardReaderNoMessage) + whenever(cardReaderManager.collectPayment(any())).thenReturn(paymentStatus) + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenReturn(readerMessages) + paymentStatus.value = CollectingPayment controller.start() + + readerMessages.value = BluetoothCardReaderMessages.CardReaderDisplayMessage(TRY_ANOTHER_CARD) advanceUntilIdle() assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) @@ -364,19 +341,15 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { @Test fun `given collect payment shown, when CARD_REMOVED_TOO_EARLY message received, then collect payment hint updated`() = testBlocking { - whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { - flow { - delay(1) // make sure it runs after collecting payment starts - emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(CARD_REMOVED_TOO_EARLY)) - } - } - - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(CollectingPayment) } - } + val paymentStatus = MutableStateFlow(InitializingPayment) + val readerMessages = MutableStateFlow(CardReaderNoMessage) + whenever(cardReaderManager.collectPayment(any())).thenReturn(paymentStatus) + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenReturn(readerMessages) + paymentStatus.value = CollectingPayment controller.start() - advanceUntilIdle() + + readerMessages.value = BluetoothCardReaderMessages.CardReaderDisplayMessage(CARD_REMOVED_TOO_EARLY) assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_card_removed_too_early) @@ -385,19 +358,15 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { @Test fun `given collect payment shown, when TRY_OTHER_READ message received, then collect payment hint updated`() = testBlocking { - whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { - flow { - delay(1) // make sure it's run after collecting payment starts - emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(TRY_ANOTHER_READ_METHOD)) - } - } - - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(CollectingPayment) } - } + val paymentStatus = MutableStateFlow(InitializingPayment) + val readerMessages = MutableStateFlow(CardReaderNoMessage) + whenever(cardReaderManager.collectPayment(any())).thenReturn(paymentStatus) + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenReturn(readerMessages) + paymentStatus.value = CollectingPayment controller.start() - advanceUntilIdle() + + readerMessages.value = BluetoothCardReaderMessages.CardReaderDisplayMessage(TRY_ANOTHER_READ_METHOD) assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_try_another_read_method_prompt) @@ -406,19 +375,16 @@ class CardReaderPaymentControllerTest : BaseUnitTest() { @Test fun `given collect payment shown, when MULTIPLE_CARDS_DETECTED received, then collect payment hint updated`() = testBlocking { - whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenAnswer { - flow { - delay(1) // make sure it's run after collecting payment starts - emit(BluetoothCardReaderMessages.CardReaderDisplayMessage(MULTIPLE_CONTACTLESS_CARDS_DETECTED)) - } - } - - whenever(cardReaderManager.collectPayment(any())).thenAnswer { - flow { emit(CollectingPayment) } - } + val paymentStatus = MutableStateFlow(InitializingPayment) + val readerMessages = MutableStateFlow(CardReaderNoMessage) + whenever(cardReaderManager.collectPayment(any())).thenReturn(paymentStatus) + whenever(cardReaderManager.displayBluetoothCardReaderMessages).thenReturn(readerMessages) + paymentStatus.value = CollectingPayment controller.start() - advanceUntilIdle() + + readerMessages.value = + BluetoothCardReaderMessages.CardReaderDisplayMessage(MULTIPLE_CONTACTLESS_CARDS_DETECTED) assertThat((controller.paymentState.value as CardReaderPaymentState.CollectingPayment).cardReaderHint) .isEqualTo(R.string.card_reader_payment_multiple_contactless_cards_detected_prompt) From 51eb2b2fd35b382f4086ae00de83e90c7588583d Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 17:55:09 +0100 Subject: [PATCH 089/132] Use WooPosColors for `paymentProcessingBackground` --- .../android/ui/woopos/common/composeui/WooPosTheme.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index 31e2718ae0d..5c89e708778 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -197,7 +197,7 @@ private val DarkCustomColors = CustomColors( paymentSuccessText = WooPosColors.oldGrayLight, paymentSuccessIcon = WooPosColors.darkCustomColorsHomeBackground, homeBackground = WooPosColors.darkCustomColorsHomeBackground, - paymentProcessingBackground = Color(0xFF533582), + paymentProcessingBackground = WooPosColors.WooPurple70, ) private val LightCustomColors = CustomColors( @@ -211,7 +211,7 @@ private val LightCustomColors = CustomColors( paymentSuccessText = WooPosColors.Purple90, paymentSuccessIcon = Color.White, homeBackground = WooPosColors.Gray0, - paymentProcessingBackground = Color(0xFF533582), + paymentProcessingBackground = WooPosColors.WooPurple70, ) private val LocalCustomColors = staticCompositionLocalOf { From 100e9882b7d0c9293db7580c07df09ef45a6831b Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 18:00:28 +0100 Subject: [PATCH 090/132] Use WooPosColors for `totalsBackground` --- .../android/ui/woopos/common/composeui/WooPosTheme.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index 5c89e708778..bd24abaddf6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -206,7 +206,7 @@ private val LightCustomColors = CustomColors( success = WooPosColors.greenNotFromPalette, error = WooPosColors.lightCustomColorsError, totalsErrorBackground = WooPosColors.lightQuaternaryBackground, - totalsBackground = Color(0xFFF6F7F7), + totalsBackground = WooPosColors.Gray0, paymentSuccessBackground = WooPosColors.White, paymentSuccessText = WooPosColors.Purple90, paymentSuccessIcon = Color.White, From 8b4f5f7d3a0291e015db03cba68ae6cb5193019a Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 19:02:32 +0100 Subject: [PATCH 091/132] Replace hardcoded error message with real one --- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 10 +++++----- .../com/woocommerce/android/util/UiStringParser.kt | 12 ++++++++++++ .../woopos/home/totals/WooPosTotalsViewModelTest.kt | 9 +++++---- 3 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index c018cf6e916..7b4f45ce59f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -28,6 +28,7 @@ import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice +import com.woocommerce.android.util.UiStringParser import com.woocommerce.android.util.WooLog import com.woocommerce.android.util.WooLog.T import com.woocommerce.android.viewmodel.ResourceProvider @@ -57,6 +58,7 @@ class WooPosTotalsViewModel @Inject constructor( private val analyticsTracker: WooPosAnalyticsTracker, private val networkStatus: WooPosNetworkStatus, private val cardReaderPaymentControllerFactory: CardReaderPaymentControllerFactory, + private val uiStringParser: UiStringParser, private val savedState: SavedStateHandle, ) : ViewModel() { @@ -239,7 +241,7 @@ class WooPosTotalsViewModel @Inject constructor( childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { - uiState.value = buildPaymentFailedState() + uiState.value = buildPaymentFailedState(paymentState) childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed) } is CardReaderPaymentOrRefundState.CardReaderInteracRefundState, @@ -253,13 +255,11 @@ class WooPosTotalsViewModel @Inject constructor( } } - private fun buildPaymentFailedState(): PaymentFailed = PaymentFailed( + private fun buildPaymentFailedState(state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment): PaymentFailed = PaymentFailed( title = resourceProvider.getString( R.string.woopos_success_totals_payment_failed_title ), - subtitle = resourceProvider.getString( - R.string.woopos_success_totals_payment_failed_subtitle - ) + subtitle = uiStringParser.asString(state.errorType.message) ) private fun buildPaymentProcessingState(): PaymentProcessing = PaymentProcessing( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt new file mode 100644 index 00000000000..f9d7c423382 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt @@ -0,0 +1,12 @@ +package com.woocommerce.android.util + +import android.content.Context +import com.woocommerce.android.model.UiString +import com.woocommerce.android.util.UiHelpers.getTextOfUiString +import javax.inject.Inject + +class UiStringParser @Inject constructor( + private val context: Context +) { + fun asString(uiString: UiString): String = getTextOfUiString(context, uiString) +} \ No newline at end of file diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 743c15db356..9f7913cf47e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -41,6 +41,7 @@ import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice import com.woocommerce.android.util.CurrencyFormatter +import com.woocommerce.android.util.UiStringParser import com.woocommerce.android.viewmodel.ResourceProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -97,6 +98,7 @@ class WooPosTotalsViewModelTest { private val cardReaderOnboardingChecker: CardReaderOnboardingChecker = mock() private val cardReaderConfigProvider: CardReaderCountryConfigProvider = mock() private val paymentReceiptShare: PaymentReceiptShare = mock() + private val uiStringParser: UiStringParser = mock() private val paymentControllerFactory = CardReaderPaymentControllerFactory( cardReaderManager = cardReaderManager, orderRepository = orderRepository, @@ -840,8 +842,7 @@ class WooPosTotalsViewModelTest { .thenReturn("Please wait…") whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) .thenReturn("Payment failed") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) - .thenReturn("Unfortunately, this payment has been declined.") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") whenever(networkStatus.isConnected()).thenReturn(true) val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) @@ -878,8 +879,7 @@ class WooPosTotalsViewModelTest { .thenReturn("Please wait…") whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) .thenReturn("Payment failed") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) - .thenReturn("Unfortunately, this payment has been declined.") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") whenever(networkStatus.isConnected()).thenReturn(true) val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) @@ -977,6 +977,7 @@ class WooPosTotalsViewModelTest { analyticsTracker = analyticsTracker, networkStatus = networkStatus, cardReaderPaymentControllerFactory = cardReaderPaymentControllerFactory, + uiStringParser = uiStringParser, savedState = savedState, ) } From ba0bcdd9ad60482b5ef9bb61af73b4c873853eab Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 5 Dec 2024 19:17:28 +0100 Subject: [PATCH 092/132] Satisfy detekt's complaints --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 4 +++- .../kotlin/com/woocommerce/android/util/UiStringParser.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 7b4f45ce59f..354978dccec 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -255,7 +255,9 @@ class WooPosTotalsViewModel @Inject constructor( } } - private fun buildPaymentFailedState(state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment): PaymentFailed = PaymentFailed( + private fun buildPaymentFailedState( + state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment + ): PaymentFailed = PaymentFailed( title = resourceProvider.getString( R.string.woopos_success_totals_payment_failed_title ), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt index f9d7c423382..df79f5e7146 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UiStringParser.kt @@ -9,4 +9,4 @@ class UiStringParser @Inject constructor( private val context: Context ) { fun asString(uiString: UiString): String = getTextOfUiString(context, uiString) -} \ No newline at end of file +} From cf8d030cfaccd8576fc3ab79fe9df91a77e915da Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 09:54:46 +0100 Subject: [PATCH 093/132] Get order from db instead of caching in memory --- .../home/totals/WooPosTotalsRepository.kt | 14 +++++- .../home/totals/WooPosTotalsViewModel.kt | 10 ++--- .../home/totals/WooPosTotalsRepositoryTest.kt | 45 +++++++++---------- .../home/totals/WooPosTotalsViewModelTest.kt | 5 ++- 4 files changed, 39 insertions(+), 35 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepository.kt index a197ca2aece..d94b80978ab 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepository.kt @@ -1,6 +1,8 @@ package com.woocommerce.android.ui.woopos.home.totals import com.woocommerce.android.model.Order +import com.woocommerce.android.model.OrderMapper +import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.creation.OrderCreateEditRepository import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.util.DateUtils @@ -8,13 +10,17 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.async import kotlinx.coroutines.withContext +import org.wordpress.android.fluxc.store.WCOrderStore import java.util.Date import javax.inject.Inject class WooPosTotalsRepository @Inject constructor( private val orderCreateEditRepository: OrderCreateEditRepository, private val dateUtils: DateUtils, - private val getProductById: WooPosGetProductById + private val getProductById: WooPosGetProductById, + private val orderStore: WCOrderStore, + private val selectedSite: SelectedSite, + private val orderMapper: OrderMapper, ) { private var orderCreationJob: Deferred>? = null @@ -59,6 +65,12 @@ class WooPosTotalsRepository @Inject constructor( } } + suspend fun getOrderById(orderId: Long) = withContext(IO) { + orderStore.getOrderByIdAndSite(orderId, selectedSite.get())?.let { + orderMapper.toAppModel(it) + } + } + private companion object { /** * This magic value used to indicate that we don't want to send subtotals and totals diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 354978dccec..14767e9141a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -69,7 +69,7 @@ class WooPosTotalsViewModel @Inject constructor( } private val uiState: MutableStateFlow = - savedState.getStateFlow( + savedState.getStateFlow( scope = viewModelScope, initialValue = InitialState, key = "woo_pos_totals_view_state" @@ -83,8 +83,6 @@ class WooPosTotalsViewModel @Inject constructor( key = KEY_STATE, ) - private var order: Order? = null - private var isTTPPaymentInProgress: Boolean get() = savedState.get(KEY_TTP_PAYMENT_IN_PROGRESS) == true set(value) { @@ -159,11 +157,12 @@ class WooPosTotalsViewModel @Inject constructor( WooPosTotalsUIEvent.RetryFailedTransactionClicked -> viewModelScope.launch { cancelPaymentAction() childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) + val order = totalsRepository.getOrderById(dataState.value.orderId) if (order == null) { uiState.value = InitialState childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) } else { - uiState.value = buildWooPosTotalsViewState(order!!) + uiState.value = buildWooPosTotalsViewState(order) collectPayment() } } @@ -181,8 +180,6 @@ class WooPosTotalsViewModel @Inject constructor( if (cardReaderFacade.readerStatus.value is Connected) { val state = uiState.value check(state is WooPosTotalsViewState.Totals) - val orderId = dataState.value.orderId - check(orderId != EMPTY_ORDER_ID) check(uiState.value is WooPosTotalsViewState.Totals) createCardReaderPaymentController(dataState.value.orderId) cardReaderPaymentController?.start() @@ -282,7 +279,6 @@ class WooPosTotalsViewModel @Inject constructor( onSuccess = { order -> dataState.value = dataState.value.copy(orderId = order.id) uiState.value = buildWooPosTotalsViewState(order) - this@WooPosTotalsViewModel.order = order analyticsTracker.track(WooPosAnalyticsEvent.Event.OrderCreationSuccess) collectPayment() }, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepositoryTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepositoryTest.kt index 2d5c07548ea..f65e5c1ed69 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepositoryTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsRepositoryTest.kt @@ -1,6 +1,8 @@ package com.woocommerce.android.ui.woopos.home.totals import com.woocommerce.android.model.Order +import com.woocommerce.android.model.OrderMapper +import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.orders.creation.OrderCreateEditRepository import com.woocommerce.android.ui.products.ProductHelper import com.woocommerce.android.ui.products.ProductType @@ -16,12 +18,16 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.store.WCOrderStore class WooPosTotalsRepositoryTest { private val orderCreateEditRepository: OrderCreateEditRepository = mock() private val getProductById: WooPosGetProductById = mock() private val dateUtils: DateUtils = mock() + private val orderStore: WCOrderStore = mock() + private val selectedSite: SelectedSite = mock() + private val orderMapper: OrderMapper = mock() private lateinit var repository: WooPosTotalsRepository @@ -33,11 +39,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given empty product list, when createOrderWithProducts called, then return error`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = emptyList() // WHEN @@ -50,11 +52,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given product ids without duplicates, when createOrderWithProducts, then items all quantity one`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = listOf(1L, 2L, 3L) whenever(getProductById(1L)).thenReturn(product1) @@ -79,11 +77,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given product id, when createOrderWithProducts, then item name matches original product`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = listOf(1L) whenever(getProductById(1L)).thenReturn(product1) @@ -105,11 +99,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given product ids with duplicates, when createOrderWithProducts, then items quantity is correct`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = listOf(1L, 1L, 2L, 3L, 3L, 3L) whenever(getProductById(1L)).thenReturn(product1) @@ -133,11 +123,7 @@ class WooPosTotalsRepositoryTest { @Test fun `given product ids, when createOrder with some invalid ids, then return failure`() = runTest { // GIVEN - repository = WooPosTotalsRepository( - orderCreateEditRepository, - dateUtils, - getProductById - ) + repository = createRepository() val productIds = listOf(1L, -1L, 3L) val mockOrder: Order = mock() whenever(orderCreateEditRepository.createOrUpdateOrder(any(), eq(""))).thenReturn(Result.success(mockOrder)) @@ -151,4 +137,13 @@ class WooPosTotalsRepositoryTest { assertThat(result.exceptionOrNull()?.message).isEqualTo("Invalid product ID: -1") verify(orderCreateEditRepository, never()).createOrUpdateOrder(any(), eq("")) } + + private fun createRepository() = WooPosTotalsRepository( + orderCreateEditRepository, + dateUtils, + getProductById, + orderStore, + selectedSite, + orderMapper, + ) } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 9f7913cf47e..ec8dda7c0e9 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -834,7 +834,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when retry clicked, then should retry`() { + fun `given payment failed, when retry clicked, then should retry`() = runTest { // GIVEN whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) .thenReturn("Processing payment") @@ -906,7 +906,7 @@ class WooPosTotalsViewModelTest { verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) } - private fun createViewModelAndSetupForSuccessfulOrderCreation( + private suspend fun createViewModelAndSetupForSuccessfulOrderCreation( controllerFactory: CardReaderPaymentControllerFactory = paymentControllerFactory ): WooPosTotalsViewModel { whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title)) @@ -952,6 +952,7 @@ class WooPosTotalsViewModelTest { val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { on { events }.thenReturn(parentToChildrenEventFlow) } + whenever(totalsRepository.getOrderById(orderId)).thenReturn(order) return createViewModel( totalsRepository = totalsRepository, priceFormat = priceFormat, From 5b12dd873a2726370859fe6032726433f2401ec0 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 14:22:20 +0100 Subject: [PATCH 094/132] Improve failed payment "retry" handling --- .../WooPosHomeChildToParentCommunication.kt | 2 +- .../WooPosHomeParentToChildCommunication.kt | 1 - .../ui/woopos/home/WooPosHomeViewModel.kt | 10 +- .../woopos/home/cart/WooPosCartViewModel.kt | 3 +- .../woopos/home/totals/WooPosTotalsUIEvent.kt | 2 +- .../home/totals/WooPosTotalsViewModel.kt | 71 ++++++++---- .../home/totals/WooPosTotalsViewState.kt | 2 + .../failed/WooPosTotalsPaymentFailedScreen.kt | 34 +++--- WooCommerce/src/main/res/values/strings.xml | 6 +- .../ui/woopos/home/WooPosHomeViewModelTest.kt | 6 +- .../home/cart/WooPosCartViewModelTest.kt | 31 ------ .../home/totals/WooPosTotalsViewModelTest.kt | 104 ++++++++++++++++-- 12 files changed, 179 insertions(+), 93 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index e7197367ff4..39bd1d75405 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -26,7 +26,7 @@ sealed class ChildToParentEvent { data object PaymentProcessing : ChildToParentEvent() data object PaymentFailed : ChildToParentEvent() data object RetryFailedPaymentClicked : ChildToParentEvent() - data object ExitOrderAfterFailedTransactionClicked : ChildToParentEvent() + data object GoBackToCheckoutAfterFailedPayment : ChildToParentEvent() data object OrderSuccessfullyPaid : ChildToParentEvent() data object ExitPosClicked : ChildToParentEvent() data object ProductsDialogInfoIconClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt index 98af275c508..ff7093dbf58 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt @@ -25,7 +25,6 @@ sealed class ParentToChildrenEvent { ) : ParentToChildrenEvent() data class CheckoutClicked(val productIds: List) : ParentToChildrenEvent() data object OrderSuccessfullyPaid : ParentToChildrenEvent() - data object OrderCardPaymentAborted : ParentToChildrenEvent() } interface WooPosParentToChildrenEventReceiver { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index d178a545bf0..8ef31e407d0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -106,18 +106,13 @@ class WooPosHomeViewModel @Inject constructor( ) } - is ChildToParentEvent.ExitOrderAfterFailedTransactionClicked -> { - _state.value = _state.value.copy( - screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible - ) - sendEventToChildren(ParentToChildrenEvent.OrderCardPaymentAborted) - } is ChildToParentEvent.NewTransactionClicked -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible ) sendEventToChildren(ParentToChildrenEvent.OrderSuccessfullyPaid) } + is ChildToParentEvent.PaymentProcessing, is ChildToParentEvent.OrderSuccessfullyPaid, is ChildToParentEvent.PaymentFailed -> { @@ -125,11 +120,14 @@ class WooPosHomeViewModel @Inject constructor( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals ) } + + is ChildToParentEvent.GoBackToCheckoutAfterFailedPayment, is ChildToParentEvent.RetryFailedPaymentClicked -> { _state.value = _state.value.copy( screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals ) } + ChildToParentEvent.ExitPosClicked -> { _state.value = _state.value.copy( exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = true) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt index 6beac3b6b17..d62010e71af 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt @@ -120,8 +120,7 @@ class WooPosCartViewModel @Inject constructor( is ParentToChildrenEvent.ItemClickedInProductSelector -> handleItemClickedInProductSelector(event) - is ParentToChildrenEvent.OrderSuccessfullyPaid, - is ParentToChildrenEvent.OrderCardPaymentAborted -> clearCart() + is ParentToChildrenEvent.OrderSuccessfullyPaid -> clearCart() is ParentToChildrenEvent.CheckoutClicked -> Unit } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt index 5540b3b69ac..7d4d7df6ad1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsUIEvent.kt @@ -3,6 +3,6 @@ package com.woocommerce.android.ui.woopos.home.totals sealed class WooPosTotalsUIEvent { data object OnNewTransactionClicked : WooPosTotalsUIEvent() data object RetryFailedTransactionClicked : WooPosTotalsUIEvent() - data object ExitOrderAfterFailedTransactionClicked : WooPosTotalsUIEvent() + data object GoBackToCheckoutAfterFailedPayment : WooPosTotalsUIEvent() data object RetryOrderCreationClicked : WooPosTotalsUIEvent() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 14767e9141a..2a5fe6fe4bb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -151,24 +151,43 @@ class WooPosTotalsViewModel @Inject constructor( is WooPosTotalsUIEvent.RetryOrderCreationClicked -> { createOrderDraft(dataState.value.productIds) } - WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked -> viewModelScope.launch { - childrenToParentEventSender.sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + WooPosTotalsUIEvent.GoBackToCheckoutAfterFailedPayment -> viewModelScope.launch { + childrenToParentEventSender.sendToParent(ChildToParentEvent.GoBackToCheckoutAfterFailedPayment) + retryPaymentCollectionFromScratch() } WooPosTotalsUIEvent.RetryFailedTransactionClicked -> viewModelScope.launch { - cancelPaymentAction() - childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) - val order = totalsRepository.getOrderById(dataState.value.orderId) - if (order == null) { - uiState.value = InitialState - childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) - } else { - uiState.value = buildWooPosTotalsViewState(order) - collectPayment() + val paymentState = cardReaderPaymentController?.paymentState?.value + check(paymentState != null) { + "Retry failed transaction clicked but payment controller is null" + } + check(paymentState is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment) { + "Retry failed transaction clicked but payment state is not PaymentFailed" + } + when { + paymentState.onRetry != null -> { + paymentState.onRetry!!() + } + else -> { + childrenToParentEventSender.sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) + retryPaymentCollectionFromScratch() + } } } } } + private suspend fun retryPaymentCollectionFromScratch() { + cancelPaymentAction() + val order = totalsRepository.getOrderById(dataState.value.orderId) + if (order == null) { + uiState.value = InitialState + childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + } else { + uiState.value = buildWooPosTotalsViewState(order) + collectPayment() + } + } + private fun collectPayment() { if (!networkStatus.isConnected()) { viewModelScope.launch { @@ -203,7 +222,6 @@ class WooPosTotalsViewModel @Inject constructor( } is ParentToChildrenEvent.ItemClickedInProductSelector, - ParentToChildrenEvent.OrderCardPaymentAborted, ParentToChildrenEvent.OrderSuccessfullyPaid -> Unit } } @@ -222,14 +240,15 @@ class WooPosTotalsViewModel @Inject constructor( when (paymentState) { is CardReaderPaymentState.CollectingPayment, - is CardReaderPaymentState.LoadingData -> { - } + is CardReaderPaymentState.LoadingData -> {} + is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, CardReaderPaymentState.ReFetchingOrder -> { uiState.value = buildPaymentProcessingState() childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentProcessing) } + is CardReaderPaymentState.PaymentSuccessful -> { uiState.value = PaymentSuccess( @@ -237,10 +256,12 @@ class WooPosTotalsViewModel @Inject constructor( ) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } + is CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment -> { uiState.value = buildPaymentFailedState(paymentState) childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed) } + is CardReaderPaymentOrRefundState.CardReaderInteracRefundState, is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment, is CardReaderPaymentState.PrintingReceipt, @@ -254,12 +275,22 @@ class WooPosTotalsViewModel @Inject constructor( private fun buildPaymentFailedState( state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment - ): PaymentFailed = PaymentFailed( - title = resourceProvider.getString( - R.string.woopos_success_totals_payment_failed_title - ), - subtitle = uiStringParser.asString(state.errorType.message) - ) + ): PaymentFailed { + val isRetryAvailable = state.onRetry != null + val retryButtonLabel = if (isRetryAvailable) { + resourceProvider.getString(R.string.woo_pos_payment_failed_try_again) + } else { + resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method) + } + return PaymentFailed( + title = resourceProvider.getString( + R.string.woopos_success_totals_payment_failed_title + ), + subtitle = uiStringParser.asString(state.errorType.message), + retryPaymentButtonLabel = retryButtonLabel, + isReturnToCheckoutButtonVisible = isRetryAvailable + ) + } private fun buildPaymentProcessingState(): PaymentProcessing = PaymentProcessing( title = resourceProvider.getString( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index 9ee0747f8fa..17feee449ed 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -31,6 +31,8 @@ sealed class WooPosTotalsViewState : Parcelable { data class PaymentFailed( val title: String, val subtitle: String, + val retryPaymentButtonLabel: String, + val isReturnToCheckoutButtonVisible: Boolean = false, ) : WooPosTotalsViewState() data class PaymentSuccess(var orderTotalText: String) : WooPosTotalsViewState() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt index 80f32ae1de1..49cc6dfea5d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/failed/WooPosTotalsPaymentFailedScreen.kt @@ -60,25 +60,27 @@ fun WooPosPaymentFailedScreen( ) Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) WooPosButton( - text = stringResource(R.string.woo_pos_payment_failed_try_another_payment_method), + text = state.retryPaymentButtonLabel, modifier = Modifier .height(80.dp) .width(604.dp) ) { onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) } - Spacer(modifier = Modifier.height(24.dp.toAdaptivePadding())) - WooPosOutlinedButton( - modifier = Modifier - .height(80.dp) - .width(604.dp), - content = { - Text( - color = MaterialTheme.colors.primary, - style = MaterialTheme.typography.h5, - fontWeight = FontWeight.Bold, - text = stringResource(R.string.woo_pos_payment_failed_exit_order) - ) - } - ) { onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) } + if (state.isReturnToCheckoutButtonVisible) { + Spacer(modifier = Modifier.height(24.dp.toAdaptivePadding())) + WooPosOutlinedButton( + modifier = Modifier + .height(80.dp) + .width(604.dp), + content = { + Text( + color = MaterialTheme.colors.primary, + style = MaterialTheme.typography.h5, + fontWeight = FontWeight.Bold, + text = stringResource(R.string.woo_pos_payment_failed_go_back_to_checkout) + ) + } + ) { onUIEvent(WooPosTotalsUIEvent.GoBackToCheckoutAfterFailedPayment) } + } Spacer(modifier = Modifier.height(80.dp.toAdaptivePadding())) } } @@ -91,6 +93,8 @@ fun WooPosPaymentFailedScreenPreview() { state = WooPosTotalsViewState.PaymentFailed( title = "Payment failed", subtitle = "Unfortunately, this payment has been declined.", + retryPaymentButtonLabel = "Try again", + isReturnToCheckoutButtonVisible = true, ), onUIEvent = {} ) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index b48e3a59659..0a84a88541d 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4304,7 +4304,9 @@ Processing payment Please wait… Payment failed - Unfortunately, this payment has been declined. + Try another payment method + Try payment again + Exit order Dimmed background. Tap to close the menu. Card reader connected @@ -4390,6 +4392,4 @@ Box Envelope Hmm, we can\'t find a WordPress.com account connected to this email address. - Try another payment method - Exit order diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index a1d8d7d316b..e19c12dee6b 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -274,7 +274,7 @@ class WooPosHomeViewModelTest { } @Test - fun `given home screen is at checkout, when exit order clicked after failed payment, then should show cart`() = runTest { + fun `given home screen is at checkout, when go back to checkout clicked after failed payment, then should show cart with totals`() = runTest { // GIVEN val events = MutableSharedFlow() whenever(childrenToParentEventReceiver.events).thenReturn(events) @@ -286,12 +286,12 @@ class WooPosHomeViewModelTest { ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) // WHEN - events.emit(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + events.emit(ChildToParentEvent.GoBackToCheckoutAfterFailedPayment) // THEN assertThat( viewModel.state.value.screenPositionState - ).isEqualTo(WooPosHomeState.ScreenPositionState.Cart.Visible) + ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) } @Test diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt index d9eb2c2e4b1..54784e1f829 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModelTest.kt @@ -513,37 +513,6 @@ class WooPosCartViewModelTest { assertThat(finalItem.isAppearanceAnimationPlayed).isTrue } - @Test - fun `given non-empty cart, when card payment is aborted, then should clear the cart`() = runTest { - // GIVEN - val product = ProductTestUtils.generateProduct( - productId = 23L, - productName = "title", - amount = "10.0" - ).copy(firstImageUrl = "url") - - val parentToChildrenEventsMutableFlow = MutableSharedFlow() - whenever(parentToChildrenEventReceiver.events).thenReturn(parentToChildrenEventsMutableFlow) - whenever(getProductById(eq(product.remoteId))).thenReturn(product) - val sut = createSut() - val states = sut.state.captureValues() - - parentToChildrenEventsMutableFlow.emit( - ParentToChildrenEvent.ItemClickedInProductSelector( - WooPosItemsViewModel.ItemClickedData.SimpleProduct(id = product.remoteId) - ) - ) - - // WHEN - parentToChildrenEventsMutableFlow.emit(ParentToChildrenEvent.OrderCardPaymentAborted) - - // THEN - val toolbar = states.last().toolbar - assertThat(toolbar.backIconVisible).isFalse() - assertThat(toolbar.itemsCount).isNull() - assertThat(toolbar.isClearAllButtonVisible).isFalse() - } - private fun createSut(): WooPosCartViewModel { return WooPosCartViewModel( childrenToParentEventSender, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index ec8dda7c0e9..f020cdcd169 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -43,6 +43,7 @@ import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.UiStringParser import com.woocommerce.android.viewmodel.ResourceProvider +import junit.framework.TestCase.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -55,6 +56,7 @@ import org.junit.Before import org.junit.Rule import org.mockito.kotlin.any import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -288,10 +290,7 @@ class WooPosTotalsViewModelTest { val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { on { events }.thenReturn(mock()) } - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) - .thenReturn("Payment failed") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_subtitle)) - .thenReturn("Unfortunately, this payment has been declined.") + val savedState = createMockSavedStateHandle() val viewModel = createViewModel( @@ -834,7 +833,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when retry clicked, then should retry`() = runTest { + fun `given payment failed with retry action, when retry clicked, then should retry previous payment action`() = runTest { // GIVEN whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) .thenReturn("Processing payment") @@ -843,6 +842,8 @@ class WooPosTotalsViewModelTest { whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) .thenReturn("Payment failed") whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") + whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_again)) + .thenReturn("Try payment again") whenever(networkStatus.isConnected()).thenReturn(true) val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) @@ -857,17 +858,98 @@ class WooPosTotalsViewModelTest { whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + val failedPaymentRetryAction: ()->Unit = mock() paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( - errorType = PaymentFlowError.NoNetwork, {} + errorType = PaymentFlowError.NoNetwork, failedPaymentRetryAction + ) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry != null) + + // WHEN + vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) + + // THEN + verify(failedPaymentRetryAction).invoke() + } + + @Test + fun `given payment failed without retry action, when retry clicked, then should cancel previous payment action and start again`() = + runTest { + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") + whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method)) + .thenReturn("Try another payment method") + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.Cancelable( + errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" + ) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null) + + // WHEN + clearInvocations(mockCardReaderPaymentController) + vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) + + // THEN + verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).start() + } + + @Test + fun `given payment failed without retry action, when retry clicked, then should go back to checkout`() = runTest { + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method)) + .thenReturn("Try another payment method") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") + + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.Cancelable( + errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null) // WHEN - paymentState.value = CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) // THEN - assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.Totals::class.java) + verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.RetryFailedPaymentClicked) } @Test @@ -879,6 +961,8 @@ class WooPosTotalsViewModelTest { .thenReturn("Please wait…") whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) .thenReturn("Payment failed") + whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_again)) + .thenReturn("Try payment again") whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") whenever(networkStatus.isConnected()).thenReturn(true) @@ -900,10 +984,10 @@ class WooPosTotalsViewModelTest { assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) // WHEN - vm.onUIEvent(WooPosTotalsUIEvent.ExitOrderAfterFailedTransactionClicked) + vm.onUIEvent(WooPosTotalsUIEvent.GoBackToCheckoutAfterFailedPayment) // THEN - verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.ExitOrderAfterFailedTransactionClicked) + verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.GoBackToCheckoutAfterFailedPayment) } private suspend fun createViewModelAndSetupForSuccessfulOrderCreation( From 6c350506e876fb9c64dbcb43b1a777e8e87d2f06 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 15:46:38 +0100 Subject: [PATCH 095/132] Satisfy detekt's complaints --- .../home/totals/WooPosTotalsViewModel.kt | 4 +- .../home/totals/WooPosTotalsViewModelTest.kt | 76 ++++++++++--------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 2a5fe6fe4bb..51f52f8edbb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -278,9 +278,9 @@ class WooPosTotalsViewModel @Inject constructor( ): PaymentFailed { val isRetryAvailable = state.onRetry != null val retryButtonLabel = if (isRetryAvailable) { - resourceProvider.getString(R.string.woo_pos_payment_failed_try_again) + resourceProvider.getString(R.string.woo_pos_payment_failed_try_again) } else { - resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method) + resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method) } return PaymentFailed( title = resourceProvider.getString( diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index f020cdcd169..576eda44b61 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -858,12 +858,14 @@ class WooPosTotalsViewModelTest { whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} - val failedPaymentRetryAction: ()->Unit = mock() + val failedPaymentRetryAction: () -> Unit = mock() paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.NonCancelable( errorType = PaymentFlowError.NoNetwork, failedPaymentRetryAction ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) - assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry != null) + assertTrue( + (paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry != null + ) // WHEN vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) @@ -875,43 +877,45 @@ class WooPosTotalsViewModelTest { @Test fun `given payment failed without retry action, when retry clicked, then should cancel previous payment action and start again`() = runTest { - // GIVEN - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) - .thenReturn("Processing payment") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) - .thenReturn("Please wait…") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) - .thenReturn("Payment failed") - whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") + // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") + whenever(uiStringParser.asString(any())).thenReturn("Unfortunately, this payment has been declined.") whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_another_payment_method)) .thenReturn("Try another payment method") - whenever(networkStatus.isConnected()).thenReturn(true) - val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) - whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) - val mockCardReaderPaymentController: CardReaderPaymentController = mock() - val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) - val paymentState = - MutableStateFlow( - CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.Cancelable( + errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" + ) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) + assertTrue( + (paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null ) - whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) - val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) - paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} - paymentState.value = CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment.Cancelable( - errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" - ) - assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) - assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null) - // WHEN - clearInvocations(mockCardReaderPaymentController) - vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) + // WHEN + clearInvocations(mockCardReaderPaymentController) + vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) - // THEN - verify(mockCardReaderPaymentController).onCleared() - verify(mockCardReaderPaymentController).start() - } + // THEN + verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).start() + } @Test fun `given payment failed without retry action, when retry clicked, then should go back to checkout`() = runTest { @@ -943,7 +947,9 @@ class WooPosTotalsViewModelTest { errorType = PaymentFlowError.NoNetwork, onRetry = null, onCancel = {}, amountWithCurrencyLabel = "" ) assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentFailed::class.java) - assertTrue((paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null) + assertTrue( + (paymentState.value as CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment).onRetry == null + ) // WHEN vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) From 6c2a7e7ef4050681cdf6b69a1c04f641f749776e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 15:54:30 +0100 Subject: [PATCH 096/132] Update string value Exit order -> Go back to checkout --- WooCommerce/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 0a84a88541d..47dbd4d9931 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4306,7 +4306,7 @@ Payment failed Try another payment method Try payment again - Exit order + Go back to checkout Dimmed background. Tap to close the menu. Card reader connected From 8040ff94a7058f5ccd15d31f8224d2c6b271891e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 17:13:10 +0100 Subject: [PATCH 097/132] Handle PaymentCollecting payment state during retry --- .../WooPosHomeChildToParentCommunication.kt | 1 + .../ui/woopos/home/WooPosHomeViewModel.kt | 6 +++ .../home/totals/WooPosTotalsViewModel.kt | 41 +++++++++++++------ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index 39bd1d75405..b43c0f2f298 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -23,6 +23,7 @@ sealed class ChildToParentEvent { data object BackFromCheckoutToCartClicked : ChildToParentEvent() data class ItemClickedInProductSelector(val itemData: WooPosItemsViewModel.ItemClickedData) : ChildToParentEvent() data object NewTransactionClicked : ChildToParentEvent() + data object PaymentCollecting : ChildToParentEvent() data object PaymentProcessing : ChildToParentEvent() data object PaymentFailed : ChildToParentEvent() data object RetryFailedPaymentClicked : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index 8ef31e407d0..2b4df204a94 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -113,6 +113,12 @@ class WooPosHomeViewModel @Inject constructor( sendEventToChildren(ParentToChildrenEvent.OrderSuccessfullyPaid) } + is ChildToParentEvent.PaymentCollecting -> { + _state.value = _state.value.copy( + screenPositionState = WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals + ) + } + is ChildToParentEvent.PaymentProcessing, is ChildToParentEvent.OrderSuccessfullyPaid, is ChildToParentEvent.PaymentFailed -> { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 51f52f8edbb..8d2b42e5023 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -180,8 +180,7 @@ class WooPosTotalsViewModel @Inject constructor( cancelPaymentAction() val order = totalsRepository.getOrderById(dataState.value.orderId) if (order == null) { - uiState.value = InitialState - childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + returnToCart() } else { uiState.value = buildWooPosTotalsViewState(order) collectPayment() @@ -231,16 +230,9 @@ class WooPosTotalsViewModel @Inject constructor( private fun listenToPaymentState() { viewModelScope.launch { cardReaderPaymentController?.paymentState?.collect { paymentState -> - val totalsState = uiState.value - if (totalsState is WooPosTotalsViewState.Totals) { - uiState.value = totalsState.copy( - paymentStateText = paymentState.javaClass.simpleName - ) - } - when (paymentState) { is CardReaderPaymentState.CollectingPayment, - is CardReaderPaymentState.LoadingData -> {} + is CardReaderPaymentState.LoadingData -> handlePaymentState(paymentState) is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, @@ -273,6 +265,28 @@ class WooPosTotalsViewModel @Inject constructor( } } + private suspend fun handlePaymentState(paymentState: CardReaderPaymentOrRefundState) { + val totalsState = uiState.value + if (totalsState is WooPosTotalsViewState.Totals) { + uiState.value = totalsState.copy( + paymentStateText = paymentState.javaClass.simpleName + ) + } else { + val order = totalsRepository.getOrderById(dataState.value.orderId) + if (order == null) { + returnToCart() + } else { + uiState.value = + buildWooPosTotalsViewState(order, paymentState as CardReaderPaymentState) + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) + } + } + } + + private suspend fun returnToCart() { + childrenToParentEventSender.sendToParent(ChildToParentEvent.BackFromCheckoutToCartClicked) + } + private fun buildPaymentFailedState( state: CardReaderPaymentState.PaymentFailed.ExternalReaderFailedPayment ): PaymentFailed { @@ -330,7 +344,10 @@ class WooPosTotalsViewModel @Inject constructor( } } - private suspend fun buildWooPosTotalsViewState(order: Order): WooPosTotalsViewState.Totals { + private suspend fun buildWooPosTotalsViewState( + order: Order, + paymentState: CardReaderPaymentState? = null + ): WooPosTotalsViewState.Totals { val subtotalAmount = order.productsTotal val taxAmount = order.totalTax val totalAmount = order.total @@ -343,7 +360,7 @@ class WooPosTotalsViewModel @Inject constructor( orderSubtotalText = priceFormat(subtotalAmount), orderTaxText = priceFormat(taxAmount), orderTotalText = priceFormat(totalAmount), - paymentStateText = "", + paymentStateText = paymentState?.javaClass?.simpleName ?: "", error = error ) } From 55925b29cfc82e790771cb6ac8f732a7886f4a79 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 17:25:00 +0100 Subject: [PATCH 098/132] Handle `PaymentCollecting` payment state received during retry --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 8d2b42e5023..c586e619b95 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -232,7 +232,8 @@ class WooPosTotalsViewModel @Inject constructor( cardReaderPaymentController?.paymentState?.collect { paymentState -> when (paymentState) { is CardReaderPaymentState.CollectingPayment, - is CardReaderPaymentState.LoadingData -> handlePaymentState(paymentState) + is CardReaderPaymentState.LoadingData -> + handlePaymentState(paymentState as CardReaderPaymentState) is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, @@ -265,7 +266,7 @@ class WooPosTotalsViewModel @Inject constructor( } } - private suspend fun handlePaymentState(paymentState: CardReaderPaymentOrRefundState) { + private suspend fun handlePaymentState(paymentState: CardReaderPaymentState) { val totalsState = uiState.value if (totalsState is WooPosTotalsViewState.Totals) { uiState.value = totalsState.copy( From 2703d6fde04395b6072e482ea43f250919a76035 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 6 Dec 2024 17:32:36 +0100 Subject: [PATCH 099/132] Clean up code --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 2 +- .../android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index c586e619b95..aaf8fe9427e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -278,7 +278,7 @@ class WooPosTotalsViewModel @Inject constructor( returnToCart() } else { uiState.value = - buildWooPosTotalsViewState(order, paymentState as CardReaderPaymentState) + buildWooPosTotalsViewState(order, paymentState) childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 576eda44b61..5719e004d94 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -959,7 +959,7 @@ class WooPosTotalsViewModelTest { } @Test - fun `given payment failed, when exit order clicked, then should inform home about the situation`() = runTest { + fun `given payment failed, when go back to checkout clicked, then should inform home about the situation`() = runTest { // GIVEN whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) .thenReturn("Processing payment") From 220a050505af277841459977ceda699e2e47dc6c Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 9 Dec 2024 09:26:20 +0100 Subject: [PATCH 100/132] Implement basic UI for reader initialization states --- ...est.kt => WooPosDisconnectedScreenTest.kt} | 2 +- .../woopos/home/totals/WooPosTotalsScreen.kt | 71 +++++++++++++------ .../home/totals/WooPosTotalsViewModel.kt | 56 +++++++++++---- .../home/totals/WooPosTotalsViewState.kt | 48 +++++++++++-- .../drawable/woopos_ic_collect_payment.xml | 22 ++++++ WooCommerce/src/main/res/values/strings.xml | 2 + .../home/totals/WooPosTotalsViewModelTest.kt | 13 ++-- 7 files changed, 163 insertions(+), 51 deletions(-) rename WooCommerce/src/androidTest/kotlin/com/woocommerce/android/{WooPosErrorScreenTest.kt => WooPosDisconnectedScreenTest.kt} (98%) create mode 100644 WooCommerce/src/main/res/drawable/woopos_ic_collect_payment.xml diff --git a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosErrorScreenTest.kt b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosDisconnectedScreenTest.kt similarity index 98% rename from WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosErrorScreenTest.kt rename to WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosDisconnectedScreenTest.kt index bc9804292fd..5fb472286c1 100644 --- a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosErrorScreenTest.kt +++ b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosDisconnectedScreenTest.kt @@ -17,7 +17,7 @@ import org.junit.runner.RunWith @HiltAndroidTest @RunWith(AndroidJUnit4::class) -class WooPosErrorScreenTest { +class WooPosDisconnectedScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index f1687b010cc..c66a9bebdbe 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -2,7 +2,6 @@ package com.woocommerce.android.ui.woopos.home.totals import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background @@ -42,6 +41,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.component.Button import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosCircularLoadingIndicator import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosErrorScreen import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding @@ -120,7 +120,6 @@ private fun StateChangeAnimated( ) } -@OptIn(ExperimentalAnimationApi::class) @Composable private fun TotalsLoaded( state: WooPosTotalsViewState.Totals, @@ -133,20 +132,46 @@ private fun TotalsLoaded( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Box( + Column( modifier = Modifier .fillMaxWidth() .weight(1.1f) - .background(WooPosTheme.colors.totalsErrorBackground) + .background(WooPosTheme.colors.totalsErrorBackground), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { - val error = state.error - when { - error != null -> TotalsError(modifier = Modifier, error = error) - else -> { + when(val readerStatus = state.readerStatus) { + is WooPosTotalsViewState.ReaderStatus.Disconnected -> { + ReaderDisconnected(modifier = Modifier, status = readerStatus) + } + is WooPosTotalsViewState.ReaderStatus.Preparing, + is WooPosTotalsViewState.ReaderStatus.CheckingOrder -> { + WooPosCircularLoadingIndicator(modifier = Modifier.size(64.dp)) Text( - modifier = Modifier.align(Alignment.Center), - text = state.paymentStateText, - style = MaterialTheme.typography.body1, + text = readerStatus.title, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.SemiBold + ) + Text( + text = readerStatus.subtitle, + style = MaterialTheme.typography.h2 + ) + } + is WooPosTotalsViewState.ReaderStatus.ReadyForPayment -> { + Icon( + modifier = Modifier.size(64.dp), + painter = painterResource(id = R.drawable.woopos_ic_collect_payment), + contentDescription = "Collect Payment", + tint = Color.Unspecified, + ) + Text( + text = readerStatus.title, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.SemiBold + ) + Text( + text = readerStatus.subtitle, + style = MaterialTheme.typography.h2 ) } } @@ -156,9 +181,9 @@ private fun TotalsLoaded( } @Composable -private fun TotalsError( +private fun ReaderDisconnected( modifier: Modifier = Modifier, - error: WooPosTotalsViewState.Totals.Error + status: WooPosTotalsViewState.ReaderStatus.Disconnected ) { Column( modifier = modifier.padding(40.dp.toAdaptivePadding()), @@ -177,7 +202,7 @@ private fun TotalsError( Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) Text( - text = error.title, + text = status.title, style = MaterialTheme.typography.h4, fontWeight = FontWeight.SemiBold ) @@ -185,13 +210,13 @@ private fun TotalsError( Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) Text( - text = error.subtitle, + text = status.subtitle, style = MaterialTheme.typography.h6 ) Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding())) WooPosButton( - text = error.actionButonLabel, - onClick = error.onAction, + text = status.actionButonLabel, + onClick = status.onAction, modifier = Modifier .fillMaxWidth() .height(80.dp) @@ -332,7 +357,10 @@ fun WooPosTotalsScreenPreview(modifier: Modifier = Modifier) { orderSubtotalText = "$420.00", orderTotalText = "$462.00", orderTaxText = "$42.00", - paymentStateText = "Payment state" + readerStatus = WooPosTotalsViewState.ReaderStatus.ReadyForPayment( + title = "Ready for payment", + subtitle = "Tap, swipe or insert card" + ) ), onUIEvent = {} ) @@ -349,8 +377,7 @@ fun WooPosTotalsScreenPreviewReaderNotConnected(modifier: Modifier = Modifier) { orderSubtotalText = "$420.00", orderTotalText = "$462.00", orderTaxText = "$42.00", - paymentStateText = "Payment state", - error = WooPosTotalsViewState.Totals.Error( + readerStatus = WooPosTotalsViewState.ReaderStatus.Disconnected( title = "Reader not connected", subtitle = "To process this payment, please connect your reader.", actionButonLabel = "Connect to a reader", @@ -365,14 +392,14 @@ fun WooPosTotalsScreenPreviewReaderNotConnected(modifier: Modifier = Modifier) { @Composable @WooPosPreview fun TotalsErrorPreview() { - val error = WooPosTotalsViewState.Totals.Error( + val readerStatus = WooPosTotalsViewState.ReaderStatus.Disconnected( title = "Reader not connected", subtitle = "To process this payment, please connect your reader.", actionButonLabel = "Connect to a reader", onAction = {} ) WooPosTheme { - TotalsError(modifier = Modifier, error = error) + ReaderDisconnected(modifier = Modifier, status = readerStatus) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index aaf8fe9427e..5d90a436350 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -117,13 +117,13 @@ class WooPosTotalsViewModel @Inject constructor( is NotConnected, is Connecting -> { val state = uiState.value if (state !is WooPosTotalsViewState.Totals) return@collect - uiState.value = state.copy(error = buildTotalsReaderNotConnectedError()) + uiState.value = state.copy(readerStatus = buildTotalsReaderNotConnectedError()) cancelPaymentAction() } is Connected -> { val state = uiState.value if (state !is WooPosTotalsViewState.Totals) return@collect - uiState.value = state.copy(error = null) + uiState.value = state.copy(readerStatus = buildPreparingReaderStatusState()) if (data.orderId != EMPTY_ORDER_ID) { collectPayment() } @@ -133,6 +133,11 @@ class WooPosTotalsViewModel @Inject constructor( } } + private fun buildPreparingReaderStatusState() = WooPosTotalsViewState.ReaderStatus.Preparing( + title = resourceProvider.getString(R.string.woopos_totals_reader_getting_ready), + subtitle = resourceProvider.getString(R.string.woopos_totals_reader_checking_order) + ) + private fun cancelPaymentAction() { cardReaderPaymentController?.onCleared() cardReaderPaymentController?.onBackPressed() @@ -231,9 +236,9 @@ class WooPosTotalsViewModel @Inject constructor( viewModelScope.launch { cardReaderPaymentController?.paymentState?.collect { paymentState -> when (paymentState) { - is CardReaderPaymentState.CollectingPayment, + is CardReaderPaymentState.CollectingPayment -> handleCollectingPaymentState() is CardReaderPaymentState.LoadingData -> - handlePaymentState(paymentState as CardReaderPaymentState) + handleReaderLoadingPaymentState() is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, @@ -266,19 +271,42 @@ class WooPosTotalsViewModel @Inject constructor( } } - private suspend fun handlePaymentState(paymentState: CardReaderPaymentState) { + private suspend fun handleCollectingPaymentState() { + val totalsState = uiState.value + if (totalsState is WooPosTotalsViewState.Totals) { + uiState.value = totalsState.copy( + readerStatus = WooPosTotalsViewState.ReaderStatus.ReadyForPayment( + title = "Ready for payment", + subtitle = "Tap, swipe or insert card" + ) + ) + } else { + val order = totalsRepository.getOrderById(dataState.value.orderId) + if (order == null) { + returnToCart() + } else { + uiState.value = buildWooPosTotalsViewState(order) + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) + } + } + } + + private suspend fun handleReaderLoadingPaymentState() { val totalsState = uiState.value if (totalsState is WooPosTotalsViewState.Totals) { uiState.value = totalsState.copy( - paymentStateText = paymentState.javaClass.simpleName + readerStatus = + WooPosTotalsViewState.ReaderStatus.Preparing( + title = "Getting ready", + subtitle = "Preparing reader for payment" + ) ) } else { val order = totalsRepository.getOrderById(dataState.value.orderId) if (order == null) { returnToCart() } else { - uiState.value = - buildWooPosTotalsViewState(order, paymentState) + uiState.value = buildWooPosTotalsViewState(order) childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) } } @@ -347,13 +375,12 @@ class WooPosTotalsViewModel @Inject constructor( private suspend fun buildWooPosTotalsViewState( order: Order, - paymentState: CardReaderPaymentState? = null ): WooPosTotalsViewState.Totals { val subtotalAmount = order.productsTotal val taxAmount = order.totalTax val totalAmount = order.total - val error = when (cardReaderFacade.readerStatus.value) { - is Connected -> null + val readerStatus = when (cardReaderFacade.readerStatus.value) { + is Connected -> buildPreparingReaderStatusState() else -> buildTotalsReaderNotConnectedError() } @@ -361,13 +388,12 @@ class WooPosTotalsViewModel @Inject constructor( orderSubtotalText = priceFormat(subtotalAmount), orderTaxText = priceFormat(taxAmount), orderTotalText = priceFormat(totalAmount), - paymentStateText = paymentState?.javaClass?.simpleName ?: "", - error = error + readerStatus = readerStatus ) } - private fun buildTotalsReaderNotConnectedError(): WooPosTotalsViewState.Totals.Error = - WooPosTotalsViewState.Totals.Error( + private fun buildTotalsReaderNotConnectedError(): WooPosTotalsViewState.ReaderStatus.Disconnected = + WooPosTotalsViewState.ReaderStatus.Disconnected( title = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_title), subtitle = resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_subtitle), actionButonLabel = resourceProvider.getString( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index 17feee449ed..e1ec5f64c13 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -11,16 +11,50 @@ sealed class WooPosTotalsViewState : Parcelable { val orderSubtotalText: String, val orderTaxText: String, val orderTotalText: String, - val paymentStateText: String, - val error: Error? = null, - ) : WooPosTotalsViewState() { + val readerStatus: ReaderStatus, + ) : WooPosTotalsViewState() + + sealed class ReaderStatus( + open val title: String, + open val subtitle: String, + ) : Parcelable { + @Parcelize + data class Preparing( + override val title: String, + override val subtitle: String, + ) : ReaderStatus( + title = title, + subtitle = subtitle + ) + + @Parcelize + data class CheckingOrder( + override val title: String, + override val subtitle: String, + ) : ReaderStatus( + title = title, + subtitle = subtitle + ) + + @Parcelize + data class ReadyForPayment( + override val title: String, + override val subtitle: String, + ) : ReaderStatus( + title = title, + subtitle = subtitle + ) + @Parcelize - data class Error( - val title: String, - val subtitle: String, + data class Disconnected( + override val title: String, + override val subtitle: String, val actionButonLabel: String, val onAction: () -> Unit, - ) : Parcelable + ) : ReaderStatus( + title = title, + subtitle = subtitle + ) } data class PaymentProcessing( diff --git a/WooCommerce/src/main/res/drawable/woopos_ic_collect_payment.xml b/WooCommerce/src/main/res/drawable/woopos_ic_collect_payment.xml new file mode 100644 index 00000000000..e5a6f87477c --- /dev/null +++ b/WooCommerce/src/main/res/drawable/woopos_ic_collect_payment.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 47dbd4d9931..c94bb6278d8 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4300,6 +4300,8 @@ Reader not connected To process this payment, please connect your reader. Connect to reader + Getting ready + Checking order Processing payment Please wait… diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 5719e004d94..902e7a849a1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -550,7 +550,7 @@ class WooPosTotalsViewModelTest { // THEN val state = viewModel.state.value as WooPosTotalsViewState.Totals assertThat(state.orderTotalText).isEqualTo("$3.00") - assertThat(state.paymentStateText).isNotNull() + assertThat(state.readerStatus).isInstanceOf(WooPosTotalsViewState.ReaderStatus.ReadyForPayment::class.java) } @org.junit.Test @@ -675,8 +675,8 @@ class WooPosTotalsViewModelTest { // THEN assertThat(viewModel.state.value).isInstanceOf(WooPosTotalsViewState.Totals::class.java) val state = viewModel.state.value as WooPosTotalsViewState.Totals - assertThat(state.error).isNotNull() - with(state.error!!) { + assertThat(state.readerStatus).isNotNull() + with(state.readerStatus as WooPosTotalsViewState.ReaderStatus.Disconnected) { assertThat(title).isEqualTo("Reader not connected") assertThat(subtitle).isEqualTo("To process this payment, please connect your reader.") assertThat(actionButonLabel).isEqualTo("Connect to reader") @@ -741,7 +741,7 @@ class WooPosTotalsViewModelTest { // THEN assertThat(viewModel.state.value).isInstanceOf(WooPosTotalsViewState.Totals::class.java) val state = viewModel.state.value as WooPosTotalsViewState.Totals - assertThat(state.error).isNull() + assertThat(state.readerStatus).isInstanceOf(WooPosTotalsViewState.ReaderStatus.ReadyForPayment::class.java) } @Test @@ -753,8 +753,9 @@ class WooPosTotalsViewModelTest { // WHEN val viewModel = createViewModelAndSetupForSuccessfulOrderCreation() - - (viewModel.state.value as WooPosTotalsViewState.Totals).error!!.onAction() + assertThat(viewModel.state.value is WooPosTotalsViewState.Totals).isTrue() + val state = viewModel.state.value as WooPosTotalsViewState.Totals + (state.readerStatus as WooPosTotalsViewState.ReaderStatus.Disconnected).onAction() // THEN verify(cardReaderFacade).connectToReader() From b3cff58a0f063c00eb285c1ee7e9f6dd9c1d3418 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 9 Dec 2024 09:55:02 +0100 Subject: [PATCH 101/132] Tune up the UI --- .../woopos/home/totals/WooPosTotalsScreen.kt | 86 +++++++++++++------ 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index c66a9bebdbe..f68ade19e49 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -146,33 +146,10 @@ private fun TotalsLoaded( } is WooPosTotalsViewState.ReaderStatus.Preparing, is WooPosTotalsViewState.ReaderStatus.CheckingOrder -> { - WooPosCircularLoadingIndicator(modifier = Modifier.size(64.dp)) - Text( - text = readerStatus.title, - style = MaterialTheme.typography.h4, - fontWeight = FontWeight.SemiBold - ) - Text( - text = readerStatus.subtitle, - style = MaterialTheme.typography.h2 - ) + PreparingReader(readerStatus) } is WooPosTotalsViewState.ReaderStatus.ReadyForPayment -> { - Icon( - modifier = Modifier.size(64.dp), - painter = painterResource(id = R.drawable.woopos_ic_collect_payment), - contentDescription = "Collect Payment", - tint = Color.Unspecified, - ) - Text( - text = readerStatus.title, - style = MaterialTheme.typography.h4, - fontWeight = FontWeight.SemiBold - ) - Text( - text = readerStatus.subtitle, - style = MaterialTheme.typography.h2 - ) + ReaderReadyForPayment(readerStatus) } } } @@ -180,6 +157,45 @@ private fun TotalsLoaded( } } +@Composable +private fun PreparingReader(readerStatus: WooPosTotalsViewState.ReaderStatus) { + WooPosCircularLoadingIndicator(modifier = Modifier.size(156.dp)) + Spacer(modifier = Modifier.height(20.dp.toAdaptivePadding())) + Text( + text = readerStatus.title, + style = MaterialTheme.typography.h5, + fontWeight = FontWeight.Medium + ) + Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) + Text( + text = readerStatus.subtitle, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.Bold + ) +} + +@Composable +private fun ReaderReadyForPayment(readerStatus: WooPosTotalsViewState.ReaderStatus) { + Icon( + modifier = Modifier.size(164.dp), + painter = painterResource(id = R.drawable.woopos_ic_collect_payment), + contentDescription = "Collect Payment", + tint = Color.Unspecified, + ) + Spacer(modifier = Modifier.height(20.dp.toAdaptivePadding())) + Text( + text = readerStatus.title, + style = MaterialTheme.typography.h5, + fontWeight = FontWeight.Medium + ) + Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) + Text( + text = readerStatus.subtitle, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.Bold + ) +} + @Composable private fun ReaderDisconnected( modifier: Modifier = Modifier, @@ -424,3 +440,23 @@ fun WooPosTotalsErrorScreenPreview() { ) } } + +@Composable +@WooPosPreview +fun PreparingReaderPReview() { + val readerStatus = WooPosTotalsViewState.ReaderStatus.Preparing( + title = "Getting ready", + subtitle = "Preparing reader for payment" + ) + WooPosTheme { + Column( + modifier = Modifier + .fillMaxWidth() + .background(WooPosTheme.colors.totalsErrorBackground), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + PreparingReader(readerStatus) + } + } +} From 9387be6ffc1a79315cb7ce01ada349f21bccf186 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 9 Dec 2024 10:02:18 +0100 Subject: [PATCH 102/132] Satisfy detekt's complaints --- .../ui/woopos/home/totals/WooPosTotalsScreen.kt | 2 +- .../woopos/home/totals/WooPosTotalsViewModel.kt | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index f68ade19e49..8d0086c929c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -140,7 +140,7 @@ private fun TotalsLoaded( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - when(val readerStatus = state.readerStatus) { + when (val readerStatus = state.readerStatus) { is WooPosTotalsViewState.ReaderStatus.Disconnected -> { ReaderDisconnected(modifier = Modifier, status = readerStatus) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 5d90a436350..bdcee459c1a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -275,10 +275,10 @@ class WooPosTotalsViewModel @Inject constructor( val totalsState = uiState.value if (totalsState is WooPosTotalsViewState.Totals) { uiState.value = totalsState.copy( - readerStatus = WooPosTotalsViewState.ReaderStatus.ReadyForPayment( - title = "Ready for payment", - subtitle = "Tap, swipe or insert card" - ) + readerStatus = WooPosTotalsViewState.ReaderStatus.ReadyForPayment( + title = "Ready for payment", + subtitle = "Tap, swipe or insert card" + ) ) } else { val order = totalsRepository.getOrderById(dataState.value.orderId) @@ -296,10 +296,10 @@ class WooPosTotalsViewModel @Inject constructor( if (totalsState is WooPosTotalsViewState.Totals) { uiState.value = totalsState.copy( readerStatus = - WooPosTotalsViewState.ReaderStatus.Preparing( - title = "Getting ready", - subtitle = "Preparing reader for payment" - ) + WooPosTotalsViewState.ReaderStatus.Preparing( + title = "Getting ready", + subtitle = "Preparing reader for payment" + ) ) } else { val order = totalsRepository.getOrderById(dataState.value.orderId) From 84342ed1a45118526598600e8c1f81e8387324c5 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 9 Dec 2024 10:16:49 +0100 Subject: [PATCH 103/132] Revert mistaken rename --- .../com/woocommerce/android/WooPosDisconnectedScreenTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosDisconnectedScreenTest.kt b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosDisconnectedScreenTest.kt index 5fb472286c1..bc9804292fd 100644 --- a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosDisconnectedScreenTest.kt +++ b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosDisconnectedScreenTest.kt @@ -17,7 +17,7 @@ import org.junit.runner.RunWith @HiltAndroidTest @RunWith(AndroidJUnit4::class) -class WooPosDisconnectedScreenTest { +class WooPosErrorScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) From 11cf2c2e2a0da73b5a1872c7282e4db2f748042f Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 9 Dec 2024 10:17:26 +0100 Subject: [PATCH 104/132] Revert mistaken rename --- .../{WooPosDisconnectedScreenTest.kt => WooPosErrorScreenTest.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename WooCommerce/src/androidTest/kotlin/com/woocommerce/android/{WooPosDisconnectedScreenTest.kt => WooPosErrorScreenTest.kt} (100%) diff --git a/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosDisconnectedScreenTest.kt b/WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosErrorScreenTest.kt similarity index 100% rename from WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosDisconnectedScreenTest.kt rename to WooCommerce/src/androidTest/kotlin/com/woocommerce/android/WooPosErrorScreenTest.kt From e9925355ead503de0a117f39d0694a5d53f5cd52 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 10 Dec 2024 10:21:06 +0100 Subject: [PATCH 105/132] Extract strings to res --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 4 ++-- WooCommerce/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index bdcee459c1a..b27e7613dc7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -297,8 +297,8 @@ class WooPosTotalsViewModel @Inject constructor( uiState.value = totalsState.copy( readerStatus = WooPosTotalsViewState.ReaderStatus.Preparing( - title = "Getting ready", - subtitle = "Preparing reader for payment" + title = resourceProvider.getString(R.string.woopos_totals_reader_getting_ready), + subtitle = resourceProvider.getString(R.string.woopos_totals_reader_preparing_reader_for_payment) ) ) } else { diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index c94bb6278d8..67af35c8742 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4302,6 +4302,7 @@ Connect to reader Getting ready Checking order + Preparing reader for payment Processing payment Please wait… From ae393fd070481a8084069b2ea3204e731d6cc1dc Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 10 Dec 2024 10:33:52 +0100 Subject: [PATCH 106/132] Extract strings to res --- .../android/ui/woopos/home/totals/WooPosTotalsViewModel.kt | 4 ++-- WooCommerce/src/main/res/values/strings.xml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index b27e7613dc7..1e7b1d09aab 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -276,8 +276,8 @@ class WooPosTotalsViewModel @Inject constructor( if (totalsState is WooPosTotalsViewState.Totals) { uiState.value = totalsState.copy( readerStatus = WooPosTotalsViewState.ReaderStatus.ReadyForPayment( - title = "Ready for payment", - subtitle = "Tap, swipe or insert card" + title = resourceProvider.getString(R.string.woopos_totals_reader_ready_for_payment_title), + subtitle = resourceProvider.getString(R.string.woopos_totals_reader_ready_for_payment_subtitle) ) ) } else { diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 67af35c8742..896e72ae866 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4303,6 +4303,8 @@ Getting ready Checking order Preparing reader for payment + "Ready for payment + Tap, swipe or insert card Processing payment Please wait… From 9f623745b3751c3781571b1b8b9d55e6ac23edf8 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 10 Dec 2024 11:07:27 +0100 Subject: [PATCH 107/132] Fix typo --- WooCommerce/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 896e72ae866..76dc1bb7ec7 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4303,7 +4303,7 @@ Getting ready Checking order Preparing reader for payment - "Ready for payment + Ready for payment Tap, swipe or insert card Processing payment From be72c83917e9f27f7a04bfdaec72c03d631d24a0 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 10 Dec 2024 11:21:46 +0100 Subject: [PATCH 108/132] Update tests --- .../home/totals/WooPosTotalsViewModelTest.kt | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 902e7a849a1..d2af9dbcf91 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -1,3 +1,4 @@ + package com.woocommerce.android.ui.woopos.home.totals import androidx.arch.core.executor.testing.InstantTaskExecutorRule @@ -177,6 +178,11 @@ class WooPosTotalsViewModelTest { @Test fun `given checkout started, when vm created, then order creation is started`() = runTest { // GIVEN + whenever(resourceProvider.getString(R.string.woopos_totals_reader_getting_ready)) + .thenReturn("Getting ready") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_checking_order)) + .thenReturn("Checking order") + val productIds = listOf(1L, 2L, 3L) val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { @@ -234,6 +240,11 @@ class WooPosTotalsViewModelTest { fun `given checkout started and successfully created order, when vm created, then totals state correctly calculated`() = runTest { // GIVEN + whenever(resourceProvider.getString(R.string.woopos_totals_reader_getting_ready)) + .thenReturn("Getting ready") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_checking_order)) + .thenReturn("Checking order") + val productIds = listOf(1L, 2L, 3L) val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { @@ -309,6 +320,11 @@ class WooPosTotalsViewModelTest { @Test fun `given order creation fails, when vm created, then error state is shown`() = runTest { // GIVEN + whenever(resourceProvider.getString(R.string.woopos_totals_reader_getting_ready)) + .thenReturn("Getting ready") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_checking_order)) + .thenReturn("Checking order") + val productIds = listOf(1L, 2L, 3L) val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { @@ -415,6 +431,11 @@ class WooPosTotalsViewModelTest { @Test fun `when order is created, then should track order creation success`() = runTest { // GIVEN + whenever(resourceProvider.getString(R.string.woopos_totals_reader_getting_ready)) + .thenReturn("Getting ready") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_checking_order)) + .thenReturn("Checking order") + val productIds = listOf(1L, 2L, 3L) val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { @@ -505,6 +526,11 @@ class WooPosTotalsViewModelTest { @Test fun `given reader connected, when order created, then payment collection started`() = runTest { // GIVEN + whenever(resourceProvider.getString(R.string.woopos_totals_reader_getting_ready)) + .thenReturn("Getting ready") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_checking_order)) + .thenReturn("Checking order") + whenever(networkStatus.isConnected()).thenReturn(true) val productIds = listOf(1L, 2L, 3L) val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) @@ -550,7 +576,7 @@ class WooPosTotalsViewModelTest { // THEN val state = viewModel.state.value as WooPosTotalsViewState.Totals assertThat(state.orderTotalText).isEqualTo("$3.00") - assertThat(state.readerStatus).isInstanceOf(WooPosTotalsViewState.ReaderStatus.ReadyForPayment::class.java) + assertThat(state.readerStatus).isInstanceOf(WooPosTotalsViewState.ReaderStatus.Preparing::class.java) } @org.junit.Test @@ -570,6 +596,8 @@ class WooPosTotalsViewModelTest { @org.junit.Test fun `given there is no internet, then collect payment method is not called`() = runTest { // GIVEN + whenever(resourceProvider.getString(R.string.woopos_totals_reader_getting_ready)).thenReturn("Getting ready") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_checking_order)).thenReturn("Checking order") whenever(networkStatus.isConnected()).thenReturn(false) val productIds = listOf(1L, 2L, 3L) val parentToChildrenEventFlow = MutableStateFlow(ParentToChildrenEvent.CheckoutClicked(productIds)) @@ -695,6 +723,10 @@ class WooPosTotalsViewModelTest { .thenReturn("To process this payment, please connect your reader.") whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label)) .thenReturn("Connect to reader") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_getting_ready)) + .thenReturn("Getting ready") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_checking_order)) + .thenReturn("Checking order") val productIds = listOf(1L, 2L, 3L) val order = Order.getEmptyOrder( @@ -741,7 +773,7 @@ class WooPosTotalsViewModelTest { // THEN assertThat(viewModel.state.value).isInstanceOf(WooPosTotalsViewState.Totals::class.java) val state = viewModel.state.value as WooPosTotalsViewState.Totals - assertThat(state.readerStatus).isInstanceOf(WooPosTotalsViewState.ReaderStatus.ReadyForPayment::class.java) + assertThat(state.readerStatus).isInstanceOf(WooPosTotalsViewState.ReaderStatus.Preparing::class.java) } @Test @@ -833,6 +865,39 @@ class WooPosTotalsViewModelTest { assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) } + @Test + fun `given order draft created and reader connected, when reader is ready, should show ready for payment state`() = runTest { + // GIVEN + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ) + ).thenReturn("Processing payment") + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_subtitle + ) + ).thenReturn("Please wait…") + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + + // WHEN + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + + // THEN + val totalState = vm.state.value as WooPosTotalsViewState.Totals + assertThat(totalState.readerStatus).isInstanceOf(WooPosTotalsViewState.ReaderStatus.ReadyForPayment::class.java) + } + @Test fun `given payment failed with retry action, when retry clicked, then should retry previous payment action`() = runTest { // GIVEN @@ -997,6 +1062,7 @@ class WooPosTotalsViewModelTest { verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.GoBackToCheckoutAfterFailedPayment) } + @Suppress("LongMethod") private suspend fun createViewModelAndSetupForSuccessfulOrderCreation( controllerFactory: CardReaderPaymentControllerFactory = paymentControllerFactory ): WooPosTotalsViewModel { @@ -1006,6 +1072,18 @@ class WooPosTotalsViewModelTest { .thenReturn("To process this payment, please connect your reader.") whenever(resourceProvider.getString(R.string.woopos_success_totals_error_reader_not_connected_cta_button_label)) .thenReturn("Connect to reader") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_getting_ready)) + .thenReturn("Getting ready") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_checking_order)) + .thenReturn("Checking order") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) + .thenReturn("Processing payment") + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) + .thenReturn("Please wait…") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_ready_for_payment_title)) + .thenReturn("Ready for payment") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_ready_for_payment_subtitle)) + .thenReturn("Tap, swipe or insert card") val productIds = listOf(1L, 2L, 3L) val orderId = 23L From 6a6be051a0862bd944ac59e6eaea70b87d04b0f0 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 10 Dec 2024 15:23:28 +0100 Subject: [PATCH 109/132] Show subtitles for `processing` and `capturing` states --- .../home/totals/WooPosTotalsViewModel.kt | 29 +++++++++++-------- WooCommerce/src/main/res/values/strings.xml | 1 + 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 1e7b1d09aab..fe01fffe7ec 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -237,13 +237,12 @@ class WooPosTotalsViewModel @Inject constructor( cardReaderPaymentController?.paymentState?.collect { paymentState -> when (paymentState) { is CardReaderPaymentState.CollectingPayment -> handleCollectingPaymentState() - is CardReaderPaymentState.LoadingData -> - handleReaderLoadingPaymentState() + + is CardReaderPaymentState.LoadingData -> handleReaderLoadingPaymentState() is CardReaderPaymentState.ProcessingPayment, - is CardReaderPaymentState.PaymentCapturing, - CardReaderPaymentState.ReFetchingOrder -> { - uiState.value = buildPaymentProcessingState() + is CardReaderPaymentState.PaymentCapturing -> { + uiState.value = buildPaymentProcessingState(paymentState) childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentProcessing) } @@ -260,6 +259,8 @@ class WooPosTotalsViewModel @Inject constructor( childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed) } + CardReaderPaymentState.ReFetchingOrder -> Unit + is CardReaderPaymentOrRefundState.CardReaderInteracRefundState, is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment, is CardReaderPaymentState.PrintingReceipt, @@ -335,14 +336,18 @@ class WooPosTotalsViewModel @Inject constructor( ) } - private fun buildPaymentProcessingState(): PaymentProcessing = PaymentProcessing( - title = resourceProvider.getString( - R.string.woopos_success_totals_payment_processing_title - ), - subtitle = resourceProvider.getString( - R.string.woopos_success_totals_payment_processing_subtitle + private fun buildPaymentProcessingState(paymentState: CardReaderPaymentOrRefundState): PaymentProcessing { + val subtitle = when (paymentState) { + is CardReaderPaymentState.ProcessingPayment -> R.string.woo_pos_payment_remove_card + else -> R.string.woopos_success_totals_payment_processing_subtitle + } + return PaymentProcessing( + title = resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ), + subtitle = resourceProvider.getString(subtitle) ) - ) + } private fun createOrderDraft(productIds: List) { viewModelScope.launch { diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 76dc1bb7ec7..f96a0559988 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4312,6 +4312,7 @@ Try another payment method Try payment again Go back to checkout + Remove card Dimmed background. Tap to close the menu. Card reader connected From 052db516841ea79e95a6519ff80bf81d50b20daa Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 10 Dec 2024 16:49:03 +0100 Subject: [PATCH 110/132] Show lottie animations for payment capturing and processing --- WooCommerce/build.gradle | 3 + .../ui/woopos/common/composeui/WooPosTheme.kt | 3 + .../home/totals/WooPosTotalsViewModel.kt | 5 +- .../WooPosTotalsPaymentProcessingScreen.kt | 39 +- .../main/res/raw/woopos_card_ilustration.json | 4272 +++++++++++++++++ gradle.properties | 14 +- gradle/libs.versions.toml | 2 + 7 files changed, 4331 insertions(+), 7 deletions(-) create mode 100644 WooCommerce/src/main/res/raw/woopos_card_ilustration.json diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index 90aa0fbff66..4127e6c2b17 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -479,6 +479,9 @@ dependencies { coreLibraryDesugaring(libs.android.desugar) + // Lottie + implementation(libs.lottie.compose) + // CameraX implementation(libs.androidx.camera.camera2) implementation(libs.androidx.camera.lifecycle) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index bd24abaddf6..a816614d677 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -24,6 +24,7 @@ data class CustomColors( val paymentProcessingBackground: Color, val paymentSuccessText: Color, val paymentSuccessIcon: Color, + val paymentProcessingText: Color, val dialogSubtitleHighlightBackground: Color = Color(0x14747480), val homeBackground: Color, ) @@ -196,6 +197,7 @@ private val DarkCustomColors = CustomColors( paymentSuccessBackground = WooPosColors.darkCustomColorsHomeBackground, paymentSuccessText = WooPosColors.oldGrayLight, paymentSuccessIcon = WooPosColors.darkCustomColorsHomeBackground, + paymentProcessingText = WooPosColors.White, homeBackground = WooPosColors.darkCustomColorsHomeBackground, paymentProcessingBackground = WooPosColors.WooPurple70, ) @@ -209,6 +211,7 @@ private val LightCustomColors = CustomColors( totalsBackground = WooPosColors.Gray0, paymentSuccessBackground = WooPosColors.White, paymentSuccessText = WooPosColors.Purple90, + paymentProcessingText = WooPosColors.White, paymentSuccessIcon = Color.White, homeBackground = WooPosColors.Gray0, paymentProcessingBackground = WooPosColors.WooPurple70, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index fe01fffe7ec..3ac508939ff 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -247,10 +247,7 @@ class WooPosTotalsViewModel @Inject constructor( } is CardReaderPaymentState.PaymentSuccessful -> { - uiState.value = - PaymentSuccess( - orderTotalText = paymentState.amountWithCurrencyLabel - ) + uiState.value = PaymentSuccess(orderTotalText = paymentState.amountWithCurrencyLabel) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index 72d7af39682..f8d2a8f23fa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -4,13 +4,28 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieClipSpec +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants +import com.airbnb.lottie.compose.rememberLottieComposition +import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme +import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState @Composable @@ -28,8 +43,28 @@ fun WooPosPaymentProcessingScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - Text(text = state.title) - Text(text = state.subtitle) + Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) + val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.woopos_card_ilustration)) + LottieAnimation( + modifier = Modifier.size(256.dp).padding(0.dp), + composition = composition, + iterations = LottieConstants.IterateForever, + clipToCompositionBounds = false, + clipSpec = LottieClipSpec.Markers("payment_processing_start", "payment_processing_end") + ) + Text( + text = state.title, + color = WooPosTheme.colors.paymentProcessingText, + style = MaterialTheme.typography.body1, + ) + Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) + Text( + text = state.subtitle, + color = WooPosTheme.colors.paymentProcessingText, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.Bold, + ) + Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) } } } diff --git a/WooCommerce/src/main/res/raw/woopos_card_ilustration.json b/WooCommerce/src/main/res/raw/woopos_card_ilustration.json new file mode 100644 index 00000000000..dfb1eed24fd --- /dev/null +++ b/WooCommerce/src/main/res/raw/woopos_card_ilustration.json @@ -0,0 +1,4272 @@ +{ + "v": "4.8.0", + "meta": { + "g": "LottieFiles AE 3.5.7", + "a": "", + "k": "", + "d": "", + "tc": "" + }, + "fr": 60, + "ip": 0, + "op": 792, + "w": 495, + "h": 522, + "nm": "card illustration (longer edition)", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 7", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 711.518, + "s": [ + 0 + ] + }, + { + "t": 720.99609375, + "s": [ + 100 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 255, + 273, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 86.25, + -11, + 0 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.362, + 0.362, + 0.362 + ], + "y": [ + 1, + 1, + 1 + ] + }, + "o": { + "x": [ + 0.558, + 0.558, + 0.558 + ], + "y": [ + 0, + 0, + 0 + ] + }, + "t": 707.162, + "s": [ + 114, + 114, + 100 + ] + }, + { + "t": 731.337890625, + "s": [ + 160, + 160, + 100 + ] + } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 61, + -7.75 + ], + [ + 75.75, + 7 + ], + [ + 106.75, + -30.25 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.011764706817, + 0.831372608858, + 0.474509833841, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 13, + "ix": 5 + }, + "lc": 2, + "lj": 2, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 0, + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.362 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.558 + ], + "y": [ + 0 + ] + }, + "t": 707.162, + "s": [ + 0 + ] + }, + { + "t": 731.337890625, + "s": [ + 100 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 2, + "op": 1823, + "st": 2, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 3, + "nm": "NULL ", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 0, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.476 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.816 + ], + "y": [ + 0 + ] + }, + "t": 0, + "s": [ + -5 + ] + }, + { + "i": { + "x": [ + 0.373 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 35, + "s": [ + 6 + ] + }, + { + "t": 71, + "s": [ + 0 + ] + } + ], + "ix": 10 + }, + "p": { + "a": 1, + "k": [ + { + "i": { + "x": 0.476, + "y": 1 + }, + "o": { + "x": 0.816, + "y": 0 + }, + "t": 0, + "s": [ + 279.5, + 418, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.373, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 35, + "s": [ + 279.5, + 396, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 0.667 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 71, + "s": [ + 276.5, + 407, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 73, + "s": [ + 276.5, + 407, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 140.334, + "s": [ + 276.5, + 417, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 0.667 + }, + "o": { + "x": 0.333, + "y": 0.333 + }, + "t": 207.666, + "s": [ + 276.5, + 407, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 208, + "s": [ + 276.5, + 407, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 275.334, + "s": [ + 276.5, + 417, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 0.667 + }, + "o": { + "x": 0.333, + "y": 0.333 + }, + "t": 342.666, + "s": [ + 276.5, + 407, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 343, + "s": [ + 276.5, + 407, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 410.334, + "s": [ + 276.5, + 417, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "t": 477.666015625, + "s": [ + 276.5, + 407, + 0 + ] + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 50, + 50, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "ip": 0, + "op": 2401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 3, + "nm": "NULL CONTROL ", + "parent": 2, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 0, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.219 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.687 + ], + "y": [ + 0 + ] + }, + "t": 378, + "s": [ + 10 + ] + }, + { + "t": 412, + "s": [ + 0 + ] + } + ], + "ix": 10 + }, + "p": { + "a": 1, + "k": [ + { + "i": { + "x": 0.476, + "y": 1 + }, + "o": { + "x": 0.816, + "y": 0 + }, + "t": 0, + "s": [ + -2.899, + 32.601, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.373, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 28, + "s": [ + -2.899, + -46.399, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 59, + "s": [ + -2.899, + -36.399, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.219, + "y": 1 + }, + "o": { + "x": 0.687, + "y": 0 + }, + "t": 378, + "s": [ + -2.899, + -36.399, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "t": 412, + "s": [ + 23.101, + -90.399, + 0 + ] + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 50, + 50, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "ip": 0, + "op": 2401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 3, + "nm": "NULL CONTROL 1", + "parent": 3, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 0, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 1, + "k": [ + { + "i": { + "x": 0.363, + "y": 1 + }, + "o": { + "x": 0.758, + "y": 0 + }, + "t": 678, + "s": [ + -39.5, + 50, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "t": 710, + "s": [ + 50, + 50, + 0 + ] + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 50, + 50, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "ip": 0, + "op": 2401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 4, + "nm": "Shape Layer 4", + "parent": 4, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 50, + 50, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -81.399, + 67.601, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 248.201, + 248.201 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 44, + "ix": 1 + }, + "e": { + "a": 0, + "k": 56, + "ix": 2 + }, + "o": { + "a": 0, + "k": -90, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 425, + "s": [ + 0.403921598547, + 0.262745098039, + 0.6, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0 + ] + }, + "t": 438, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 470, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 510, + "s": [ + 0.403921598547, + 0.262745098039, + 0.6, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 549, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 589, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 628, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 669, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 708, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0 + ] + }, + "t": 749, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 786, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "t": 827, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + } + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 470, + "s": [ + 14 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 510, + "s": [ + 22 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 549, + "s": [ + 14 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 589, + "s": [ + 22 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 628, + "s": [ + 14 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 0.99 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 669, + "s": [ + 22 + ] + }, + { + "t": 690, + "s": [ + 0 + ] + } + ], + "ix": 5 + }, + "lc": 1, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -81.399, + 67.601 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 4, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 2401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 6, + "ty": 4, + "nm": "Shape Layer 3", + "parent": 4, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 50, + 50, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -81.399, + 67.601, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 183.201, + 183.201 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 44, + "ix": 1 + }, + "e": { + "a": 0, + "k": 56, + "ix": 2 + }, + "o": { + "a": 0, + "k": -90, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 425, + "s": [ + 0.403921598547, + 0.262745098039, + 0.6, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + -0.612 + ] + }, + "t": 438, + "s": [ + 0.541176497936, + 0.380392193794, + 0.792156934738, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 461, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 501, + "s": [ + 0.403921598547, + 0.262745098039, + 0.6, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 540, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 580, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 619, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 660, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 699, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0 + ] + }, + "t": 740, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 777, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "t": 818, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + } + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 461, + "s": [ + 14 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 501, + "s": [ + 22 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 540, + "s": [ + 14 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 580, + "s": [ + 22 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 619, + "s": [ + 14 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 660, + "s": [ + 22 + ] + }, + { + "t": 690, + "s": [ + 0 + ] + } + ], + "ix": 5 + }, + "lc": 1, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -81.399, + 67.601 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 4, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 2401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 7, + "ty": 4, + "nm": "Shape Layer 2", + "parent": 4, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 50, + 50, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -81.399, + 67.601, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 116.201, + 116.201 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 44, + "ix": 1 + }, + "e": { + "a": 0, + "k": 56, + "ix": 2 + }, + "o": { + "a": 0, + "k": -90, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 425, + "s": [ + 0.403921598547, + 0.262745098039, + 0.6, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + -0.612 + ] + }, + "t": 438, + "s": [ + 0.541176497936, + 0.380392193794, + 0.792156934738, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 452, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 492, + "s": [ + 0.403921598547, + 0.262745098039, + 0.6, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 531, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 571, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 610, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 651, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 690, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0 + ] + }, + "t": 731, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 768, + "s": [ + 0.533333361149, + 0.384313762188, + 0.764705955982, + 1 + ] + }, + { + "t": 809, + "s": [ + 0.403921604156, + 0.262745112181, + 0.600000023842, + 1 + ] + } + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 452, + "s": [ + 14 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 492, + "s": [ + 22 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 531, + "s": [ + 14 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 571, + "s": [ + 22 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 610, + "s": [ + 14 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 651, + "s": [ + 22 + ] + }, + { + "t": 690, + "s": [ + 0 + ] + } + ], + "ix": 5 + }, + "lc": 1, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -81.399, + 67.601 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 4, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 2401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 8, + "ty": 4, + "nm": "Shape Layer 1", + "parent": 3, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 50, + 50, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -4.77, + 42.309, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.363, + 0.363 + ], + "y": [ + 1, + 1 + ] + }, + "o": { + "x": [ + 0.758, + 0.758 + ], + "y": [ + 0, + 0 + ] + }, + "t": 672, + "s": [ + 262.461, + 228.617 + ] + }, + { + "t": 717.41796875, + "s": [ + 270, + 270 + ] + } + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.363 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.758 + ], + "y": [ + 0 + ] + }, + "t": 672, + "s": [ + 28 + ] + }, + { + "t": 717.41796875, + "s": [ + 164 + ] + } + ], + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 682.988, + "s": [ + 0.745098039216, + 0.627450980392, + 0.949019667682, + 1 + ] + }, + { + "t": 700.5703125, + "s": [ + 1, + 1, + 1, + 1 + ] + } + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -4.77, + 42.309 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 2401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 9, + "ty": 3, + "nm": "NULL CONTROL 4", + "parent": 3, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 0, + "ix": 11 + }, + "r": { + "a": 0, + "k": -10, + "ix": 10 + }, + "p": { + "a": 1, + "k": [ + { + "i": { + "x": 0.562, + "y": 0.562 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 98, + "s": [ + 152.23, + 12.453, + 0 + ], + "to": [ + 5.883, + 0, + 0 + ], + "ti": [ + 0, + -5.883, + 0 + ] + }, + { + "i": { + "x": 0.656, + "y": 0.656 + }, + "o": { + "x": 0.311, + "y": 0.311 + }, + "t": 127.998, + "s": [ + 162.882, + 23.105, + 0 + ], + "to": [ + 0, + 5.883, + 0 + ], + "ti": [ + 5.883, + 0, + 0 + ] + }, + { + "i": { + "x": 0.689, + "y": 0.689 + }, + "o": { + "x": 0.343, + "y": 0.343 + }, + "t": 157.998, + "s": [ + 152.23, + 33.758, + 0 + ], + "to": [ + -5.883, + 0, + 0 + ], + "ti": [ + 0, + 5.883, + 0 + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.437, + "y": 0.437 + }, + "t": 187.998, + "s": [ + 141.577, + 23.105, + 0 + ], + "to": [ + 0, + -5.883, + 0 + ], + "ti": [ + -5.883, + 0, + 0 + ] + }, + { + "i": { + "x": 0.562, + "y": 0.562 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 218, + "s": [ + 152.23, + 12.453, + 0 + ], + "to": [ + 5.883, + 0, + 0 + ], + "ti": [ + 0, + -5.883, + 0 + ] + }, + { + "i": { + "x": 0.656, + "y": 0.656 + }, + "o": { + "x": 0.311, + "y": 0.311 + }, + "t": 248, + "s": [ + 162.882, + 23.105, + 0 + ], + "to": [ + 0, + 5.883, + 0 + ], + "ti": [ + 5.883, + 0, + 0 + ] + }, + { + "i": { + "x": 0.689, + "y": 0.689 + }, + "o": { + "x": 0.343, + "y": 0.343 + }, + "t": 278, + "s": [ + 152.23, + 33.758, + 0 + ], + "to": [ + -5.883, + 0, + 0 + ], + "ti": [ + 0, + 5.883, + 0 + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.437, + "y": 0.437 + }, + "t": 308, + "s": [ + 141.577, + 23.105, + 0 + ], + "to": [ + 0, + -5.883, + 0 + ], + "ti": [ + -5.883, + 0, + 0 + ] + }, + { + "i": { + "x": 0.562, + "y": 0.562 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 338, + "s": [ + 152.23, + 12.453, + 0 + ], + "to": [ + 5.883, + 0, + 0 + ], + "ti": [ + 0, + -5.883, + 0 + ] + }, + { + "i": { + "x": 0.656, + "y": 0.656 + }, + "o": { + "x": 0.311, + "y": 0.311 + }, + "t": 368, + "s": [ + 162.882, + 23.105, + 0 + ], + "to": [ + 0, + 5.883, + 0 + ], + "ti": [ + 5.883, + 0, + 0 + ] + }, + { + "i": { + "x": 0.689, + "y": 0.689 + }, + "o": { + "x": 0.343, + "y": 0.343 + }, + "t": 398, + "s": [ + 152.23, + 33.758, + 0 + ], + "to": [ + -5.883, + 0, + 0 + ], + "ti": [ + 0, + 5.883, + 0 + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.437, + "y": 0.437 + }, + "t": 428, + "s": [ + 141.577, + 23.105, + 0 + ], + "to": [ + 0, + -5.883, + 0 + ], + "ti": [ + -5.883, + 0, + 0 + ] + }, + { + "t": 458, + "s": [ + 152.23, + 12.453, + 0 + ] + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 50, + 50, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "ip": 0, + "op": 2401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 10, + "ty": 3, + "nm": "NULL CONTROL 3", + "parent": 9, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 0, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.476 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.816 + ], + "y": [ + 0 + ] + }, + "t": 6, + "s": [ + 30 + ] + }, + { + "i": { + "x": [ + 0.373 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 34, + "s": [ + 50 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0 + ] + }, + "t": 71, + "s": [ + 41 + ] + }, + { + "i": { + "x": [ + 0.219 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.687 + ], + "y": [ + 0 + ] + }, + "t": 378, + "s": [ + 41 + ] + }, + { + "t": 412, + "s": [ + 13 + ] + } + ], + "ix": 10 + }, + "p": { + "a": 1, + "k": [ + { + "i": { + "x": 0.476, + "y": 1 + }, + "o": { + "x": 0.816, + "y": 0 + }, + "t": 6, + "s": [ + 50, + 114, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.373, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 34, + "s": [ + 50, + 30, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 71, + "s": [ + 50, + 50, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.219, + "y": 1 + }, + "o": { + "x": 0.687, + "y": 0 + }, + "t": 378, + "s": [ + 50, + 50, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "t": 412, + "s": [ + 22, + 120, + 0 + ] + } + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 50, + 50, + 0 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.219, + 0.219, + 0.219 + ], + "y": [ + 1, + 1, + 1 + ] + }, + "o": { + "x": [ + 0.687, + 0.687, + 0.687 + ], + "y": [ + 0, + 0, + 0 + ] + }, + "t": 378, + "s": [ + 110, + 110, + 100 + ] + }, + { + "t": 412, + "s": [ + 98, + 98, + 100 + ] + } + ], + "ix": 6 + } + }, + "ao": 0, + "ip": 0, + "op": 2401, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 11, + "ty": 4, + "nm": "Shape Layer 5", + "parent": 10, + "td": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + -34.21, + 4.143, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -4.77, + 42.309, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 248.461, + 155.617 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 28, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.403921598547, + 0.262745098039, + 0.6, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -4.77, + 42.309 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 413, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 12, + "ty": 4, + "nm": "Shape Layer 6", + "parent": 8, + "tt": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": -17, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 93.808, + 0.822, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -4.77, + 42.309, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 110, + 110, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 354.461, + 196.617 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.20000001496, + 0.137254901961, + 0.337254901961, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -4.77, + 42.309 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 413, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 13, + "ty": 4, + "nm": "Shape Layer 8", + "parent": 10, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + -34.21, + 4.143, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -4.77, + 42.309, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 248.461, + 155.617 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 28, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.403921598547, + 0.262745098039, + 0.6, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -4.77, + 42.309 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 413, + "st": 0, + "bm": 0 + } + ], + "markers": [ + { + "tm": 0, + "cm": "reader_loading_entry_start", + "dr": 0 + }, + { + "tm": 0, + "cm": "reader_awaiting_start", + "dr": 0 + }, + { + "tm": 0, + "cm": "reader_awaiting_end", + "dr": 0 + }, + { + "tm": 469, + "cm": "payment_processing_start", + "dr": 0 + }, + { + "tm": 619, + "cm": "payment_processing_end", + "dr": 0 + } + ] +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index b5559839f0d..c6ea0229456 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,19 @@ android.useAndroidX=true android.enableJetifier=false android.nonTransitiveRClass=true -android.enableR8.fullMode=false +android.enableR8.fullMode=true # Dependency Analysis Plugin dependency.analysis.android.ignored.variants=release,vanillaDebug,vanillaRelease,wasabiDebug,wasabiRelease,jalapenoRelease + +# Enable Gradle Daemon +org.gradle.daemon=true + +# Enable parallel builds +org.gradle.parallel=true + +# Enable configuration on demand +org.gradle.configureondemand=true + +# Enable incremental compilation +kotlin.incremental=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e8b35551339..bc316491db3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -73,6 +73,7 @@ java = '11' jetty-webapp = '9.4.51.v20230217' json-path = '2.9.0' junit = '4.13.2' +lottie = '5.2.0' kotlin = '2.0.21' kotlinx-coroutines = '1.8.1' ksp = '2.0.21-1.0.27' @@ -213,6 +214,7 @@ jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-datab jetty-webapp = { group = "org.eclipse.jetty", name = "jetty-webapp", version.ref = "jetty-webapp" } json-path = { group = "com.jayway.jsonpath", name = "json-path", version.ref = "json-path" } junit = { group = "junit", name = "junit", version.ref = "junit" } +lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottie" } kotlin-test-junit = { group = "org.jetbrains.kotlin", name = "kotlin-test-junit", version.ref = "kotlin" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } From f919b572be3f1863bdb21f46f0ea1b97923228b0 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 10 Dec 2024 16:54:38 +0100 Subject: [PATCH 111/132] Revert accidental gradle.properties changes --- gradle.properties | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/gradle.properties b/gradle.properties index c6ea0229456..401ad1c005b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,26 +1,12 @@ org.gradle.jvmargs=-Xmx8g ksp.allow.all.target.configuration=false - # Enables Gradle Build Cache - https://docs.gradle.org/current/userguide/build_cache.html org.gradle.caching=true - android.useAndroidX=true android.enableJetifier=false android.nonTransitiveRClass=true -android.enableR8.fullMode=true +android.enableR8.fullMode=false # Dependency Analysis Plugin -dependency.analysis.android.ignored.variants=release,vanillaDebug,vanillaRelease,wasabiDebug,wasabiRelease,jalapenoRelease - -# Enable Gradle Daemon -org.gradle.daemon=true - -# Enable parallel builds -org.gradle.parallel=true - -# Enable configuration on demand -org.gradle.configureondemand=true - -# Enable incremental compilation -kotlin.incremental=true \ No newline at end of file +dependency.analysis.android.ignored.variants=release,vanillaDebug,vanillaRelease,wasabiDebug,wasabiRelease,jalapenoRelease \ No newline at end of file From 4de5bae0bef84457347e2702ed19dc3c889df1e2 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 10 Dec 2024 18:18:22 +0100 Subject: [PATCH 112/132] Animate "reader ready" state in Totals --- .../ui/woopos/home/totals/WooPosTotalsScreen.kt | 17 ++++++++++++----- .../main/res/raw/woopos_card_ilustration.json | 9 +++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 8d0086c929c..3c1116edae4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -36,6 +37,11 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieClipSpec +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants +import com.airbnb.lottie.compose.rememberLottieComposition import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme @@ -176,11 +182,12 @@ private fun PreparingReader(readerStatus: WooPosTotalsViewState.ReaderStatus) { @Composable private fun ReaderReadyForPayment(readerStatus: WooPosTotalsViewState.ReaderStatus) { - Icon( - modifier = Modifier.size(164.dp), - painter = painterResource(id = R.drawable.woopos_ic_collect_payment), - contentDescription = "Collect Payment", - tint = Color.Unspecified, + val tapCardAnimation by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.woopos_card_ilustration)) + LottieAnimation( + modifier = Modifier.size(256.dp).padding(0.dp), + composition = tapCardAnimation, + clipSpec = LottieClipSpec.Markers("reader_awaiting_start", "reader_awaiting_end"), + iterations = LottieConstants.IterateForever, ) Spacer(modifier = Modifier.height(20.dp.toAdaptivePadding())) Text( diff --git a/WooCommerce/src/main/res/raw/woopos_card_ilustration.json b/WooCommerce/src/main/res/raw/woopos_card_ilustration.json index dfb1eed24fd..395a8bf1f9b 100644 --- a/WooCommerce/src/main/res/raw/woopos_card_ilustration.json +++ b/WooCommerce/src/main/res/raw/woopos_card_ilustration.json @@ -4249,12 +4249,17 @@ "dr": 0 }, { - "tm": 0, + "tm": 71, + "cm": "reader_loading_entry_end", + "dr": 0 + }, + { + "tm": 92, "cm": "reader_awaiting_start", "dr": 0 }, { - "tm": 0, + "tm": 337, "cm": "reader_awaiting_end", "dr": 0 }, From 54e65c69a46fe8040ba9f16ff18efeff84423b86 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 12:19:06 +0100 Subject: [PATCH 113/132] Revert accidental change to gradle.properties --- gradle.properties | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 401ad1c005b..a9cdcd14e30 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,10 @@ org.gradle.jvmargs=-Xmx8g ksp.allow.all.target.configuration=false + # Enables Gradle Build Cache - https://docs.gradle.org/current/userguide/build_cache.html org.gradle.caching=true +org.gradle.configuration-cache=true + android.useAndroidX=true android.enableJetifier=false @@ -9,4 +12,4 @@ android.nonTransitiveRClass=true android.enableR8.fullMode=false # Dependency Analysis Plugin -dependency.analysis.android.ignored.variants=release,vanillaDebug,vanillaRelease,wasabiDebug,wasabiRelease,jalapenoRelease \ No newline at end of file +dependency.analysis.android.ignored.variants=release,vanillaDebug,vanillaRelease,wasabiDebug,wasabiRelease,jalapenoRelease From b183afbb5416a349115060466f92e75f3324ea3c Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 12:19:39 +0100 Subject: [PATCH 114/132] Revert accidental change to gradle.properties --- gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a9cdcd14e30..b5559839f0d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,6 @@ ksp.allow.all.target.configuration=false # Enables Gradle Build Cache - https://docs.gradle.org/current/userguide/build_cache.html org.gradle.caching=true -org.gradle.configuration-cache=true android.useAndroidX=true android.enableJetifier=false From d82111caf0e654c0dc575b43c843d5983752dfb1 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 13:11:17 +0100 Subject: [PATCH 115/132] Update unit tests --- .../android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index d2af9dbcf91..a97b106a3b2 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -1084,6 +1084,8 @@ class WooPosTotalsViewModelTest { .thenReturn("Ready for payment") whenever(resourceProvider.getString(R.string.woopos_totals_reader_ready_for_payment_subtitle)) .thenReturn("Tap, swipe or insert card") + whenever(resourceProvider.getString(R.string.woo_pos_payment_remove_card)) + .thenReturn("Remove card") val productIds = listOf(1L, 2L, 3L) val orderId = 23L From 2f4ab2e2c3c2bccda2feb1d3dc3dc1f882af86aa Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 13:48:01 +0100 Subject: [PATCH 116/132] Add tests: * given order draft created and reader connected, when payment is processed, should show processing state * given order draft created and reader connected, when payment is captured, should show processing state --- .../home/totals/WooPosTotalsViewModelTest.kt | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index a97b106a3b2..a8d58bf3cbc 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -530,6 +530,8 @@ class WooPosTotalsViewModelTest { .thenReturn("Getting ready") whenever(resourceProvider.getString(R.string.woopos_totals_reader_checking_order)) .thenReturn("Checking order") + whenever(resourceProvider.getString(R.string.woopos_totals_reader_preparing_reader_for_payment)) + .thenReturn("Preparing reader for payment") whenever(networkStatus.isConnected()).thenReturn(true) val productIds = listOf(1L, 2L, 3L) @@ -898,6 +900,82 @@ class WooPosTotalsViewModelTest { assertThat(totalState.readerStatus).isInstanceOf(WooPosTotalsViewState.ReaderStatus.ReadyForPayment::class.java) } + @Test + fun `given order draft created and reader connected, when payment is captured, should show processing state`() = runTest { + // GIVEN + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ) + ).thenReturn("Processing payment") + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_subtitle + ) + ).thenReturn("Please wait…") + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + + // WHEN + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.PaymentCapturing.ExternalReaderPaymentCapturing("") + + // THEN + val processingState = vm.state.value as WooPosTotalsViewState.PaymentProcessing + assertThat(processingState).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) + with(processingState){ + assertThat(title).isEqualTo("Processing payment") + assertThat(subtitle).isEqualTo("Please wait…") + } + } + + @Test + fun `given order draft created and reader connected, when payment is processed, should show processing state`() = runTest { + // GIVEN + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_title + ) + ).thenReturn("Processing payment") + whenever( + resourceProvider.getString( + R.string.woopos_success_totals_payment_processing_subtitle + ) + ).thenReturn("Please wait…") + whenever(networkStatus.isConnected()).thenReturn(true) + val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) + whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) + val mockCardReaderPaymentController: CardReaderPaymentController = mock() + val factory: CardReaderPaymentControllerFactory = mock() + whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + val paymentState = + MutableStateFlow( + CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} + ) + whenever(mockCardReaderPaymentController.paymentState).thenReturn(paymentState) + + // WHEN + val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) + paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} + + // THEN + val processingState = vm.state.value as WooPosTotalsViewState.PaymentProcessing + assertThat(processingState).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) + with(processingState){ + assertThat(title).isEqualTo("Processing payment") + assertThat(subtitle).isEqualTo("Remove card") + } + } + @Test fun `given payment failed with retry action, when retry clicked, then should retry previous payment action`() = runTest { // GIVEN From 0866ff8703d21b7940ce7b9133da08d249bba519 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 14:50:13 +0100 Subject: [PATCH 117/132] Add payment processing screen enter animation --- .../WooPosTotalsPaymentProcessingScreen.kt | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index f8d2a8f23fa..f1858f061d8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -1,5 +1,9 @@ package com.woocommerce.android.ui.woopos.home.totals.payment.processing +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -12,7 +16,11 @@ import androidx.compose.foundation.layout.size import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight @@ -27,11 +35,19 @@ import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState +import kotlinx.coroutines.delay + +private const val ENTER_ANIMATION_DURATION = 280 @Composable fun WooPosPaymentProcessingScreen( state: WooPosTotalsViewState.PaymentProcessing, ) { + var enterAnimationStarted by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + delay(50) + enterAnimationStarted = true + } Box( modifier = Modifier .background(color = WooPosTheme.colors.paymentProcessingBackground) @@ -52,18 +68,31 @@ fun WooPosPaymentProcessingScreen( clipToCompositionBounds = false, clipSpec = LottieClipSpec.Markers("payment_processing_start", "payment_processing_end") ) - Text( - text = state.title, - color = WooPosTheme.colors.paymentProcessingText, - style = MaterialTheme.typography.body1, - ) - Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) - Text( - text = state.subtitle, - color = WooPosTheme.colors.paymentProcessingText, - style = MaterialTheme.typography.h4, - fontWeight = FontWeight.Bold, + val marginBetweenAnimatedIconAndText by animateDpAsState( + targetValue = if (enterAnimationStarted) 0.dp else 256.dp, + animationSpec = tween(durationMillis = ENTER_ANIMATION_DURATION) ) + Spacer(modifier = Modifier.height(marginBetweenAnimatedIconAndText.toAdaptivePadding())) + AnimatedVisibility( + visible = enterAnimationStarted, + enter = fadeIn(animationSpec = tween(durationMillis = ENTER_ANIMATION_DURATION * 2)), + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = state.title, + color = WooPosTheme.colors.paymentProcessingText, + style = MaterialTheme.typography.h6, + fontWeight = FontWeight.Normal, + ) + Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) + Text( + text = state.subtitle, + color = WooPosTheme.colors.paymentProcessingText, + style = MaterialTheme.typography.h4, + fontWeight = FontWeight.Bold, + ) + } + } Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) } } From bebd92af2c1055dd8e2ef14007af5d7e5a9bca62 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 14:51:49 +0100 Subject: [PATCH 118/132] Satisfy detekt's complaints --- .../ui/woopos/home/totals/WooPosTotalsViewModelTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index a8d58bf3cbc..bd5b23786a5 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -932,7 +932,7 @@ class WooPosTotalsViewModelTest { // THEN val processingState = vm.state.value as WooPosTotalsViewState.PaymentProcessing assertThat(processingState).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) - with(processingState){ + with(processingState) { assertThat(title).isEqualTo("Processing payment") assertThat(subtitle).isEqualTo("Please wait…") } @@ -970,7 +970,7 @@ class WooPosTotalsViewModelTest { // THEN val processingState = vm.state.value as WooPosTotalsViewState.PaymentProcessing assertThat(processingState).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) - with(processingState){ + with(processingState) { assertThat(title).isEqualTo("Processing payment") assertThat(subtitle).isEqualTo("Remove card") } From f388120666d49cd2458b3118e21839f2c86ca970 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 14:53:03 +0100 Subject: [PATCH 119/132] Tune up animation --- .../processing/WooPosTotalsPaymentProcessingScreen.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index f1858f061d8..1c24333235f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -3,7 +3,6 @@ package com.woocommerce.android.ui.woopos.home.totals.payment.processing import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -73,10 +72,7 @@ fun WooPosPaymentProcessingScreen( animationSpec = tween(durationMillis = ENTER_ANIMATION_DURATION) ) Spacer(modifier = Modifier.height(marginBetweenAnimatedIconAndText.toAdaptivePadding())) - AnimatedVisibility( - visible = enterAnimationStarted, - enter = fadeIn(animationSpec = tween(durationMillis = ENTER_ANIMATION_DURATION * 2)), - ) { + AnimatedVisibility(visible = enterAnimationStarted) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( text = state.title, From d79fe17670a25f3f8d16224a9797e7dd16623e7a Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 14:57:55 +0100 Subject: [PATCH 120/132] Clean up code --- .../payment/processing/WooPosTotalsPaymentProcessingScreen.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index 1c24333235f..64421b0b803 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -2,7 +2,6 @@ package com.woocommerce.android.ui.woopos.home.totals.payment.processing import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -36,8 +35,6 @@ import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState import kotlinx.coroutines.delay -private const val ENTER_ANIMATION_DURATION = 280 - @Composable fun WooPosPaymentProcessingScreen( state: WooPosTotalsViewState.PaymentProcessing, @@ -69,7 +66,6 @@ fun WooPosPaymentProcessingScreen( ) val marginBetweenAnimatedIconAndText by animateDpAsState( targetValue = if (enterAnimationStarted) 0.dp else 256.dp, - animationSpec = tween(durationMillis = ENTER_ANIMATION_DURATION) ) Spacer(modifier = Modifier.height(marginBetweenAnimatedIconAndText.toAdaptivePadding())) AnimatedVisibility(visible = enterAnimationStarted) { From 7d0bbf0543e277f8bbd497cfb6e769e9ff79eef7 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 15:40:17 +0100 Subject: [PATCH 121/132] Clean up tests --- .../home/totals/WooPosTotalsViewModelTest.kt | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index bd5b23786a5..6b0e64707eb 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -298,6 +298,8 @@ class WooPosTotalsViewModelTest { fun `given OnNewTransactionClicked, should send NewTransactionClicked event and reset state to initial`() = runTest { // GIVEN + whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) + .thenReturn("Payment failed") val parentToChildrenEventReceiver: WooPosParentToChildrenEventReceiver = mock { on { events }.thenReturn(mock()) } @@ -941,16 +943,6 @@ class WooPosTotalsViewModelTest { @Test fun `given order draft created and reader connected, when payment is processed, should show processing state`() = runTest { // GIVEN - whenever( - resourceProvider.getString( - R.string.woopos_success_totals_payment_processing_title - ) - ).thenReturn("Processing payment") - whenever( - resourceProvider.getString( - R.string.woopos_success_totals_payment_processing_subtitle - ) - ).thenReturn("Please wait…") whenever(networkStatus.isConnected()).thenReturn(true) val readerStatus = MutableStateFlow(CardReaderStatus.Connected(mock())) whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) @@ -1105,10 +1097,6 @@ class WooPosTotalsViewModelTest { @Test fun `given payment failed, when go back to checkout clicked, then should inform home about the situation`() = runTest { // GIVEN - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_title)) - .thenReturn("Processing payment") - whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_processing_subtitle)) - .thenReturn("Please wait…") whenever(resourceProvider.getString(R.string.woopos_success_totals_payment_failed_title)) .thenReturn("Payment failed") whenever(resourceProvider.getString(R.string.woo_pos_payment_failed_try_again)) From 876841477e1c1532e3ea4eb35d2943ba5a5e33db Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 15:55:32 +0100 Subject: [PATCH 122/132] Remove redundant code --- .../payment/processing/WooPosTotalsPaymentProcessingScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index 64421b0b803..29ab8750f14 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -58,7 +58,7 @@ fun WooPosPaymentProcessingScreen( Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding())) val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.woopos_card_ilustration)) LottieAnimation( - modifier = Modifier.size(256.dp).padding(0.dp), + modifier = Modifier.size(256.dp), composition = composition, iterations = LottieConstants.IterateForever, clipToCompositionBounds = false, From 9171931d9e0a41f33d795e95748a699369118489 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 15:56:30 +0100 Subject: [PATCH 123/132] Remove redundant code --- .../android/ui/woopos/home/totals/WooPosTotalsScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index 3c1116edae4..0c22c3fcd82 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -184,7 +184,7 @@ private fun PreparingReader(readerStatus: WooPosTotalsViewState.ReaderStatus) { private fun ReaderReadyForPayment(readerStatus: WooPosTotalsViewState.ReaderStatus) { val tapCardAnimation by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.woopos_card_ilustration)) LottieAnimation( - modifier = Modifier.size(256.dp).padding(0.dp), + modifier = Modifier.size(256.dp), composition = tapCardAnimation, clipSpec = LottieClipSpec.Markers("reader_awaiting_start", "reader_awaiting_end"), iterations = LottieConstants.IterateForever, From a16613c7c9b4342b4ed12081895c0c87163ef6d3 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 16:11:28 +0100 Subject: [PATCH 124/132] Satisfy detekt's complaints --- .../payment/processing/WooPosTotalsPaymentProcessingScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt index 29ab8750f14..3f96b07676b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.MaterialTheme import androidx.compose.material.Text From ca316077eb553539fa3da5e97f3b8ed4072d4f65 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 16:53:52 +0100 Subject: [PATCH 125/132] Give color a name --- .../android/ui/woopos/common/composeui/WooPosTheme.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt index 226de53dd03..f5c8e4718ee 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/WooPosTheme.kt @@ -51,6 +51,7 @@ private object WooPosColors { val darkCustomloadingSkeleton = Color(0xFF616161) val darkCustomColorsHomeBackground = Color(0xFF1E1E1E) val darkQuaternaryBackground = Color(0xFF111111) + val darkTotalsBackground = Color(0xFF1C1C1E) // LightCustomColors val lightCustomColorsError = Color(0xFFF16618) From 1c2ddee4cb604c964aba821b918dd7f43631aee8 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 18:09:16 +0100 Subject: [PATCH 126/132] Clean up tests after merge --- .../home/totals/WooPosTotalsViewModelTest.kt | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 5719e004d94..01980e4a979 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -769,7 +769,7 @@ class WooPosTotalsViewModelTest { val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) readerStatus.value = CardReaderStatus.Connected(mock()) @@ -787,16 +787,15 @@ class WooPosTotalsViewModelTest { val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val vm = createViewModelAndSetupForSuccessfulOrderCreation(controllerFactory = factory) // WHEN readerStatus.value = CardReaderStatus.NotConnected() // THEN - verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).stop() verify(mockCardReaderPaymentController).onBackPressed() - assertThat(vm.paymentScope!!.isActive).isFalse } @Test @@ -817,7 +816,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} @@ -850,7 +849,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} @@ -892,7 +891,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} @@ -913,7 +912,7 @@ class WooPosTotalsViewModelTest { vm.onUIEvent(WooPosTotalsUIEvent.RetryFailedTransactionClicked) // THEN - verify(mockCardReaderPaymentController).onCleared() + verify(mockCardReaderPaymentController).stop() verify(mockCardReaderPaymentController).start() } @@ -935,7 +934,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} @@ -976,7 +975,7 @@ class WooPosTotalsViewModelTest { whenever(cardReaderFacade.readerStatus).thenReturn(readerStatus) val mockCardReaderPaymentController: CardReaderPaymentController = mock() val factory: CardReaderPaymentControllerFactory = mock() - whenever(factory.create(any(), any(), any(), any())).thenReturn(mockCardReaderPaymentController) + whenever(factory.create(any(), any(), any())).thenReturn(mockCardReaderPaymentController) val paymentState = MutableStateFlow( CardReaderPaymentState.CollectingPayment.ExternalReaderCollectPaymentState("") {} From 8b218982ef31417149dd423666937e676fdca00e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Wed, 11 Dec 2024 18:09:24 +0100 Subject: [PATCH 127/132] Crash in case order is null illegally --- .../home/totals/WooPosTotalsViewModel.kt | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 354220fdf09..ba351005d69 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -168,12 +168,9 @@ class WooPosTotalsViewModel @Inject constructor( private suspend fun retryPaymentCollectionFromScratch() { cancelPaymentAction() val order = totalsRepository.getOrderById(dataState.value.orderId) - if (order == null) { - returnToCart() - } else { - uiState.value = buildWooPosTotalsViewState(order) - collectPayment() - } + checkNotNull(order) + uiState.value = buildWooPosTotalsViewState(order) + collectPayment() } private fun collectPayment() { @@ -222,7 +219,7 @@ class WooPosTotalsViewModel @Inject constructor( when (paymentState) { is CardReaderPaymentState.CollectingPayment, is CardReaderPaymentState.LoadingData -> - handlePaymentState(paymentState as CardReaderPaymentState) + handlePaymentState(paymentState) is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing, @@ -233,9 +230,7 @@ class WooPosTotalsViewModel @Inject constructor( is CardReaderPaymentState.PaymentSuccessful -> { uiState.value = - PaymentSuccess( - orderTotalText = paymentState.amountWithCurrencyLabel - ) + PaymentSuccess(orderTotalText = paymentState.amountWithCurrencyLabel) childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid) } @@ -263,13 +258,9 @@ class WooPosTotalsViewModel @Inject constructor( ) } else { val order = totalsRepository.getOrderById(dataState.value.orderId) - if (order == null) { - returnToCart() - } else { - uiState.value = - buildWooPosTotalsViewState(order, paymentState) - childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) - } + checkNotNull(order) + uiState.value = buildWooPosTotalsViewState(order, paymentState) + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentCollecting) } } From fadb13d9b62e4bc98243638d7820024c11edd62e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 12 Dec 2024 16:48:12 +0100 Subject: [PATCH 128/132] Rename Processing UI state to InProgress --- .../home/WooPosHomeChildToParentCommunication.kt | 2 +- .../android/ui/woopos/home/WooPosHomeViewModel.kt | 2 +- .../ui/woopos/home/totals/WooPosTotalsScreen.kt | 8 ++++---- .../ui/woopos/home/totals/WooPosTotalsViewModel.kt | 10 +++++----- .../ui/woopos/home/totals/WooPosTotalsViewState.kt | 2 +- .../WooPosTotalsPaymentInProgressScreen.kt} | 12 ++++++------ .../ui/woopos/home/WooPosHomeViewModelTest.kt | 6 +++--- .../woopos/home/totals/WooPosTotalsViewModelTest.kt | 10 +++++----- 8 files changed, 26 insertions(+), 26 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/{processing/WooPosTotalsPaymentProcessingScreen.kt => inprogress/WooPosTotalsPaymentInProgressScreen.kt} (93%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index 75dc773b717..1e872f88f18 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -26,7 +26,7 @@ sealed class ChildToParentEvent { data class ItemClickedInProductSelector(val itemData: WooPosItemsViewModel.ItemClickedData) : ChildToParentEvent() data object NewTransactionClicked : ChildToParentEvent() data object PaymentCollecting : ChildToParentEvent() - data object PaymentProcessing : ChildToParentEvent() + data object PaymentInProgress : ChildToParentEvent() data object PaymentFailed : ChildToParentEvent() data object RetryFailedPaymentClicked : ChildToParentEvent() data object GoBackToCheckoutAfterFailedPayment : ChildToParentEvent() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index f352566836a..f4e7127073f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -117,7 +117,7 @@ class WooPosHomeViewModel @Inject constructor( screenPositionState = ScreenPositionState.Checkout.CartWithTotals ) } - is ChildToParentEvent.PaymentProcessing, + is ChildToParentEvent.PaymentInProgress, is ChildToParentEvent.PaymentFailed -> { _state.value = _state.value.copy( screenPositionState = ScreenPositionState.Checkout.FullScreenTotals diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt index cd3d6319efd..07610f1abe6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsScreen.kt @@ -53,7 +53,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimme import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.Totals.CashPaymentAvailability import com.woocommerce.android.ui.woopos.home.totals.payment.failed.WooPosPaymentFailedScreen -import com.woocommerce.android.ui.woopos.home.totals.payment.processing.WooPosPaymentProcessingScreen +import com.woocommerce.android.ui.woopos.home.totals.payment.inprogress.WooPosPaymentInProgressScreen import com.woocommerce.android.ui.woopos.home.totals.payment.receipt.WooPosTotalsPaymentReceiptScreen import com.woocommerce.android.ui.woopos.home.totals.payment.success.WooPosPaymentSuccessScreen import com.woocommerce.android.ui.woopos.root.navigation.WooPosNavigationEvent @@ -125,9 +125,9 @@ private fun WooPosTotalsScreen( } } - StateChangeAnimated(visible = state is WooPosTotalsViewState.PaymentProcessing) { - if (state is WooPosTotalsViewState.PaymentProcessing) { - WooPosPaymentProcessingScreen(state) + StateChangeAnimated(visible = state is WooPosTotalsViewState.PaymentInProgress) { + if (state is WooPosTotalsViewState.PaymentInProgress) { + WooPosPaymentInProgressScreen(state) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 11068b544c2..93edf07bb33 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -24,7 +24,7 @@ import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceive import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewModel import com.woocommerce.android.ui.woopos.home.items.navigation.WooPosItemsNavigator import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.PaymentFailed -import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.PaymentProcessing +import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.PaymentInProgress import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState.ReceiptSending import com.woocommerce.android.ui.woopos.home.totals.payment.receipt.WooPosTotalsPaymentReceiptIsSendingSupported import com.woocommerce.android.ui.woopos.home.totals.payment.receipt.WooPosTotalsPaymentReceiptIsSendingSupported.Companion.WC_VERSION_SUPPORTS_SENDING_RECEIPTS_BY_EMAIL @@ -282,8 +282,8 @@ class WooPosTotalsViewModel @Inject constructor( is CardReaderPaymentState.ProcessingPayment, is CardReaderPaymentState.PaymentCapturing -> { - uiState.value = buildPaymentProcessingState(paymentState) - childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentProcessing) + uiState.value = buildPaymentInProgressState(paymentState) + childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentInProgress) } is CardReaderPaymentState.PaymentSuccessful -> { @@ -366,12 +366,12 @@ class WooPosTotalsViewModel @Inject constructor( ) } - private fun buildPaymentProcessingState(paymentState: CardReaderPaymentOrRefundState): PaymentProcessing { + private fun buildPaymentInProgressState(paymentState: CardReaderPaymentOrRefundState): PaymentInProgress { val subtitle = when (paymentState) { is CardReaderPaymentState.ProcessingPayment -> R.string.woo_pos_payment_remove_card else -> R.string.woopos_success_totals_payment_processing_subtitle } - return PaymentProcessing( + return PaymentInProgress( title = resourceProvider.getString( R.string.woopos_success_totals_payment_processing_title ), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt index 0d50e2c599d..bfed71e8cbd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewState.kt @@ -64,7 +64,7 @@ sealed class WooPosTotalsViewState : Parcelable { ) } - data class PaymentProcessing( + data class PaymentInProgress( val title: String, val subtitle: String, ) : WooPosTotalsViewState() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/inprogress/WooPosTotalsPaymentInProgressScreen.kt similarity index 93% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/inprogress/WooPosTotalsPaymentInProgressScreen.kt index 3f96b07676b..927753caa77 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/processing/WooPosTotalsPaymentProcessingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/payment/inprogress/WooPosTotalsPaymentInProgressScreen.kt @@ -1,4 +1,4 @@ -package com.woocommerce.android.ui.woopos.home.totals.payment.processing +package com.woocommerce.android.ui.woopos.home.totals.payment.inprogress import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState @@ -35,8 +35,8 @@ import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsViewState import kotlinx.coroutines.delay @Composable -fun WooPosPaymentProcessingScreen( - state: WooPosTotalsViewState.PaymentProcessing, +fun WooPosPaymentInProgressScreen( + state: WooPosTotalsViewState.PaymentInProgress, ) { var enterAnimationStarted by remember { mutableStateOf(false) } LaunchedEffect(Unit) { @@ -91,10 +91,10 @@ fun WooPosPaymentProcessingScreen( @WooPosPreview @Composable -fun WooPosPaymentProcessingScreenPreview() { +fun WooPosPaymentInProgressScreenPreview() { WooPosTheme { - WooPosPaymentProcessingScreen( - state = WooPosTotalsViewState.PaymentProcessing( + WooPosPaymentInProgressScreen( + state = WooPosTotalsViewState.PaymentInProgress( title = "Processing payment", subtitle = "Please wait...", ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index a8ab23b81f9..52064fca867 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -313,7 +313,7 @@ class WooPosHomeViewModelTest { ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) // WHEN - events.emit(ChildToParentEvent.PaymentProcessing) + events.emit(ChildToParentEvent.PaymentInProgress) // THEN assertThat( @@ -332,7 +332,7 @@ class WooPosHomeViewModelTest { assertThat( viewModel.state.value.screenPositionState ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) - events.emit(ChildToParentEvent.PaymentProcessing) + events.emit(ChildToParentEvent.PaymentInProgress) assertThat( viewModel.state.value.screenPositionState ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) @@ -357,7 +357,7 @@ class WooPosHomeViewModelTest { assertThat( viewModel.state.value.screenPositionState ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.CartWithTotals) - events.emit(ChildToParentEvent.PaymentProcessing) + events.emit(ChildToParentEvent.PaymentInProgress) assertThat( viewModel.state.value.screenPositionState ).isEqualTo(WooPosHomeState.ScreenPositionState.Checkout.FullScreenTotals) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt index 15c5aadd7e9..92d5dcdd0e6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModelTest.kt @@ -754,7 +754,7 @@ class WooPosTotalsViewModelTest { paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} // THEN - assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) + assertThat(vm.state.value).isInstanceOf(WooPosTotalsViewState.PaymentInProgress::class.java) } @Test @@ -820,8 +820,8 @@ class WooPosTotalsViewModelTest { paymentState.value = CardReaderPaymentState.PaymentCapturing.ExternalReaderPaymentCapturing("") // THEN - val processingState = vm.state.value as WooPosTotalsViewState.PaymentProcessing - assertThat(processingState).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) + val processingState = vm.state.value as WooPosTotalsViewState.PaymentInProgress + assertThat(processingState).isInstanceOf(WooPosTotalsViewState.PaymentInProgress::class.java) with(processingState) { assertThat(title).isEqualTo("Processing payment") assertThat(subtitle).isEqualTo("Please wait…") @@ -848,8 +848,8 @@ class WooPosTotalsViewModelTest { paymentState.value = CardReaderPaymentState.ProcessingPayment.ExternalReaderProcessingPayment("") {} // THEN - val processingState = vm.state.value as WooPosTotalsViewState.PaymentProcessing - assertThat(processingState).isInstanceOf(WooPosTotalsViewState.PaymentProcessing::class.java) + val processingState = vm.state.value as WooPosTotalsViewState.PaymentInProgress + assertThat(processingState).isInstanceOf(WooPosTotalsViewState.PaymentInProgress::class.java) with(processingState) { assertThat(title).isEqualTo("Processing payment") assertThat(subtitle).isEqualTo("Remove card") From a2054fcaeb56f38c17e3049e0a17a0fc326f2a74 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 12 Dec 2024 20:54:37 +0100 Subject: [PATCH 129/132] Remove unused drawable --- .../drawable/woopos_ic_collect_payment.xml | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 WooCommerce/src/main/res/drawable/woopos_ic_collect_payment.xml diff --git a/WooCommerce/src/main/res/drawable/woopos_ic_collect_payment.xml b/WooCommerce/src/main/res/drawable/woopos_ic_collect_payment.xml deleted file mode 100644 index e5a6f87477c..00000000000 --- a/WooCommerce/src/main/res/drawable/woopos_ic_collect_payment.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - From 085cf3de6ec7f45ece3c33d7b52adbcf48437de3 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 12 Dec 2024 21:06:02 +0100 Subject: [PATCH 130/132] Update release notes --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 1c7e864c2ae..cb6050c0d76 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -12,8 +12,8 @@ - [*] When entering a wrong WordPress.com account for login, retrying will bring the step back to the email input screen [https://github.com/woocommerce/woocommerce-android/pull/13024] - [Internal] Replaces a function in WCSSRExt that then removes the need for commons-io dependency. [https://github.com/woocommerce/woocommerce-android/pull/13073] - [Internal] Removes coupons feature announcement banner [https://github.com/woocommerce/woocommerce-android/pull/13077] - - [Internal] Updated androidx-compose-bom to 2024.09.00 [https://github.com/woocommerce/woocommerce-android/pull/13060] +- [**] Improved UX of card reader payment flow in Point of Sale, allowing faster payment collection [https://github.com/woocommerce/woocommerce-android/pull/12977] ----- 21.2 From b90cc07b0dd5bb726f58ee04b1f43f2bcd9a67dc Mon Sep 17 00:00:00 2001 From: samiuelson Date: Thu, 12 Dec 2024 21:50:57 +0100 Subject: [PATCH 131/132] Extract `isTTPPaymentInProgress` to property delegate --- .../home/totals/TTPPaymentProgressDelegate.kt | 17 +++++++++++++++++ .../woopos/home/totals/WooPosTotalsViewModel.kt | 8 +------- 2 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/TTPPaymentProgressDelegate.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/TTPPaymentProgressDelegate.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/TTPPaymentProgressDelegate.kt new file mode 100644 index 00000000000..6aa8d7e0bff --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/TTPPaymentProgressDelegate.kt @@ -0,0 +1,17 @@ +package com.woocommerce.android.ui.woopos.home.totals + +import androidx.lifecycle.SavedStateHandle +import kotlin.reflect.KProperty + +private const val KEY_TTP_PAYMENT_IN_PROGRESS = "ttp_payment_in_progress" +class TTPPaymentProgressDelegate( + private val savedStateHandle: SavedStateHandle, +) { + operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { + return savedStateHandle.get(KEY_TTP_PAYMENT_IN_PROGRESS) == true + } + + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { + savedStateHandle[KEY_TTP_PAYMENT_IN_PROGRESS] = value + } +} \ No newline at end of file diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index 93edf07bb33..4dd593ceb02 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -50,8 +50,6 @@ import kotlinx.parcelize.Parcelize import java.math.BigDecimal import javax.inject.Inject -private const val KEY_TTP_PAYMENT_IN_PROGRESS = "ttp_payment_in_progress" - @HiltViewModel class WooPosTotalsViewModel @Inject constructor( private val resourceProvider: ResourceProvider, @@ -97,11 +95,7 @@ class WooPosTotalsViewModel @Inject constructor( key = KEY_STATE, ) - private var isTTPPaymentInProgress: Boolean - get() = savedState.get(KEY_TTP_PAYMENT_IN_PROGRESS) == true - set(value) { - savedState[KEY_TTP_PAYMENT_IN_PROGRESS] = value - } + private var isTTPPaymentInProgress: Boolean by TTPPaymentProgressDelegate(savedState) private var cardReaderPaymentController: CardReaderPaymentController? = null From 1c90560adbd51094776b7dea88f2a82dc84a0147 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 13 Dec 2024 08:28:29 +0100 Subject: [PATCH 132/132] Satisfy detekt's complaints --- .../android/ui/woopos/home/totals/TTPPaymentProgressDelegate.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/TTPPaymentProgressDelegate.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/TTPPaymentProgressDelegate.kt index 6aa8d7e0bff..62be170bc85 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/TTPPaymentProgressDelegate.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/TTPPaymentProgressDelegate.kt @@ -14,4 +14,4 @@ class TTPPaymentProgressDelegate( operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { savedStateHandle[KEY_TTP_PAYMENT_IN_PROGRESS] = value } -} \ No newline at end of file +}