Skip to content

Commit

Permalink
Merge pull request #13100 from woocommerce/custom-payment-ui-payment-…
Browse files Browse the repository at this point in the history
…processing-animation

[POS][Custom payment UI] – Payment processing animation with Lottie
  • Loading branch information
malinajirka authored Dec 12, 2024
2 parents b1a4dd5 + 089bc61 commit e9d9360
Show file tree
Hide file tree
Showing 14 changed files with 4,535 additions and 100 deletions.
3 changes: 3 additions & 0 deletions WooCommerce/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,9 @@ dependencies {

coreLibraryDesugaring(libs.android.desugar)

// Lottie
implementation(libs.lottie.compose)

// CameraX
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -197,6 +198,7 @@ private val DarkCustomColors = CustomColors(
paymentSuccessBackground = WooPosColors.darkCustomColorsHomeBackground,
paymentSuccessText = WooPosColors.oldGrayLight,
paymentSuccessIcon = WooPosColors.darkCustomColorsHomeBackground,
paymentProcessingText = WooPosColors.White,
homeBackground = WooPosColors.darkCustomColorsHomeBackground,
paymentProcessingBackground = WooPosColors.WooPurple70,
)
Expand All @@ -210,6 +212,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -47,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
Expand Down Expand Up @@ -119,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)
}
}

Expand Down Expand Up @@ -237,11 +243,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),
composition = tapCardAnimation,
clipSpec = LottieClipSpec.Markers("reader_awaiting_start", "reader_awaiting_end"),
iterations = LottieConstants.IterateForever,
)
Spacer(modifier = Modifier.height(20.dp.toAdaptivePadding()))
Text(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -281,17 +281,16 @@ class WooPosTotalsViewModel @Inject constructor(
is CardReaderPaymentState.LoadingData -> handleReaderLoadingPaymentState()

is CardReaderPaymentState.ProcessingPayment,
is CardReaderPaymentState.PaymentCapturing,
CardReaderPaymentState.ReFetchingOrder -> {
uiState.value = buildPaymentProcessingState()
childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentProcessing)
is CardReaderPaymentState.PaymentCapturing -> {
uiState.value = buildPaymentInProgressState(paymentState)
childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentInProgress)
}

is CardReaderPaymentState.PaymentSuccessful -> {
wooPosItemsNavigator.sendNavigationEvent(
WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateBackToItemListScreen
)
showSuccessfulPaymentState()
showSuccessfulPaymentState(paymentState)
childrenToParentEventSender.sendToParent(ChildToParentEvent.OrderSuccessfullyPaid)
}

Expand All @@ -300,6 +299,8 @@ class WooPosTotalsViewModel @Inject constructor(
childrenToParentEventSender.sendToParent(ChildToParentEvent.PaymentFailed)
}

CardReaderPaymentState.ReFetchingOrder -> Unit

is CardReaderPaymentOrRefundState.CardReaderInteracRefundState,
is CardReaderPaymentState.PaymentFailed.BuiltInReaderFailedPayment,
is CardReaderPaymentState.PrintingReceipt,
Expand Down Expand Up @@ -365,14 +366,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 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 PaymentInProgress(
title = resourceProvider.getString(
R.string.woopos_success_totals_payment_processing_title
),
subtitle = resourceProvider.getString(subtitle)
)
)
}

override fun onCleared() {
cardReaderPaymentController?.stop()
Expand Down Expand Up @@ -425,6 +430,16 @@ class WooPosTotalsViewModel @Inject constructor(
}
}

private fun showSuccessfulPaymentState(cardPaymentSuccess: CardReaderPaymentState.PaymentSuccessful) {
viewModelScope.launch {
val orderTotalText = cardPaymentSuccess.amountWithCurrencyLabel
uiState.value = WooPosTotalsViewState.PaymentSuccess(
orderTotalText = orderTotalText,
isReceiptAvailable = isReceiptsEnabled()
)
}
}

private suspend fun buildWooPosTotalsViewState(order: Order): WooPosTotalsViewState.Totals {
val subtotalAmount = order.productsTotal
val taxAmount = order.totalTax
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ sealed class WooPosTotalsViewState : Parcelable {
)
}

data class PaymentProcessing(
data class PaymentInProgress(
val title: String,
val subtitle: String,
) : WooPosTotalsViewState()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.woocommerce.android.ui.woopos.home.totals.payment.inprogress

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
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.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
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
import kotlinx.coroutines.delay

@Composable
fun WooPosPaymentInProgressScreen(
state: WooPosTotalsViewState.PaymentInProgress,
) {
var enterAnimationStarted by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(50)
enterAnimationStarted = true
}
Box(
modifier = Modifier
.background(color = WooPosTheme.colors.paymentProcessingBackground)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding()))
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.woopos_card_ilustration))
LottieAnimation(
modifier = Modifier.size(256.dp),
composition = composition,
iterations = LottieConstants.IterateForever,
clipToCompositionBounds = false,
clipSpec = LottieClipSpec.Markers("payment_processing_start", "payment_processing_end")
)
val marginBetweenAnimatedIconAndText by animateDpAsState(
targetValue = if (enterAnimationStarted) 0.dp else 256.dp,
)
Spacer(modifier = Modifier.height(marginBetweenAnimatedIconAndText.toAdaptivePadding()))
AnimatedVisibility(visible = enterAnimationStarted) {
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()))
}
}
}

@WooPosPreview
@Composable
fun WooPosPaymentInProgressScreenPreview() {
WooPosTheme {
WooPosPaymentInProgressScreen(
state = WooPosTotalsViewState.PaymentInProgress(
title = "Processing payment",
subtitle = "Please wait...",
)
)
}
}

This file was deleted.

Loading

0 comments on commit e9d9360

Please sign in to comment.