Skip to content

Commit

Permalink
Fix router on main screen (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
AmniX authored Oct 2, 2024
1 parent 90ce4d0 commit 6538bad
Show file tree
Hide file tree
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
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
}
}
Expand Down
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
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
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() {
Expand Down
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
Expand All @@ -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 {
Expand All @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
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
Expand All @@ -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) {
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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
Expand Up @@ -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()
}
Expand Down
Loading

0 comments on commit 6538bad

Please sign in to comment.