Skip to content

Commit

Permalink
Fix router on main screen (#24)
Browse files Browse the repository at this point in the history
AmniX authored Oct 2, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 90ce4d0 commit 6538bad
Showing 13 changed files with 104 additions and 81 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -9,6 +9,8 @@

This **Komoju Mobile SDK** allows you to seamlessly integrate secure and reliable payment experiences into your Naive Android and iOS app.

[API DOCS](https://cautious-adventure-g6zje9v.pages.github.io/)

## Get Started

### Android
Original file line number Diff line number Diff line change
@@ -63,7 +63,6 @@ internal class KomojuPaymentActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
val isVisible by viewModel.isVisible.collectAsStateWithLifecycle()
val router by viewModel.router.collectAsStateWithLifecycle()
val animatedAlpha by animateFloatAsState(
targetValue = if (isVisible) .3f else .0f,
label = "scrim_alpha_animation",
@@ -94,7 +93,7 @@ internal class KomojuPaymentActivity : ComponentActivity() {
KomojuPaymentScreen(viewModel.configuration),
) { navigator ->
SlideTransition(navigator)
RouterEffect(router, viewModel::onRouteConsumed)
RouterEffect(viewModel.router.collectAsStateWithLifecycle(), viewModel::onRouteConsumed)
NewIntentEffect(LocalContext.current, viewModel::onNewDeeplink)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.komoju.android.sdk.navigation

import cafe.adriel.voyager.core.model.ScreenModel
import com.komoju.android.sdk.ui.screens.Router
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

internal abstract class RouterStateScreenModel<S>(initialState: S) : ScreenModel {

protected val mutableState: MutableStateFlow<S> = MutableStateFlow(initialState)
val state: StateFlow<S> = mutableState.asStateFlow()

protected val mutableRouter = MutableStateFlow<Router?>(null)
val router = mutableRouter.asStateFlow()

fun onRouteConsumed() {
mutableRouter.value = null
}

protected fun MutableStateFlow<Router?>.pop() {
value = Router.Pop
}
}
Original file line number Diff line number Diff line change
@@ -3,8 +3,11 @@ package com.komoju.android.sdk.ui.screens
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@@ -55,12 +58,14 @@ internal sealed interface KomojuPaymentRoute {
}

@Composable
internal fun RouterEffect(router: Router?, onHandled: () -> Unit) {
internal fun RouterEffect(routerState: State<Router?>, onHandled: () -> Unit) {
val navigator = LocalNavigator.currentOrThrow
val context = LocalContext.current
val router = routerState.value
val backPressDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
LaunchedEffect(router) {
when (router) {
is Router.Pop -> navigator.pop()
is Router.Pop -> if (navigator.pop().not()) backPressDispatcher?.onBackPressed()
is Router.PopAll -> navigator.popAll()
is Router.PopToRoot -> navigator.popUntilRoot()
is Router.Push -> navigator.push(router.route.screen)
Original file line number Diff line number Diff line change
@@ -53,8 +53,7 @@ internal data class KonbiniAwaitingPaymentScreen(val route: KomojuPaymentRoute.K
override fun Content() {
val screenModel = rememberScreenModel { KonbiniAwaitingPaymentScreenModel(route.configuration, route.payment) }
val uiState by screenModel.state.collectAsStateWithLifecycle()
val router by screenModel.router.collectAsStateWithLifecycle()
RouterEffect(router, screenModel::onRouteConsumed)
RouterEffect(screenModel.router.collectAsStateWithLifecycle(), screenModel::onRouteConsumed)
uiState.payment?.let {
PaymentStatus(it, onPrimaryButtonClicked = screenModel::onPrimaryButtonClicked, onSecondaryButtonClicked = screenModel::onSecondaryButtonClicked)
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,32 @@
package com.komoju.android.sdk.ui.screens.awating

import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.komoju.android.sdk.KomojuSDK
import com.komoju.android.sdk.navigation.RouterStateScreenModel
import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute
import com.komoju.android.sdk.ui.screens.Router
import com.komoju.mobile.sdk.entities.Payment
import com.komoju.mobile.sdk.remote.apis.KomojuRemoteApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

internal class KonbiniAwaitingPaymentScreenModel(private val config: KomojuSDK.Configuration, private val payment: Payment? = null) :
StateScreenModel<KonbiniAwaitingPaymentUiState>(KonbiniAwaitingPaymentUiState(payment)) {
RouterStateScreenModel<KonbiniAwaitingPaymentUiState>(KonbiniAwaitingPaymentUiState(payment)) {
private val komojuApi = KomojuRemoteApi(config.publishableKey, config.language.languageCode)
private val _router = MutableStateFlow<Router?>(null)
val router = _router.asStateFlow()

fun onRouteConsumed() {
_router.value = null
}

fun onPrimaryButtonClicked() {
when (val payment = state.value.payment) {
is Payment.Konbini -> _router.value = Router.Push(KomojuPaymentRoute.WebView(payment.instructionURL, canComeBack = true))
is Payment.Konbini -> mutableRouter.value = Router.Push(KomojuPaymentRoute.WebView(payment.instructionURL, canComeBack = true))
else -> Unit
}
}

fun onSecondaryButtonClicked() {
when (val payment = state.value.payment) {
is Payment.Konbini -> refreshPayment()
else -> Unit
}
// when (state.value.payment) {
// is Payment.Konbini -> refreshPayment()
// else -> Unit
// }
mutableRouter.value = Router.Pop
}

fun refreshPayment() {
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.komoju.android.sdk.ui.screens.failed

import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@@ -19,14 +18,15 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import com.komoju.android.sdk.R
import com.komoju.android.sdk.ui.composables.PrimaryButton
import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute
import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme
import com.komoju.android.sdk.ui.screens.RouterEffect
import com.komoju.android.sdk.ui.theme.LocalI18nTexts

internal class PaymentFailedScreen(private val route: KomojuPaymentRoute.PaymentFailed) : Screen {
@@ -43,19 +43,18 @@ enum class Reason {
}

@Composable
private fun PaymentFailedScreenContent(route: KomojuPaymentRoute.PaymentFailed) {
val onBackPressDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
private fun Screen.PaymentFailedScreenContent(route: KomojuPaymentRoute.PaymentFailed) {
val i18nTexts = LocalI18nTexts.current
val screenModel = rememberScreenModel { PaymentFailedScreenModel() }
RouterEffect(screenModel.router.collectAsStateWithLifecycle(), screenModel::onRouteConsumed)
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
Icon(
Icons.Rounded.Clear,
"Close Button",
modifier = Modifier
.padding(16.dp)
.clickable {
onBackPressDispatcher?.onBackPressed()
},
.clickable(onClick = screenModel::onCloseButtonClicked),
)
}
Image(painterResource(R.drawable.komoju_ic_payment_status_failed), "status_icon")
@@ -77,15 +76,7 @@ private fun PaymentFailedScreenContent(route: KomojuPaymentRoute.PaymentFailed)
.padding(16.dp),
text = i18nTexts["BACK_TO_STORE"],
) {
onBackPressDispatcher?.onBackPressed()
screenModel.onBackToStoreButtonClicked()
}
}
}

@Composable
@Preview
private fun PaymentSuccessScreenContentPreview() {
KomojuMobileSdkTheme {
PaymentFailedScreenContent(KomojuPaymentRoute.PaymentFailed(Reason.USER_CANCEL))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.komoju.android.sdk.ui.screens.failed

import com.komoju.android.sdk.navigation.RouterStateScreenModel

internal class PaymentFailedScreenModel : RouterStateScreenModel<Unit>(Unit) {
fun onCloseButtonClicked() {
mutableRouter.pop()
}

fun onBackToStoreButtonClicked() {
mutableRouter.pop()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.komoju.android.sdk.ui.screens.payment

import android.os.Parcelable
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -41,19 +40,16 @@ internal data class KomojuPaymentScreen(private val sdkConfiguration: KomojuSDK.
override fun Content() {
val screenViewModel = rememberScreenModel { KomojuPaymentScreenModel(sdkConfiguration) }
val uiState by screenViewModel.state.collectAsStateWithLifecycle()
val router by screenViewModel.router.collectAsStateWithLifecycle()
val onBackPressDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
LaunchedEffect(sdkConfiguration.sessionId) {
screenViewModel.init()
}
RouterEffect(router, screenViewModel::onRouteHandled)

RouterEffect(screenViewModel.router.collectAsStateWithLifecycle(), screenViewModel::onRouteConsumed)
Box {
if (uiState.session != null) {
Column {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
PaymentSheetHandle(LocalI18nTexts.current["PAYMENT_OPTIONS"], onCloseClicked = {
onBackPressDispatcher?.onBackPressed()
screenViewModel.onCloseClicked()
})
PaymentMethodsRow(
paymentMethods = uiState.session!!.paymentMethods,
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.komoju.android.sdk.ui.screens.payment

import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.komoju.android.sdk.KomojuSDK
import com.komoju.android.sdk.navigation.RouterStateScreenModel
import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute
import com.komoju.android.sdk.ui.screens.Router
import com.komoju.android.sdk.ui.screens.failed.Reason
@@ -21,17 +21,12 @@ import com.komoju.mobile.sdk.entities.SecureTokenResponse.Status.OK
import com.komoju.mobile.sdk.entities.SecureTokenResponse.Status.SKIPPED
import com.komoju.mobile.sdk.entities.SecureTokenResponse.Status.UNKNOWN
import com.komoju.mobile.sdk.remote.apis.KomojuRemoteApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configuration) : StateScreenModel<KomojuPaymentUIState>(KomojuPaymentUIState()) {
internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configuration) : RouterStateScreenModel<KomojuPaymentUIState>(KomojuPaymentUIState()) {
private val komojuApi: KomojuRemoteApi = KomojuRemoteApi(config.publishableKey, config.language.languageCode)

private val _router = MutableStateFlow<Router?>(null)
val router = _router.asStateFlow()

fun init() {
val sessionId = config.sessionId
if (sessionId != null) {
@@ -114,26 +109,26 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat
komojuApi.tokens.generateSecureToken(request).onSuccess {
when (it.status) {
OK, SKIPPED ->
_router.value =
mutableRouter.value =
Router.ReplaceAll(
KomojuPaymentRoute.ProcessPayment(
config,
processType = KomojuPaymentRoute.ProcessPayment.ProcessType.PayByToken(it.id, request.amount, request.currency),
),
)
NEEDS_VERIFY -> _router.value = Router.ReplaceAll(KomojuPaymentRoute.WebView(url = it.authURL, isJavaScriptEnabled = true))
ERRORED, UNKNOWN -> _router.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.CREDIT_CARD_ERROR))
NEEDS_VERIFY -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.WebView(url = it.authURL, isJavaScriptEnabled = true))
ERRORED, UNKNOWN -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.CREDIT_CARD_ERROR))
}
}.onFailure {
_router.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.CREDIT_CARD_ERROR))
mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.CREDIT_CARD_ERROR))
}
}
}

private fun Payment.handle() {
when (this) {
is Payment.Konbini -> _router.value = Router.Replace(KomojuPaymentRoute.KonbiniAwaitingPayment(config, payment = this))
is Payment.PayPay -> _router.value = Router.Handle(url = redirectURL)
is Payment.Konbini -> mutableRouter.value = Router.Replace(KomojuPaymentRoute.KonbiniAwaitingPayment(config, payment = this))
is Payment.PayPay -> mutableRouter.value = Router.Handle(url = redirectURL)
else -> Unit
}
}
@@ -219,7 +214,7 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat
is PaymentMethod.WebMoney -> TODO()
}

fun onRouteHandled() {
_router.value = null
fun onCloseClicked() {
mutableRouter.pop()
}
}
Original file line number Diff line number Diff line change
@@ -24,11 +24,10 @@ internal class ProcessPaymentScreen(private val route: KomojuPaymentRoute.Proces
@Composable
private fun Screen.VerifyPaymentScreenContent(route: KomojuPaymentRoute.ProcessPayment) {
val screenViewModel = rememberScreenModel { VerifyPaymentScreenModel(route.configuration) }
val router by screenViewModel.router.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
screenViewModel.process(route.processType)
}
RouterEffect(router, screenViewModel::onRouteHandled)
RouterEffect(screenViewModel.router.collectAsStateWithLifecycle(), screenViewModel::onRouteConsumed)
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
ThemedCircularProgressIndicator()
}
Loading

0 comments on commit 6538bad

Please sign in to comment.