diff --git a/build.gradle b/build.gradle index 3e563df32..de0b7774a 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension buildscript { ext { - androidGradlePluginVersion = '9.3.1' + androidGradlePluginVersion = '9.4.1' kotlinVersion = '2.1.20' kspVersion = '2.1.20-1.0.32' dokkaVersion = '1.9.20' diff --git a/sdk/src/main/AndroidManifest.xml b/sdk/src/main/AndroidManifest.xml index 59aaab951..b318e16c0 100644 --- a/sdk/src/main/AndroidManifest.xml +++ b/sdk/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ @@ -39,7 +39,7 @@ diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt index 738502c1a..092526f71 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt @@ -2,9 +2,7 @@ package com.processout.sdk.ui.web.customtab import android.app.Activity import android.content.Intent -import android.content.pm.ActivityInfo import android.net.Uri -import android.os.Build import android.os.Bundle import androidx.activity.addCallback import androidx.activity.viewModels @@ -61,12 +59,6 @@ class POCustomTabAuthorizationActivity : AppCompatActivity() { @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // This is a workaround for crash on Android 8.0 (API level 26). - // https://issuetracker.google.com/issues/68454482 - // https://stackoverflow.com/questions/48072438/java-lang-illegalstateexception-only-fullscreen-opaque-activities-can-request-o - requestedOrientation = if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) - ActivityInfo.SCREEN_ORIENTATION_BEHIND else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - onBackPressedDispatcher.addCallback(this) { // Ignore back press to avoid finishing activity without a result. // Cancelled result will be provided from onResume() when going back from the Custom Tab. @@ -168,8 +160,8 @@ class POCustomTabAuthorizationActivity : AppCompatActivity() { result.onFailure { POLogger.info("Custom Chrome Tabs failure: %s", it) } when (configuration.resultApi) { Android -> when (result) { - is ProcessOutActivityResult.Success -> setActivityResult(Activity.RESULT_OK, result) - is ProcessOutActivityResult.Failure -> setActivityResult(Activity.RESULT_CANCELED, result) + is ProcessOutActivityResult.Success -> setActivityResult(RESULT_OK, result) + is ProcessOutActivityResult.Failure -> setActivityResult(RESULT_CANCELED, result) } Dispatcher -> resultDispatcher.dispatch(result) } diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt index 93a4ed1e1..1b43e43ba 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt @@ -1,8 +1,6 @@ package com.processout.sdk.ui.web.webview -import android.app.Activity import android.content.Intent -import android.content.pm.ActivityInfo import android.net.Uri import android.os.Build import android.os.Bundle @@ -33,12 +31,6 @@ class POWebViewAuthorizationActivity : AppCompatActivity() { @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // This is a workaround for crash on Android 8.0 (API level 26). - // https://issuetracker.google.com/issues/68454482 - // https://stackoverflow.com/questions/48072438/java-lang-illegalstateexception-only-fullscreen-opaque-activities-can-request-o - requestedOrientation = if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) - ActivityInfo.SCREEN_ORIENTATION_BEHIND else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - if (intent.getBooleanExtra(EXTRA_FORCE_FINISH, false)) { POLogger.info("Activity is started to clear the back stack and finished immediately before it's created.") finish() @@ -87,8 +79,8 @@ class POWebViewAuthorizationActivity : AppCompatActivity() { if (!isFinishing) { when (configuration.resultApi) { Android -> when (result) { - is ProcessOutActivityResult.Success -> setActivityResult(Activity.RESULT_OK, result) - is ProcessOutActivityResult.Failure -> setActivityResult(Activity.RESULT_CANCELED, result) + is ProcessOutActivityResult.Success -> setActivityResult(RESULT_OK, result) + is ProcessOutActivityResult.Failure -> setActivityResult(RESULT_CANCELED, result) } Dispatcher -> resultDispatcher.dispatch(result) } @@ -104,7 +96,7 @@ class POWebViewAuthorizationActivity : AppCompatActivity() { super.finish() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { overrideActivityTransition( - Activity.OVERRIDE_TRANSITION_CLOSE, + OVERRIDE_TRANSITION_CLOSE, R.anim.po_slide_in_left, R.anim.po_slide_out_right ) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationInteractor.kt index f2dd1936d..fd00e1fa8 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationInteractor.kt @@ -60,7 +60,7 @@ internal class CardTokenizationInteractor( private val cardsRepository: POCardsRepository, private val cardSchemeProvider: CardSchemeProvider, private val addressSpecificationProvider: AddressSpecificationProvider, - private val eventDispatcher: POEventDispatcher + val eventDispatcher: POEventDispatcher ) : BaseInteractor() { private companion object { diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt index b1783648b..124dbcc12 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt @@ -74,6 +74,8 @@ internal class CardTokenizationViewModel private constructor( val sideEffects = interactor.sideEffects + val eventDispatcher = interactor.eventDispatcher + init { addCloseable(interactor.interactorScope) } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt index 2a395b794..ce1cb32b8 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt @@ -37,6 +37,7 @@ import com.processout.sdk.ui.card.tokenization.screen.CardTokenizationScreen import com.processout.sdk.ui.core.theme.ProcessOutTheme import com.processout.sdk.ui.shared.configuration.POBottomSheetConfiguration import com.processout.sdk.ui.shared.configuration.POBottomSheetConfiguration.Height.WrapContent +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -50,15 +51,12 @@ class POCardTokenizationViewComponent private constructor( val view: View, private var configuration: POCardTokenizationConfiguration, private val viewModel: CardTokenizationViewModel, - private val lifecycleOwner: LifecycleOwner, private val cardScannerLauncher: POCardScannerLauncher, private val delegate: POCardTokenizationDelegate, private val callback: (ProcessOutResult) -> Unit, private val eventDispatcher: POEventDispatcher ) { - private val scope = lifecycleOwner.lifecycleScope - companion object { /** * Creates the card tokenization view component from a Fragment. @@ -71,12 +69,11 @@ class POCardTokenizationViewComponent private constructor( callback: (ProcessOutResult) -> Unit ): POCardTokenizationViewComponent { val configuration = configuration.map() - val eventDispatcher = POEventDispatcher() val viewModel: CardTokenizationViewModel by from.viewModels { CardTokenizationViewModel.Factory( app = from.requireActivity().application, configuration = configuration, - eventDispatcher = eventDispatcher + eventDispatcher = POEventDispatcher() ) } return POCardTokenizationViewComponent( @@ -87,7 +84,6 @@ class POCardTokenizationViewComponent private constructor( ), configuration = configuration, viewModel = viewModel, - lifecycleOwner = from, cardScannerLauncher = POCardScannerLauncher.create( from = from, callback = { result -> @@ -96,8 +92,12 @@ class POCardTokenizationViewComponent private constructor( ), delegate = delegate, callback = callback, - eventDispatcher = eventDispatcher - ) + eventDispatcher = viewModel.eventDispatcher + ).apply { + from.viewLifecycleOwnerLiveData.observe(from) { lifecycleOwner -> + init(lifecycleOwner) + } + } } /** @@ -111,12 +111,11 @@ class POCardTokenizationViewComponent private constructor( callback: (ProcessOutResult) -> Unit ): POCardTokenizationViewComponent { val configuration = configuration.map() - val eventDispatcher = POEventDispatcher() val viewModel: CardTokenizationViewModel by from.viewModels { CardTokenizationViewModel.Factory( app = from.application, configuration = configuration, - eventDispatcher = eventDispatcher + eventDispatcher = POEventDispatcher() ) } return POCardTokenizationViewComponent( @@ -127,7 +126,6 @@ class POCardTokenizationViewComponent private constructor( ), configuration = configuration, viewModel = viewModel, - lifecycleOwner = from, cardScannerLauncher = POCardScannerLauncher.create( from = from, callback = { result -> @@ -136,8 +134,10 @@ class POCardTokenizationViewComponent private constructor( ), delegate = delegate, callback = callback, - eventDispatcher = eventDispatcher - ) + eventDispatcher = viewModel.eventDispatcher + ).apply { + init(lifecycleOwner = from) + } } private fun POCardTokenizationViewComponentConfiguration.map() = @@ -180,35 +180,36 @@ class POCardTokenizationViewComponent private constructor( } } - init { - dispatchEvents() - dispatchState() - dispatchTokenizedCard() - dispatchEligibility() - dispatchPreferredScheme() - dispatchShouldContinue() - collectSideEffects() - collectCompletion() + private fun init(lifecycleOwner: LifecycleOwner) { + val coroutineScope = lifecycleOwner.lifecycleScope + dispatchEvents(coroutineScope) + dispatchState(coroutineScope) + dispatchTokenizedCard(coroutineScope) + dispatchEligibility(coroutineScope) + dispatchPreferredScheme(coroutineScope) + dispatchShouldContinue(coroutineScope) + collectSideEffects(lifecycleOwner) + collectCompletion(lifecycleOwner) viewModel.start() } - private fun dispatchEvents() { + private fun dispatchEvents(coroutineScope: CoroutineScope) { eventDispatcher.subscribe( - coroutineScope = scope + coroutineScope ) { delegate.onEvent(it) } } - private fun dispatchState() { + private fun dispatchState(coroutineScope: CoroutineScope) { eventDispatcher.subscribe( - coroutineScope = scope + coroutineScope ) { delegate.onStateChanged(it) } } - private fun dispatchTokenizedCard() { + private fun dispatchTokenizedCard(coroutineScope: CoroutineScope) { eventDispatcher.subscribeForRequest( - coroutineScope = scope + coroutineScope ) { request -> - scope.launch { + coroutineScope.launch { val result = delegate.processTokenizedCard( card = request.card, saveCard = request.saveCard @@ -218,11 +219,11 @@ class POCardTokenizationViewComponent private constructor( } } - private fun dispatchEligibility() { + private fun dispatchEligibility(coroutineScope: CoroutineScope) { eventDispatcher.subscribeForRequest( - coroutineScope = scope + coroutineScope ) { request -> - scope.launch { + coroutineScope.launch { val eligibility = delegate.evaluateEligibility( iin = request.iin, issuerInformation = request.issuerInformation @@ -232,30 +233,30 @@ class POCardTokenizationViewComponent private constructor( } } - private fun dispatchPreferredScheme() { + private fun dispatchPreferredScheme(coroutineScope: CoroutineScope) { eventDispatcher.subscribeForRequest( - coroutineScope = scope + coroutineScope ) { request -> - scope.launch { + coroutineScope.launch { val preferredScheme = delegate.preferredScheme(request.issuerInformation) eventDispatcher.send(request.toResponse(preferredScheme)) } } } - private fun dispatchShouldContinue() { + private fun dispatchShouldContinue(coroutineScope: CoroutineScope) { eventDispatcher.subscribeForRequest( - coroutineScope = scope + coroutineScope ) { request -> - scope.launch { + coroutineScope.launch { val shouldContinue = delegate.shouldContinue(request.failure) eventDispatcher.send(request.toResponse(shouldContinue)) } } } - private fun collectSideEffects() { - scope.launch { + private fun collectSideEffects(lifecycleOwner: LifecycleOwner) { + lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.STARTED) { withContext(Dispatchers.Main.immediate) { viewModel.sideEffects.collect { sideEffect -> @@ -270,8 +271,8 @@ class POCardTokenizationViewComponent private constructor( } } - private fun collectCompletion() { - scope.launch { + private fun collectCompletion(lifecycleOwner: LifecycleOwner) { + lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.STARTED) { viewModel.completion.collect { completion -> when (completion) {