Skip to content

Commit

Permalink
fix(ad-hoc): Finish SavedPaymentMethods on DC before transition to Su…
Browse files Browse the repository at this point in the history
…ccess (#260)
  • Loading branch information
vitalii-vanziak-cko authored Feb 5, 2025
1 parent c32ba9e commit ae514b2
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal abstract class BaseBottomSheetDialogFragment<T : Parcelable> : BottomSh
protected abstract var expandable: Boolean
protected abstract val defaultViewHeight: Int
protected val screenHeight by lazy { requireContext().screenSize().height }
protected var animationDurationMillis: Long = 400
protected open val animationDurationMillis: Long = 400

private val bottomSheetDialog by lazy { requireDialog() as BottomSheetDialog }
private val bottomSheetBehavior by lazy { bottomSheetDialog.behavior }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() {
private var pendingAlternativePayment: AlternativePayment? = null

private lateinit var savedPaymentMethodsLauncher: POSavedPaymentMethodsLauncher
private var pendingSavedPaymentMethods = false

private val permissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions(),
Expand Down Expand Up @@ -192,7 +193,7 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() {
savedPaymentMethodsLauncher = POSavedPaymentMethodsLauncher.create(
from = this,
delegate = savedPaymentMethodsDelegate,
callback = {}
callback = { pendingSavedPaymentMethods = false }
)
setContent {
val isLightTheme = !isSystemInDarkTheme()
Expand Down Expand Up @@ -262,9 +263,15 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() {
returnUrl = sideEffect.returnUrl
)
}
is SavedPaymentMethods -> savedPaymentMethodsLauncher.launch(sideEffect.configuration)
is SavedPaymentMethods -> {
pendingSavedPaymentMethods = true
savedPaymentMethodsLauncher.launch(sideEffect.configuration)
}
is PermissionRequest -> requestPermission(sideEffect)
is CancelWebAuthorization -> cancelWebAuthorization()
CancelWebAuthorization -> cancelWebAuthorization()
BeforeSuccess -> if (pendingSavedPaymentMethods) {
savedPaymentMethodsLauncher.finish()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ internal sealed interface DynamicCheckoutSideEffect {
) : DynamicCheckoutSideEffect

data object CancelWebAuthorization : DynamicCheckoutSideEffect

data object BeforeSuccess : DynamicCheckoutSideEffect
}

internal sealed interface DynamicCheckoutCompletion {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,9 @@ internal class DynamicCheckoutInteractor(
}

private fun handleSuccess() {
interactorScope.launch {
_sideEffects.send(DynamicCheckoutSideEffect.BeforeSuccess)
}
configuration.paymentSuccess?.let { paymentSuccess ->
_state.update { it.copy(delayedSuccess = true) }
handler.postDelayed(delayInMillis = paymentSuccess.durationSeconds * 1000L) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
@file:Suppress("MayBeConstant")
@file:Suppress("MayBeConstant", "MemberVisibilityCanBePrivate")

package com.processout.sdk.ui.checkout.screen

import android.view.Gravity
import androidx.annotation.DrawableRes
import androidx.compose.animation.*
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
Expand Down Expand Up @@ -39,7 +38,6 @@ import com.processout.sdk.ui.checkout.DynamicCheckoutViewModelState.*
import com.processout.sdk.ui.checkout.DynamicCheckoutViewModelState.Field.CheckboxField
import com.processout.sdk.ui.checkout.DynamicCheckoutViewModelState.RegularPayment.Content.*
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration
import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.CrossfadeAnimationDurationMillis
import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.LongAnimationDurationMillis
import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.PaymentLogoSize
import com.processout.sdk.ui.checkout.screen.DynamicCheckoutScreen.PaymentSuccessStyle
Expand Down Expand Up @@ -108,85 +106,84 @@ internal fun DynamicCheckoutScreen(
}
}
) { scaffoldPadding ->
Crossfade(
targetState = state is Success,
animationSpec = tween(
durationMillis = CrossfadeAnimationDurationMillis,
easing = LinearEasing
)
) { crossfade ->
if (crossfade && state is Success) {
Success(
message = state.message,
style = style.paymentSuccess
AnimatedContent(
targetState = state,
contentKey = { it::class.java },
transitionSpec = {
fadeIn(
animationSpec = tween(
durationMillis = ShortAnimationDurationMillis,
easing = LinearEasing
)
) togetherWith fadeOut(
animationSpec = tween(
durationMillis = ShortAnimationDurationMillis,
easing = LinearEasing
)
)
} else {
Column(
modifier = Modifier
.fillMaxSize()
.padding(scaffoldPadding)
.verticalScroll(rememberScrollState()),
verticalArrangement = if (state is Loading) Arrangement.Center else Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
when (state) {
is Loading -> Loading(progressIndicatorColor = style.progressIndicatorColor)
is Loaded -> Content(
state = state,
onEvent = onEvent,
style = style,
isLightTheme = isLightTheme
)
else -> {}
}
}
) { state ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(scaffoldPadding)
.verticalScroll(rememberScrollState()),
verticalArrangement = if (state is Loading) Arrangement.Center else Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
when (state) {
is Loading -> POCircularProgressIndicator.Large(color = style.progressIndicatorColor)
is Loaded -> Content(
state = state,
onEvent = onEvent,
style = style,
isLightTheme = isLightTheme
)
is Success -> Success(
message = state.message,
style = style.paymentSuccess
)
}
}
}
}
}
}

@Composable
private fun Loading(progressIndicatorColor: Color) {
AnimatedVisibility {
POCircularProgressIndicator.Large(color = progressIndicatorColor)
}
}

@Composable
private fun Content(
state: Loaded,
onEvent: (DynamicCheckoutEvent) -> Unit,
style: DynamicCheckoutScreen.Style,
isLightTheme: Boolean
) {
AnimatedVisibility {
Column(
modifier = Modifier.padding(spacing.extraLarge)
) {
POMessageBox(
text = state.errorMessage,
style = style.messageBox,
modifier = Modifier.padding(bottom = spacing.large),
horizontalArrangement = Arrangement.spacedBy(RowComponentSpacing),
enterAnimationDelayMillis = ShortAnimationDurationMillis
Column(
modifier = Modifier
.fillMaxSize()
.padding(spacing.extraLarge)
) {
POMessageBox(
text = state.errorMessage,
style = style.messageBox,
modifier = Modifier.padding(bottom = spacing.large),
horizontalArrangement = Arrangement.spacedBy(RowComponentSpacing),
enterAnimationDelayMillis = ShortAnimationDurationMillis
)
state.expressCheckout?.let {
ExpressCheckout(
state = it,
onEvent = onEvent,
style = style,
isLightTheme = isLightTheme
)
}
if (state.regularPayments.elements.isNotEmpty()) {
RegularPayments(
payments = state.regularPayments,
onEvent = onEvent,
style = style,
isLightTheme = isLightTheme
)
state.expressCheckout?.let {
ExpressCheckout(
state = it,
onEvent = onEvent,
style = style,
isLightTheme = isLightTheme
)
}
if (state.regularPayments.elements.isNotEmpty()) {
RegularPayments(
payments = state.regularPayments,
onEvent = onEvent,
style = style,
isLightTheme = isLightTheme
)
}
}
}
}
Expand Down Expand Up @@ -631,7 +628,7 @@ private fun Actions(
onConfirmationRequested = { onEvent(ActionConfirmationRequested(id = it)) },
containerStyle = containerStyle,
confirmationDialogStyle = dialogStyle,
animationDurationMillis = CrossfadeAnimationDurationMillis
animationDurationMillis = ShortAnimationDurationMillis
)
}

Expand All @@ -642,7 +639,7 @@ private fun Success(
) {
Column(
modifier = Modifier
.fillMaxSize()
.fillMaxWidth()
.padding(top = spacing.extraLarge * 2),
verticalArrangement = Arrangement.spacedBy(spacing.extraLarge),
horizontalAlignment = Alignment.CenterHorizontally
Expand All @@ -666,23 +663,6 @@ private fun Success(
}
}

@Composable
private fun AnimatedVisibility(
visibleState: MutableTransitionState<Boolean> = remember {
MutableTransitionState(initialState = false)
.apply { targetState = true }
},
content: @Composable () -> Unit
) {
AnimatedVisibility(
visibleState = visibleState,
enter = fadeIn(animationSpec = tween(durationMillis = ShortAnimationDurationMillis)),
exit = fadeOut(animationSpec = tween(durationMillis = ShortAnimationDurationMillis))
) {
content()
}
}

internal object DynamicCheckoutScreen {

@Immutable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.processout.sdk.ui.savedpaymentmethods

import android.content.Context
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.core.app.ActivityOptionsCompat
Expand All @@ -12,6 +13,7 @@ import com.processout.sdk.api.model.event.POSavedPaymentMethodsEvent
import com.processout.sdk.core.POUnit
import com.processout.sdk.core.ProcessOutActivityResult
import com.processout.sdk.ui.core.annotation.ProcessOutInternalApi
import com.processout.sdk.ui.savedpaymentmethods.SavedPaymentMethodsActivityContract.Companion.EXTRA_FORCE_FINISH
import kotlinx.coroutines.CoroutineScope

/**
Expand All @@ -20,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope
/** @suppress */
@ProcessOutInternalApi
class POSavedPaymentMethodsLauncher private constructor(
private val parentActivity: ComponentActivity,
private val scope: CoroutineScope,
private val launcher: ActivityResultLauncher<POSavedPaymentMethodsConfiguration>,
private val activityOptions: ActivityOptionsCompat,
Expand All @@ -37,6 +40,7 @@ class POSavedPaymentMethodsLauncher private constructor(
delegate: POSavedPaymentMethodsDelegate,
callback: (ProcessOutActivityResult<POUnit>) -> Unit
) = POSavedPaymentMethodsLauncher(
parentActivity = from.requireActivity(),
scope = from.lifecycleScope,
launcher = from.registerForActivityResult(
SavedPaymentMethodsActivityContract(),
Expand All @@ -55,6 +59,7 @@ class POSavedPaymentMethodsLauncher private constructor(
delegate: POSavedPaymentMethodsDelegate,
callback: (ProcessOutActivityResult<POUnit>) -> Unit
) = POSavedPaymentMethodsLauncher(
parentActivity = from,
scope = from.lifecycleScope,
launcher = from.registerForActivityResult(
SavedPaymentMethodsActivityContract(),
Expand Down Expand Up @@ -87,4 +92,15 @@ class POSavedPaymentMethodsLauncher private constructor(
fun launch(configuration: POSavedPaymentMethodsConfiguration) {
launcher.launch(configuration, activityOptions)
}

/**
* Finishes the activity.
*/
fun finish() {
Intent(parentActivity, SavedPaymentMethodsActivity::class.java).let {
it.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
it.putExtra(EXTRA_FORCE_FINISH, true)
parentActivity.startActivity(it)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package com.processout.sdk.ui.savedpaymentmethods

import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import com.processout.sdk.core.POFailure.Code.Cancelled
import com.processout.sdk.core.ProcessOutResult
import com.processout.sdk.ui.base.BaseTransparentPortraitActivity
import com.processout.sdk.ui.savedpaymentmethods.SavedPaymentMethodsActivityContract.Companion.EXTRA_FORCE_FINISH

internal class SavedPaymentMethodsActivity : BaseTransparentPortraitActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.getBooleanExtra(EXTRA_FORCE_FINISH, false)) {
finish()
return
}
enableEdgeToEdge()
if (savedInstanceState == null) {
val bottomSheet = supportFragmentManager.findFragmentByTag(SavedPaymentMethodsBottomSheet.tag)
Expand All @@ -19,4 +27,25 @@ internal class SavedPaymentMethodsActivity : BaseTransparentPortraitActivity() {
}
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.getBooleanExtra(EXTRA_FORCE_FINISH, false)) {
forceFinish()
}
}

private fun forceFinish() {
val bottomSheet = supportFragmentManager.findFragmentByTag(SavedPaymentMethodsBottomSheet.tag)
if (bottomSheet == null) {
finish()
return
}
(bottomSheet as SavedPaymentMethodsBottomSheet).dismiss(
ProcessOutResult.Failure(
code = Cancelled,
message = "Cancelled: finished programmatically."
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal class SavedPaymentMethodsActivityContract : ActivityResultContract
companion object {
const val EXTRA_CONFIGURATION = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_CONFIGURATION"
const val EXTRA_RESULT = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_RESULT"
const val EXTRA_FORCE_FINISH = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_FORCE_FINISH"
}

override fun createIntent(
Expand Down
Loading

0 comments on commit ae514b2

Please sign in to comment.