diff --git a/android/build.gradle.kts b/android/build.gradle.kts index b741c24..f1ef6bd 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -52,30 +52,20 @@ android { } dependencies { - api(project(":shared")) + implementation(project(":shared")) implementation(libs.core.ktx) implementation(libs.androidx.fragment.ktx) implementation(libs.appcompat) implementation(libs.material) + implementation(libs.activity.compose) implementation(libs.kotlinx.coroutines.android) - implementation(libs.kotlinx.datetime) - implementation(libs.kotlinx.serialization.json) - implementation(libs.lifecycle.runtime.ktx) implementation(libs.lifecycle.viewmodel.compose) - implementation(libs.activity.compose) - implementation(platform(libs.compose.bom)) - implementation(libs.ui) - implementation(libs.ui.graphics) - implementation(libs.ui.tooling.preview) - implementation(libs.material3) - implementation(libs.compose.webview) implementation(libs.voyager.navigator) implementation(libs.voyager.screenModel) - implementation(libs.voyager.transitions) - implementation(libs.androidx.browser) - testImplementation(libs.junit) - androidTestImplementation(libs.ext.junit) - androidTestImplementation(libs.espresso.core) + implementation(libs.ui.graphics) + implementation(libs.material3) + implementation(platform(libs.compose.bom)) + implementation(libs.ui) debugImplementation(libs.androidx.ui.tooling) } diff --git a/android/src/main/java/com/komoju/android/sdk/KomojuAndroidSDK.kt b/android/src/main/java/com/komoju/android/sdk/KomojuAndroidSDK.kt new file mode 100644 index 0000000..bea751f --- /dev/null +++ b/android/src/main/java/com/komoju/android/sdk/KomojuAndroidSDK.kt @@ -0,0 +1,176 @@ +package com.komoju.android.sdk + +import android.os.Parcelable +import androidx.activity.result.contract.ActivityResultContract +import androidx.compose.ui.graphics.toArgb +import com.komoju.android.sdk.annotations.ExperimentalKomojuPaymentApi +import com.komoju.android.sdk.types.Currency +import com.komoju.android.sdk.types.Language +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration +import com.komoju.mobile.sdk.KomojuMobileSDKPaymentResult +import com.komoju.mobile.sdk.canProcessPayment +import com.komoju.mobile.sdk.ui.theme.DefaultConfigurableTheme +import com.komoju.mobile.sdk.ui.theme.toColor +import kotlinx.parcelize.Parcelize +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract +import com.komoju.mobile.sdk.ui.theme.ConfigurableTheme as CoreConfigurableTheme + +object KomojuAndroidSDK { + val activityResultContract: ActivityResultContract + get() = KomojuStartPaymentForResultContract() + + /** + * Configuration class to hold parameters required for payment processing. + */ + @Parcelize + data class Configuration( + val language: Language, // Language setting for the payment UI. + val currency: Currency, // Currency used in the transaction. + val publishableKey: String?, // Public API key for Komoju integration. + val isDebugMode: Boolean, // Debug mode flag for logging and testing. + val sessionId: String?, // Unique session ID for payment transaction. + val redirectURL: String, // URL to redirect after payment completion. + val appScheme: String, // App schema for deep links. + val configurableTheme: ConfigurableTheme, // Custom theme for UI elements. + val inlinedProcessing: Boolean, // Flag to enable inlined processing. + ) : Parcelable { + + /** + * Builder class for creating a [Configuration] instance. + * Offers a flexible way to set optional parameters. + */ + class Builder(private val publishableKey: String, private val sessionId: String) { + private var language: Language = Language.JAPANESE // Default language is Japanese. + private var currency: Currency = Currency.JPY // Default currency is Japanese Yen. + private var isDebugMode: Boolean = false // Debug mode is off by default. + private var configurableTheme: ConfigurableTheme = + ConfigurableTheme.default // Custom theme for UI elements. + private var inlinedProcessing: Boolean = false // Inlined processing is off by default. + + /** Sets the language for the payment. */ + fun setLanguage(language: Language) = apply { + this.language = language + } + + /** Sets the currency for the transaction. */ + fun setCurrency(currency: Currency) = apply { + this.currency = currency + } + + /** Enables or disables debug mode. */ + fun setDebugMode(isDebugMode: Boolean) = apply { + this.isDebugMode = isDebugMode + } + + /** Sets the custom theme for the payment UI. */ + fun setConfigurableTheme(configurableTheme: ConfigurableTheme) = apply { + this.configurableTheme = configurableTheme + } + + /** + * WARNING: Experimental API [Try this only if you are sure] Disabled by Default. + * + * This API enables or disables inlined processing. + * If this is enabled then The SDK will try to do processing with minimum amount of screens. + * + * For e.g. + * * If PayPay Payment id captured, it will close the SDK ASAP it verifies the payment. + * * When you will try to pay with Credit Card and Second step verification is not required, SDK will never show the WebView and will handle the callback itself. + * + */ + @ExperimentalKomojuPaymentApi + fun setInlinedProcessing(inlinedProcessing: Boolean) = apply { + this.inlinedProcessing = inlinedProcessing + } + + /** + * Builds the [Configuration] instance with the provided settings. + */ + fun build(): Configuration = Configuration( + language = language, + currency = currency, + publishableKey = publishableKey, + sessionId = sessionId, + isDebugMode = isDebugMode, + configurableTheme = configurableTheme, + inlinedProcessing = inlinedProcessing, + appScheme = "", + redirectURL = "", + ) + } + } + + @Parcelize + data class ConfigurableTheme( + val primaryColorInt: Int, + val primaryContentColorInt: Int, + val loaderColorInt: Int, + val primaryShapeCornerRadiusInDp: Int, + ) : Parcelable { + companion object { + val default: ConfigurableTheme = com.komoju.mobile.sdk.ui.theme.ConfigurableTheme.default.toAndroidSDKConfigurableTheme() + } + } + + @Parcelize + data class PaymentResult(val isSuccessFul: Boolean) : Parcelable +} + +internal fun KomojuAndroidSDK.ConfigurableTheme.toKomojuConfigurableTheme(): CoreConfigurableTheme = DefaultConfigurableTheme( + primaryColor = primaryColorInt.toLong(), + primaryContentColor = primaryContentColorInt.toLong(), + loaderColor = loaderColorInt.toLong(), + primaryShapeCornerRadiusInDp = this.primaryShapeCornerRadiusInDp, +) + +internal fun CoreConfigurableTheme.toAndroidSDKConfigurableTheme(): KomojuAndroidSDK.ConfigurableTheme = + KomojuAndroidSDK.ConfigurableTheme( + primaryColorInt = primaryColor.toColor().toArgb(), + primaryContentColorInt = primaryContentColor.toColor().toArgb(), + loaderColorInt = loaderColor.toColor().toArgb(), + primaryShapeCornerRadiusInDp = primaryShapeCornerRadiusInDp, + ) + + +/** + * Extension function to check if the current configuration is valid for processing a payment. + * @return True if the configuration is non-null and contains both publishableKey and sessionId. + */ +@OptIn(ExperimentalContracts::class) +fun KomojuAndroidSDK.Configuration?.canProcessPayment(): Boolean { + contract { + returns(true) implies (this@canProcessPayment != null) + } + return this?.toMobileConfiguration().canProcessPayment() +} + +internal fun KomojuMobileSDKConfiguration.toAndroidConfiguration(): KomojuAndroidSDK.Configuration = KomojuAndroidSDK.Configuration( + language = Language.parse(this.language), + currency = Currency.parse(this.currency), + publishableKey = this.publishableKey, + isDebugMode = this.isDebugMode, + sessionId = this.sessionId, + redirectURL = this.redirectURL, + configurableTheme = this.configurableTheme.toAndroidSDKConfigurableTheme(), + inlinedProcessing = this.inlinedProcessing, + appScheme = this.appScheme, +) + +internal fun KomojuAndroidSDK.Configuration.toMobileConfiguration(): KomojuMobileSDKConfiguration = KomojuMobileSDKConfiguration( + language = this.language.languageCode, + currency = this.currency.currencyCode, + publishableKey = this.publishableKey, + isDebugMode = this.isDebugMode, + sessionId = this.sessionId, + redirectURL = this.redirectURL, + configurableTheme = this.configurableTheme.toKomojuConfigurableTheme(), + inlinedProcessing = this.inlinedProcessing, + appScheme = this.appScheme, +) + + +internal fun KomojuMobileSDKPaymentResult.toParcelable(): KomojuAndroidSDK.PaymentResult = KomojuAndroidSDK.PaymentResult(this.isSuccessFul) + +internal fun KomojuAndroidSDK.PaymentResult.toPaymentResult(): KomojuMobileSDKPaymentResult = KomojuMobileSDKPaymentResult(this.isSuccessFul) + diff --git a/android/src/main/java/com/komoju/android/sdk/KomojuPaymentActivity.kt b/android/src/main/java/com/komoju/android/sdk/KomojuPaymentActivity.kt index b116936..d0ad7a6 100644 --- a/android/src/main/java/com/komoju/android/sdk/KomojuPaymentActivity.kt +++ b/android/src/main/java/com/komoju/android/sdk/KomojuPaymentActivity.kt @@ -14,7 +14,6 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding @@ -30,13 +29,10 @@ import androidx.core.content.IntentCompat import androidx.core.util.Consumer import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope -import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.transitions.SlideTransition -import com.komoju.android.sdk.navigation.PaymentResultScreenModel -import com.komoju.android.sdk.navigation.paymentResultScreenModel -import com.komoju.android.sdk.ui.screens.RouterEffect -import com.komoju.android.sdk.ui.screens.payment.KomojuPaymentScreen -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme +import com.komoju.mobile.sdk.navigation.PaymentResultScreenModel +import com.komoju.mobile.sdk.navigation.paymentResultScreenModel +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentEntryPoint +import com.komoju.mobile.sdk.ui.screens.RouterEffect import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay import kotlinx.coroutines.flow.callbackFlow @@ -55,7 +51,7 @@ internal class KomojuPaymentActivity : ComponentActivity() { /* name = */ KomojuStartPaymentForResultContract.CONFIGURATION_KEY, /* clazz = */ - KomojuSDK.Configuration::class.java, + KomojuAndroidSDK.Configuration::class.java, ) ?: error("komoju sdk configuration is null"), ) }, @@ -87,21 +83,10 @@ internal class KomojuPaymentActivity : ComponentActivity() { .navigationBarsPadding(), contentAlignment = Alignment.BottomCenter, ) { - KomojuMobileSdkTheme(viewModel.configuration) { - Box( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(.9f), - ) { - Navigator( - KomojuPaymentScreen(viewModel.configuration), - ) { navigator -> - commonScreenModel = navigator.paymentResultScreenModel() - SlideTransition(navigator) - RouterEffect(viewModel.router.collectAsStateWithLifecycle(), viewModel::onRouteConsumed) - NewIntentEffect(LocalContext.current, viewModel::onNewDeeplink) - } - } + KomojuPaymentEntryPoint(viewModel.configuration) { navigator -> + RouterEffect(viewModel.router.collectAsStateWithLifecycle(), viewModel::onRouteConsumed) + commonScreenModel = navigator.paymentResultScreenModel() + NewIntentEffect(LocalContext.current, viewModel::onNewDeeplink) } } } @@ -128,7 +113,7 @@ internal class KomojuPaymentActivity : ComponentActivity() { setResult( RESULT_OK, Intent().apply { - putExtra(KomojuStartPaymentForResultContract.RESULT_KEY, commonScreenModel?.result) + putExtra(KomojuStartPaymentForResultContract.RESULT_KEY, commonScreenModel?.result?.toParcelable()) }, ) lifecycleScope.launch { diff --git a/android/src/main/java/com/komoju/android/sdk/KomojuPaymentViewModel.kt b/android/src/main/java/com/komoju/android/sdk/KomojuPaymentViewModel.kt index 04ce1c9..b9e37f5 100644 --- a/android/src/main/java/com/komoju/android/sdk/KomojuPaymentViewModel.kt +++ b/android/src/main/java/com/komoju/android/sdk/KomojuPaymentViewModel.kt @@ -2,13 +2,14 @@ package com.komoju.android.sdk import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute -import com.komoju.android.sdk.ui.screens.Router -import com.komoju.android.sdk.utils.DeeplinkEntity +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentRoute +import com.komoju.mobile.sdk.ui.screens.Router +import com.komoju.mobile.sdk.utils.DeeplinkEntity import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -internal class KomojuPaymentViewModel(internal val configuration: KomojuSDK.Configuration) : ViewModel() { +internal class KomojuPaymentViewModel(internal val configuration: KomojuMobileSDKConfiguration) : ViewModel() { private val _isVisible = MutableStateFlow(false) val isVisible = _isVisible.asStateFlow() @@ -35,6 +36,7 @@ internal class KomojuPaymentViewModel(internal val configuration: KomojuSDK.Conf amount = deeplinkEntity.amount, currency = deeplinkEntity.currency, ) + DeeplinkEntity.Verify.BySessionId -> KomojuPaymentRoute.ProcessPayment.ProcessType.Session }, ), @@ -42,9 +44,10 @@ internal class KomojuPaymentViewModel(internal val configuration: KomojuSDK.Conf } } -internal class KomojuPaymentViewModelFactory(private val configuration: KomojuSDK.Configuration) : ViewModelProvider.NewInstanceFactory() { +internal class KomojuPaymentViewModelFactory(private val configuration: KomojuAndroidSDK.Configuration) : + ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T { @Suppress("UNCHECKED_CAST") - return KomojuPaymentViewModel(configuration) as T + return KomojuPaymentViewModel(configuration.toMobileConfiguration()) as T } } diff --git a/android/src/main/java/com/komoju/android/sdk/KomojuSDK.kt b/android/src/main/java/com/komoju/android/sdk/KomojuSDK.kt deleted file mode 100644 index c1d2e8b..0000000 --- a/android/src/main/java/com/komoju/android/sdk/KomojuSDK.kt +++ /dev/null @@ -1,179 +0,0 @@ -package com.komoju.android.sdk - -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.Parcelable -import androidx.activity.result.contract.ActivityResultContract -import com.komoju.android.sdk.types.Currency -import com.komoju.android.sdk.types.Language -import com.komoju.android.sdk.ui.theme.ConfigurableTheme -import com.komoju.android.sdk.utils.empty -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract -import kotlinx.parcelize.Parcelize - -/** - * Main entry point for Komoju SDK integration. - * Provides configuration setup and payment processing contract. - */ -object KomojuSDK { - - /** - * Configuration class to hold parameters required for payment processing. - * Implements [Parcelable] to allow passing data between activities. - */ - @Parcelize - data class Configuration( - internal val language: Language, // Language setting for the payment UI. - internal val currency: Currency, // Currency used in the transaction. - internal val publishableKey: String?, // Public API key for Komoju integration. - internal val isDebugMode: Boolean, // Debug mode flag for logging and testing. - internal val sessionId: String?, // Unique session ID for payment transaction. - internal val redirectURL: String = String.empty, // URL to redirect after payment completion. - internal val configurableTheme: ConfigurableTheme, // Custom theme for UI elements. - internal val inlinedProcessing: Boolean, // Flag to enable inlined processing. - ) : Parcelable { - - /** - * Builder class for creating a [Configuration] instance. - * Offers a flexible way to set optional parameters. - */ - class Builder(private val publishableKey: String, private val sessionId: String) { - private var language: Language = Language.ENGLISH // Default language is English. - private var currency: Currency = Currency.JPY // Default currency is Japanese Yen. - private var isDebugMode: Boolean = false // Debug mode is off by default. - private var configurableTheme: ConfigurableTheme = ConfigurableTheme.default // Custom theme for UI elements. - private var inlinedProcessing: Boolean = false // Inlined processing is off by default. - - /** Sets the language for the payment. */ - fun setLanguage(language: Language) = apply { - this.language = language - } - - /** Sets the currency for the transaction. */ - fun setCurrency(currency: Currency) = apply { - this.currency = currency - } - - /** Enables or disables debug mode. */ - fun setDebugMode(isDebugMode: Boolean) = apply { - this.isDebugMode = isDebugMode - } - - /** Sets the custom theme for the payment UI. */ - fun setConfigurableTheme(configurableTheme: ConfigurableTheme) = apply { - this.configurableTheme = configurableTheme - } - - /** - * WARNING: Experimental API [Try this only if you are sure] Disabled by Default. - * - * This API enables or disables inlined processing. - * If this is enabled then The SDK will try to do processing with minimum amount of screens. - * - * For e.g. - * * If PayPay Payment id captured, it will close the SDK ASAP it verifies the payment. - * * When you will try to pay with Credit Card and Second step verification is not required, SDK will never show the WebView and will handle the callback itself. - * - */ - @ExperimentalKomojuPaymentApi - fun setInlinedProcessing(inlinedProcessing: Boolean) = apply { - this.inlinedProcessing = inlinedProcessing - } - - /** - * Builds the [Configuration] instance with the provided settings. - */ - fun build(): Configuration = Configuration( - language = language, - currency = currency, - publishableKey = publishableKey, - sessionId = sessionId, - isDebugMode = isDebugMode, - configurableTheme = configurableTheme, - inlinedProcessing = inlinedProcessing, - ) - } - } - - /** - * Data class to hold the result of a payment transaction. - * @param isSuccessFul Whether the payment was successful or not. - */ - @Parcelize - data class PaymentResult(val isSuccessFul: Boolean) : Parcelable - - /** - * Property that provides the contract to start the payment process - * and receive the result (success or failure). - */ - @JvmStatic - val KomojuPaymentResultContract: ActivityResultContract get() = KomojuStartPaymentForResultContract() -} - -/** - * Extension function to check if the current configuration is valid for processing a payment. - * @return True if the configuration is non-null and contains both publishableKey and sessionId. - */ -@OptIn(ExperimentalContracts::class) -fun KomojuSDK.Configuration?.canProcessPayment(): Boolean { - contract { - returns(true) implies (this@canProcessPayment != null) - } - return this?.publishableKey != null && this.sessionId != null -} - -/** - * Internal contract that handles starting the payment activity and returning the result. - */ -internal class KomojuStartPaymentForResultContract : ActivityResultContract() { - - companion object { - const val CONFIGURATION_KEY: String = "KomojuSDK.Configuration" // Key for passing configuration data via Intent. - const val RESULT_KEY: String = "KomojuSDK.PaymentResult" // Key for passing result data via Intent. - } - - /** - * Creates an [Intent] to start the payment activity using the provided configuration. - * Performs pre-checks before starting the activity. - * @param context Context in which the payment activity will be started. - * @param input Configuration used for payment setup. - * @return The [Intent] with necessary data to start the payment activity. - */ - override fun createIntent(context: Context, input: KomojuSDK.Configuration): Intent { - context.preChecks() // Ensure app scheme is correctly set in resources. - val intent = Intent(context, KomojuPaymentActivity::class.java) - intent.putExtra( - CONFIGURATION_KEY, - input.copy( - redirectURL = "${context.resources.getString(R.string.komoju_consumer_app_scheme)}://", - ), - ) - return intent - } - - /** - * Performs pre-checks to ensure that the app scheme is correctly set in strings.xml. - * Throws an error if the scheme is not properly configured. - */ - private fun Context.preChecks() { - if (resources.getString(R.string.komoju_consumer_app_scheme) == "this-should-not-be-the-case") { - error("Please set komoju_consumer_app_scheme in strings.xml with your app scheme") - } - } - - /** - * Parses the result returned by the payment activity. - * This is a placeholder for actual result parsing logic. - * @param resultCode Result code from the activity. - * @param intent The returned [Intent] containing the result data. - * @return [KomojuSDK.PaymentResult] indicating whether the payment was successful or not. - */ - override fun parseResult(resultCode: Int, intent: Intent?): KomojuSDK.PaymentResult = when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> intent?.getParcelableExtra(RESULT_KEY, KomojuSDK.PaymentResult::class.java) - else -> - @Suppress("DEPRECATION") - intent?.getParcelableExtra(RESULT_KEY) - } ?: KomojuSDK.PaymentResult(isSuccessFul = false) -} diff --git a/android/src/main/java/com/komoju/android/sdk/KomojuStartPaymentForResultContract.kt b/android/src/main/java/com/komoju/android/sdk/KomojuStartPaymentForResultContract.kt new file mode 100644 index 0000000..0fb7f9f --- /dev/null +++ b/android/src/main/java/com/komoju/android/sdk/KomojuStartPaymentForResultContract.kt @@ -0,0 +1,66 @@ +package com.komoju.android.sdk + +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.activity.result.contract.ActivityResultContract +import com.komoju.mobile.sdk.KomojuMobileSDKPaymentResult + +/** + * Internal contract that handles starting the payment activity and returning the result. + */ +internal class KomojuStartPaymentForResultContract : + ActivityResultContract() { + + companion object { + const val CONFIGURATION_KEY: String = "KomojuSDK.Configuration" // Key for passing configuration data via Intent. + const val RESULT_KEY: String = "KomojuSDK.PaymentResult" // Key for passing result data via Intent. + } + + /** + * Creates an [Intent] to start the payment activity using the provided configuration. + * Performs pre-checks before starting the activity. + * @param context Context in which the payment activity will be started. + * @param input Configuration used for payment setup. + * @return The [Intent] with necessary data to start the payment activity. + */ + override fun createIntent(context: Context, input: KomojuAndroidSDK.Configuration): Intent { + context.preChecks() // Ensure app scheme is correctly set in resources. + val intent = Intent(context, KomojuPaymentActivity::class.java) + intent.putExtra( + CONFIGURATION_KEY, + input.copy( + redirectURL = "${context.resources.getString(R.string.komoju_consumer_app_scheme)}://", + appScheme = context.resources.getString(R.string.komoju_consumer_app_scheme), + ), + ) + return intent + } + + /** + * Performs pre-checks to ensure that the app scheme is correctly set in strings.xml. + * Throws an error if the scheme is not properly configured. + */ + private fun Context.preChecks() { + if (resources.getString(R.string.komoju_consumer_app_scheme) == "this-should-not-be-the-case") { + error("Please set komoju_consumer_app_scheme in strings.xml with your app scheme") + } + } + + /** + * Parses the result returned by the payment activity. + * This is a placeholder for actual result parsing logic. + * @param resultCode Result code from the activity. + * @param intent The returned [Intent] containing the result data. + * @return [KomojuMobileSDKPaymentResult] indicating whether the payment was successful or not. + */ + override fun parseResult(resultCode: Int, intent: Intent?): KomojuAndroidSDK.PaymentResult = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> intent?.getParcelableExtra( + RESULT_KEY, + KomojuAndroidSDK.PaymentResult::class.java, + ) + else -> + @Suppress("DEPRECATION") + intent?.getParcelableExtra(RESULT_KEY) + } ?: KomojuAndroidSDK.PaymentResult(isSuccessFul = false) +} diff --git a/android/src/main/java/com/komoju/android/sdk/ExperimentalKomojuPaymentApi.kt b/android/src/main/java/com/komoju/android/sdk/annotations/ExperimentalKomojuPaymentApi.kt similarity index 96% rename from android/src/main/java/com/komoju/android/sdk/ExperimentalKomojuPaymentApi.kt rename to android/src/main/java/com/komoju/android/sdk/annotations/ExperimentalKomojuPaymentApi.kt index 3566258..dbb7e6a 100644 --- a/android/src/main/java/com/komoju/android/sdk/ExperimentalKomojuPaymentApi.kt +++ b/android/src/main/java/com/komoju/android/sdk/annotations/ExperimentalKomojuPaymentApi.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk +package com.komoju.android.sdk.annotations /** * Annotation used to mark APIs in the Komoju SDK as experimental. diff --git a/android/src/main/java/com/komoju/android/sdk/types/Currency.kt b/android/src/main/java/com/komoju/android/sdk/types/Currency.kt index 2381b3d..967b712 100644 --- a/android/src/main/java/com/komoju/android/sdk/types/Currency.kt +++ b/android/src/main/java/com/komoju/android/sdk/types/Currency.kt @@ -1,7 +1,5 @@ package com.komoju.android.sdk.types -import java.util.Locale - /** * Enum class representing supported currencies with corresponding ISO 4217 currency codes. * Each currency is associated with a specific locale. @@ -19,18 +17,6 @@ enum class Currency(val currencyCode: String) { USD(currencyCode = "USD"), ; - /** - * Converts the current currency to its corresponding locale. - * - * @return [Locale] for the currency. - * - JPY returns [Locale.JAPAN] - * - USD returns [Locale.US] - */ - fun toLocale(): Locale = when (this) { - JPY -> Locale.JAPAN - USD -> Locale.US - } - companion object { /** * Parses the given language code and returns the corresponding [Currency] enum constant. diff --git a/android/src/main/java/com/komoju/android/sdk/types/Language.kt b/android/src/main/java/com/komoju/android/sdk/types/Language.kt index 3c7528e..e63f6f6 100644 --- a/android/src/main/java/com/komoju/android/sdk/types/Language.kt +++ b/android/src/main/java/com/komoju/android/sdk/types/Language.kt @@ -1,6 +1,6 @@ package com.komoju.android.sdk.types -import java.util.Locale +import androidx.compose.ui.text.intl.Locale /** * Enum class representing supported languages with their respective language codes. @@ -19,17 +19,7 @@ enum class Language(val languageCode: String) { JAPANESE(languageCode = "ja"), ; - /** - * Converts the current language to its corresponding locale. - * - * @return [Locale] for the language. - * - ENGLISH returns [Locale.ENGLISH] - * - JAPANESE returns [Locale.JAPANESE] - */ - internal fun toLocale(): Locale = when (this) { - ENGLISH -> Locale.ENGLISH - JAPANESE -> Locale.JAPANESE - } + override fun toString(): String = languageCode companion object { /** @@ -40,9 +30,11 @@ enum class Language(val languageCode: String) { * - Returns [ENGLISH] for all other cases. */ val default - get() = when (Locale.getDefault().language) { - Locale.JAPANESE.language -> JAPANESE + get() = when (Locale.current.language) { + "ja" -> JAPANESE else -> ENGLISH } + + fun parse(languageCode: String?): Language = entries.find { it.languageCode == languageCode } ?: default } } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/composables/ThemedCircularProgressIndicator.kt b/android/src/main/java/com/komoju/android/sdk/ui/composables/ThemedCircularProgressIndicator.kt deleted file mode 100644 index fa9f26f..0000000 --- a/android/src/main/java/com/komoju/android/sdk/ui/composables/ThemedCircularProgressIndicator.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.komoju.android.sdk.ui.composables - -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import com.komoju.android.sdk.ui.theme.LocalConfigurableTheme - -@Composable -internal fun ThemedCircularProgressIndicator() { - val configuration = LocalConfigurableTheme.current - CircularProgressIndicator(color = Color(configuration.loaderColor)) -} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreenModel.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreenModel.kt deleted file mode 100644 index ed76f3b..0000000 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreenModel.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.komoju.android.sdk.ui.screens.awating - -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 - -internal class KonbiniAwaitingPaymentScreenModel(payment: Payment? = null) : - RouterStateScreenModel( - KonbiniAwaitingPaymentUiState(payment), - ) { - - fun onPrimaryButtonClicked() { - when (val payment = state.value.payment) { - is Payment.Konbini -> mutableRouter.value = Router.Push(KomojuPaymentRoute.WebView(payment.instructionURL, canComeBack = true)) - else -> Unit - } - } - - fun onSecondaryButtonClicked() { - mutableRouter.value = Router.Pop - } -} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/CreditCardSchemeIcons.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/CreditCardSchemeIcons.kt deleted file mode 100644 index 7819ac6..0000000 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/CreditCardSchemeIcons.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.komoju.android.sdk.ui.screens.payment.composables - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.width -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.utils.CardScheme - -@Composable -internal fun CreditCardSchemeIcons(cardScheme: CardScheme) { - Row { - AnimatedVisibility(visible = cardScheme == CardScheme.UNKNOWN) { - Row { - Image(painter = painterResource(id = R.drawable.komoju_ic_visa), contentDescription = "visa_icon") - Spacer(Modifier.width(8.dp)) - Image(painter = painterResource(id = R.drawable.komoju_ic_master), contentDescription = "mastercard_icon") - Spacer(Modifier.width(8.dp)) - Image(painter = painterResource(id = R.drawable.komoju_ic_amex), contentDescription = "amex_icon") - } - } - AnimatedVisibility(visible = cardScheme == CardScheme.VISA) { - Image(painter = painterResource(id = R.drawable.komoju_ic_visa), contentDescription = "visa_icon") - } - AnimatedVisibility(visible = cardScheme == CardScheme.MASTERCARD) { - Image(painter = painterResource(id = R.drawable.komoju_ic_master), contentDescription = "mastercard_icon") - } - AnimatedVisibility(visible = cardScheme == CardScheme.AMEX) { - Image(painter = painterResource(id = R.drawable.komoju_ic_amex), contentDescription = "amex_icon") - } - AnimatedVisibility(visible = cardScheme == CardScheme.DINERS_CLUB) { - Image(painter = painterResource(id = R.drawable.komoju_ic_diners), contentDescription = "diners_icon") - } - AnimatedVisibility(visible = cardScheme == CardScheme.JCB) { - Image(painter = painterResource(id = R.drawable.komoju_ic_jcb), contentDescription = "jcb_icon") - } - } -} - -@Composable -@Preview -private fun CreditCardSchemeIconsPreview() { - CreditCardSchemeIcons(CardScheme.UNKNOWN) -} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/OffSitePayForm.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/OffSitePayForm.kt deleted file mode 100644 index 1f66d91..0000000 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/OffSitePayForm.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.komoju.android.sdk.ui.screens.payment.composables - -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme -import com.komoju.mobile.sdk.entities.PaymentMethod -import com.komoju.mobile.sdk.types.OffSitePaymentType - -@Composable -internal fun OffSitePayForm(paymentMethod: PaymentMethod.OffSitePayment, onPayButtonClicked: () -> Unit) { - val titleKey = remember(paymentMethod) { - when (paymentMethod.type) { - OffSitePaymentType.ALI_PAY -> R.string.komoju_payment_via_alipay - OffSitePaymentType.AU_PAY -> R.string.komoju_payment_via_au_pay - OffSitePaymentType.MER_PAY -> R.string.komoju_payment_via_mer_pay - OffSitePaymentType.PAY_PAY -> R.string.komoju_payment_via_paypay - OffSitePaymentType.RAKUTEN_PAY -> R.string.komoju_payment_via_rakuten_pay - OffSitePaymentType.LINE_PAY -> R.string.komoju_payment_via_line_pay - else -> null - } - } - - val messageKey = remember(paymentMethod) { - when (paymentMethod.type) { - OffSitePaymentType.ALI_PAY -> R.string.komoju_you_will_be_redirected_to_alipay - OffSitePaymentType.AU_PAY -> R.string.komoju_you_will_be_redirected_to_au_pay - OffSitePaymentType.MER_PAY -> R.string.komoju_you_will_be_redirected_to_mer_pay - OffSitePaymentType.PAY_PAY -> R.string.komoju_you_will_be_redirected_to_paypay - OffSitePaymentType.RAKUTEN_PAY -> R.string.komoju_you_will_be_redirected_to_rakuten - OffSitePaymentType.LINE_PAY -> R.string.komoju_you_will_be_redirected_to_line_pay - else -> null - } - } - - val paymentButtonKey = remember(paymentMethod) { - when (paymentMethod.type) { - OffSitePaymentType.ALI_PAY -> R.string.komoju_continue_to_alipay - OffSitePaymentType.AU_PAY -> R.string.komoju_continue_to_aupay - OffSitePaymentType.MER_PAY -> R.string.komoju_continue_to_merpay - OffSitePaymentType.PAY_PAY -> R.string.komoju_continue_to_paypay - OffSitePaymentType.RAKUTEN_PAY -> R.string.komoju_continue_to_rakuten - OffSitePaymentType.LINE_PAY -> R.string.komoju_continue_to_linepay - else -> null - } - } - - if (titleKey != null && messageKey != null && paymentButtonKey != null) { - Column(modifier = Modifier.padding(all = 16.dp)) { - Text(text = stringResource(titleKey), style = TextStyle(fontWeight = FontWeight.Bold, fontSize = 24.sp)) - Spacer(modifier = Modifier.height(12.dp)) - Text(text = stringResource(messageKey)) - Spacer(modifier = Modifier.height(24.dp)) - Row( - modifier = Modifier - .background(Color(0xFFF3F7F9), shape = RoundedCornerShape(8.dp)) - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Image( - painter = painterResource(R.drawable.komoju_ic_app_opens_info), - contentDescription = "app_opens_info", - modifier = Modifier.size(32.dp), - ) - Spacer(modifier = Modifier.width(16.dp)) - Text(text = stringResource(R.string.komoju_webview_open_info)) - } - Spacer(modifier = Modifier.height(32.dp)) - PrimaryButton(stringResource(paymentButtonKey), modifier = Modifier.fillMaxWidth(), onPayButtonClicked) - } - } -} - -@Composable -@Preview(showBackground = true) -private fun AppPayFormPreview() { - KomojuMobileSdkTheme { - OffSitePayForm( - PaymentMethod.OffSitePayment( - hashedGateway = "paypay", - exchangeRate = 1.0, - currency = "JPY", - amount = "100", - additionalFields = listOf(), - type = OffSitePaymentType.PAY_PAY, - ), - ) { } - } -} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaymentMethodsRow.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaymentMethodsRow.kt deleted file mode 100644 index 97c41c7..0000000 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaymentMethodsRow.kt +++ /dev/null @@ -1,149 +0,0 @@ -package com.komoju.android.sdk.ui.screens.payment.composables - -import androidx.compose.foundation.Image -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.ui.theme.Gray200 -import com.komoju.android.sdk.ui.theme.KomojuDarkGreen -import com.komoju.mobile.sdk.entities.PaymentMethod -import com.komoju.mobile.sdk.types.OffSitePaymentType - -@Composable -internal fun PaymentMethodsRow( - paymentMethods: List, - selectedPaymentMethod: PaymentMethod?, - onSelected: (PaymentMethod) -> Unit, -) { - LazyRow(contentPadding = PaddingValues(horizontal = 8.dp)) { - items(paymentMethods) { paymentMethod -> - PaymentMethodComposable( - paymentMethod, - paymentMethod == selectedPaymentMethod, - onSelected = { - onSelected(paymentMethod) - }, - ) - } - } -} - -@Composable -private fun PaymentMethodComposable(paymentMethod: PaymentMethod, isSelected: Boolean, onSelected: () -> Unit) { - Column( - modifier = Modifier - .defaultMinSize(minWidth = 120.dp) - .padding(4.dp) - .clip(RoundedCornerShape(8.dp)) - .border(1.dp, if (isSelected) KomojuDarkGreen else Gray200, RoundedCornerShape(8.dp)) - .clickable(onClick = onSelected) - .padding(start = 12.dp, end = 12.dp, top = 12.dp, bottom = 8.dp), - ) { - Image( - painter = painterResource(paymentMethod.displayIcon), - contentDescription = "${paymentMethod.displayName} icon", - modifier = Modifier.height(32.dp), - ) - Spacer(modifier = Modifier.height(4.dp)) - Text(paymentMethod.displayName, color = Color.Black, fontWeight = FontWeight.SemiBold, fontSize = 14.sp) - } -} - -private val PaymentMethod.displayName - @Composable - get() = when (this) { - is PaymentMethod.BankTransfer -> stringResource(R.string.komoju_bank_transfer) - is PaymentMethod.BitCash -> stringResource(R.string.komoju_bitcash) - is PaymentMethod.CreditCard -> stringResource(R.string.komoju_credit_card) - is PaymentMethod.Konbini -> stringResource(R.string.komoju_konbini) - is PaymentMethod.NetCash -> stringResource(R.string.komoju_netcash) - is PaymentMethod.OffSitePayment -> when (type) { - OffSitePaymentType.AU_PAY -> stringResource(R.string.komoju_aupay) - OffSitePaymentType.ALI_PAY -> stringResource(R.string.komoju_alipay) - OffSitePaymentType.MER_PAY -> stringResource(R.string.komoju_merpay) - OffSitePaymentType.PAY_PAY -> stringResource(R.string.komoju_paypay) - OffSitePaymentType.RAKUTEN_PAY -> stringResource(R.string.komoju_rakuten_pay) - OffSitePaymentType.LINE_PAY -> stringResource(R.string.komoju_line_pay) - OffSitePaymentType.UNKNOWN -> stringResource(R.string.komoju_unknown) - } - is PaymentMethod.Other -> stringResource(R.string.komoju_other) - is PaymentMethod.Paidy -> stringResource(R.string.komoju_paidy) - is PaymentMethod.PayEasy -> stringResource(R.string.komoju_payeasy) - is PaymentMethod.WebMoney -> stringResource(R.string.komoju_webmoney) - } - -private val PaymentMethod.displayIcon - get() = when (this) { - is PaymentMethod.OffSitePayment -> when (type) { - OffSitePaymentType.ALI_PAY -> R.drawable.komoju_ic_alipay - OffSitePaymentType.AU_PAY -> R.drawable.komoju_ic_au_pay - OffSitePaymentType.MER_PAY -> R.drawable.komoju_ic_merpay - OffSitePaymentType.PAY_PAY -> R.drawable.komoju_ic_paypay - OffSitePaymentType.RAKUTEN_PAY -> R.drawable.komoju_ic_rakuten_pay - OffSitePaymentType.LINE_PAY -> R.drawable.komoju_ic_linepay - OffSitePaymentType.UNKNOWN -> R.drawable.komoju_ic_credit_card - } - - is PaymentMethod.BankTransfer -> R.drawable.komoju_ic_bank_transfer - is PaymentMethod.BitCash -> R.drawable.komoju_ic_bitcash - is PaymentMethod.CreditCard -> R.drawable.komoju_ic_credit_card - is PaymentMethod.Konbini -> R.drawable.komoju_ic_konbini - is PaymentMethod.NetCash -> R.drawable.komoju_ic_net_cash - is PaymentMethod.Paidy -> R.drawable.komoju_ic_paidy - is PaymentMethod.PayEasy -> R.drawable.komoju_ic_pay_easy - is PaymentMethod.WebMoney -> R.drawable.komoju_ic_web_money - is PaymentMethod.Other -> R.drawable.komoju_ic_credit_card - } - -@Composable -@Preview -private fun PaymentMethodComposablePreview() { - val paymentMethods = listOf( - PaymentMethod.CreditCard( - hashedGateway = "", - exchangeRate = 0.0, - currency = "", - amount = "0", - additionalFields = listOf(), - brands = listOf(), - ), - PaymentMethod.Konbini( - hashedGateway = "", - exchangeRate = 0.0, - currency = "", - amount = "0", - additionalFields = listOf(), - customerFee = 0, - brands = listOf(), - ), - PaymentMethod.OffSitePayment( - hashedGateway = "", - exchangeRate = 0.0, - currency = "", - amount = "0", - additionalFields = listOf(), - type = OffSitePaymentType.PAY_PAY, - ), - ) - PaymentMethodsRow(paymentMethods, paymentMethods.first()) {} -} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebChromeClient.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebChromeClient.kt deleted file mode 100644 index b3206b6..0000000 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebChromeClient.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.komoju.android.sdk.ui.screens.webview - -import android.webkit.ConsoleMessage -import com.kevinnzou.web.AccompanistWebChromeClient - -internal class WebChromeClient : AccompanistWebChromeClient() { - override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean = true -} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebViewClient.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebViewClient.kt deleted file mode 100644 index e41a27a..0000000 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebViewClient.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.komoju.android.sdk.ui.screens.webview - -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.webkit.WebResourceRequest -import android.webkit.WebView -import androidx.core.app.ActivityOptionsCompat -import androidx.core.content.ContextCompat.startActivity -import androidx.core.net.toUri -import com.kevinnzou.web.AccompanistWebViewClient -import com.komoju.android.sdk.KomojuPaymentActivity -import com.komoju.android.sdk.R - -internal class WebViewClient : AccompanistWebViewClient() { - - @Deprecated("Deprecated in Java") - override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean = view.checkAndOpen(url) - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean = view.checkAndOpen(request.url.toString()) - - private fun WebView.checkAndOpen(url: String): Boolean { - try { - val uri = url.toUri() - val handled = uri.handle(context) - if (handled.not()) { - error("Unsupported scheme for deeplink, load in webView Instead.") - } else { - return handled - } - } catch (_: Exception) { - loadUrl(url) - return false - } - } -} - -private fun Uri.handle(context: Context): Boolean = openKomojuSDKIfAvailable(context) - -private fun Uri.openKomojuSDKIfAvailable(context: Context): Boolean { - if (scheme == context.resources.getString(R.string.komoju_consumer_app_scheme)) { - startActivity( - context, - Intent(context, KomojuPaymentActivity::class.java).apply { - data = this@openKomojuSDKIfAvailable - }, - ActivityOptionsCompat.makeBasic().toBundle(), - ) - return true - } else { - return false - } -} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebViewScreenModel.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebViewScreenModel.kt deleted file mode 100644 index 4e62610..0000000 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebViewScreenModel.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.komoju.android.sdk.ui.screens.webview - -import com.komoju.android.sdk.navigation.RouterStateScreenModel - -internal class WebViewScreenModel : RouterStateScreenModel(Unit) { - - fun onBackPressed() { - mutableRouter.pop() - } -} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/theme/ConfigurableTheme.kt b/android/src/main/java/com/komoju/android/sdk/ui/theme/ConfigurableTheme.kt deleted file mode 100644 index 864953d..0000000 --- a/android/src/main/java/com/komoju/android/sdk/ui/theme/ConfigurableTheme.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.komoju.android.sdk.ui.theme - -import android.graphics.Color -import android.os.Parcelable -import androidx.annotation.ColorInt -import kotlinx.parcelize.Parcelize - -/** - * Interface representing a configurable theme with customizable UI properties. - * This interface extends [Parcelable], allowing themes to be passed between Android components. - */ -interface ConfigurableTheme : Parcelable { - - /** - * Color value for the primary button background. - * - * @return An integer representing a color value annotated with [ColorInt]. - */ - @get:ColorInt - val primaryButtonColor: Int - - /** - * Color value for the content (e.g., text or icon) inside the primary button. - * - * @return An integer representing a color value annotated with [ColorInt]. - */ - @get:ColorInt - val primaryButtonContentColor: Int - - /** - * Corner radius for the primary button in density-independent pixels (DP). - * - * @return An integer representing the corner radius in DP annotated with [ColorInt]. - */ - @get:ColorInt - val primaryButtonCornerRadiusInDP: Int - - /** - * Color value for the loader/spinner. - * - * @return An integer representing a color value annotated with [ColorInt]. - */ - @get:ColorInt - val loaderColor: Int - - companion object { - /** - * Provides a default implementation of the [ConfigurableTheme]. - */ - val default = DefaultConfigurableTheme() - } -} - -/** - * Data class representing the default implementation of [ConfigurableTheme]. - * This class uses default values for UI elements such as buttons and loaders. - * It is annotated with [Parcelize] to allow it to be passed between Android components. - * - * @param primaryButtonColor The background color of the primary button. - * @param primaryButtonContentColor The color of the content inside the primary button. - * @param primaryButtonCornerRadiusInDP The corner radius of the primary button in DP. - * @param loaderColor The color of the loader/spinner. - */ -@Parcelize -data class DefaultConfigurableTheme( - override val primaryButtonColor: Int = Color.parseColor("#FF297FE7"), // Default blue color for primary button. - override val primaryButtonContentColor: Int = Color.WHITE, // Default white color for primary button content. - override val primaryButtonCornerRadiusInDP: Int = 8, // Default corner radius of 8 DP for primary button. - override val loaderColor: Int = Color.parseColor("#FF3CC239"), // Default green color for loader. -) : ConfigurableTheme diff --git a/android/src/main/java/com/komoju/android/sdk/utils/AmountUtils.kt b/android/src/main/java/com/komoju/android/sdk/utils/AmountUtils.kt deleted file mode 100644 index 1d542bb..0000000 --- a/android/src/main/java/com/komoju/android/sdk/utils/AmountUtils.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.komoju.android.sdk.utils - -import android.icu.text.NumberFormat -import android.icu.util.Currency as PlatformCurrency -import com.komoju.android.sdk.types.Currency - -internal object AmountUtils { - fun formatToDecimal(currency: Currency, amount: String): String { - if (amount.isBlank()) return "" - val locale = currency.toLocale() - return NumberFormat.getCurrencyInstance(locale).apply { - this.maximumFractionDigits = 0 - this.minimumFractionDigits = 0 - this.currency = PlatformCurrency.getInstance(locale) - }.format(amount.toDouble()) - } -} diff --git a/android/src/main/java/com/komoju/android/sdk/utils/PreviewScreen.kt b/android/src/main/java/com/komoju/android/sdk/utils/PreviewScreen.kt deleted file mode 100644 index 3777cd2..0000000 --- a/android/src/main/java/com/komoju/android/sdk/utils/PreviewScreen.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.komoju.android.sdk.utils - -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.core.screen.Screen - -/** - * An Empty Screen to Support Android Studio Previews - */ -internal object PreviewScreen : Screen { - private fun readResolve(): Any = PreviewScreen - - @Composable - override fun Content() = Unit -} diff --git a/android/src/main/java/com/komoju/android/sdk/utils/StringExt.kt b/android/src/main/java/com/komoju/android/sdk/utils/StringExt.kt deleted file mode 100644 index b431528..0000000 --- a/android/src/main/java/com/komoju/android/sdk/utils/StringExt.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.komoju.android.sdk.utils - -private val EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$".toRegex() - -internal inline val String.Companion.empty get() = "" - -internal inline val String.isValidEmail: Boolean get() = matches(EMAIL_REGEX) - -internal inline val String.isKatakanaOnly: Boolean get() = all { it.isKatakana } - -internal inline val Char.isKatakana get() = Character.UnicodeBlock.of(this) == Character.UnicodeBlock.KATAKANA diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index 2bd2a0f..333e094 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -1,121 +1,4 @@ this-should-not-be-the-case - The entered cardholder name cannot be empty - The entered cardholder name is not valid - The entered card number is not valid - The entered expiry date is not valid - The entered CVV is not valid - Payment successful - Payment failed - Awaiting payment - Your payment has been processed successfully. - Your payment has failed. - You need to go to your local %1$s and make the payment to proceed. - Your payment is awaiting processing. - Error - Receipt Number - Confirmation Code - Done - Update Payment method - View instructions - Okay - Have a question? Contact us - I will do it later - We noticed that you’ve canceled the payment process. If this was a mistake, you can try again to complete your purchase. - We attempted to process your payment, but something went wrong. Please update your payment method below to continue. - We tried to charge your card but, something went wrong. Please update your payment method below to continue - Back to store - Payment Options - Pay %1$s - Cardholder name - Full name on card - Card Number - MM/YY - CVV - Save this card for future payments - Daily Yamazaki - Family Mart - Lawson - MiniStop - SeicoMart - 7-Eleven - Full name on receipt - Name (shown on receipt) - Email - Enter Your Email Address - NET CASH Information - NET CASH id - Payment via Alipay - Payment via AU Pay - Payment via Mer Pay - Payment via Paypay - Payment via Rakuten Pay - Payment via Line Pay - You will be redirected to Alipay to complete the payment - You will be redirected to AU Pay to complete the payment - You will be redirected to Mer Pay to complete the payment - You will be redirected to Paypay to complete the payment - You will be redirected to Rakuten to complete the payment - You will be redirected to Line Pay to complete the payment - Continue to Alipay - Continue to AuPay - Continue to MerPay - Continue to PayPay - Continue to Rakuten - Continue to LinePay - Continue to Paidy - Note, a web window will be opened. After purchase you will be redirected back. - Full Name - Enter your name - Phone Number - Enter your phone number - Last Name - First Name - Last Name (Phonetic) - First Name (Phonetic) - WebMoney Information - Prepaid number - Payment Success - Thank you for your order - BitCash Information - Hiragana id - Bank Transfer - BitCash - Credit card - Konbini - NetCash - au Pay - AliPay - Mer Pay - PayPay - Rakuten Pay - Line Pay - Unknown - Other - Paidy - PayEasy - WebMoney - Total Payment - The entered name cannot be empty - The entered email is not valid - Please select a konbini brand - The entered phone number cannot be empty - The entered phone number is not valid - The entered net cash id cannot be empty - The entered net cash id is not valid - The entered bit cash id cannot be empty - The entered bit cash id is not valid - The entered prepaid number cannot be empty - The entered prepaid number is not valid - The entered last name cannot be empty - The entered first name cannot be empty - The entered first name phonetic cannot be empty - The entered first name phonetic must be a kana - The entered last name phonetic cannot be empty - The entered last name phonetic must be a kana - Yes - No - Are you sure you want to cancel the payment? - Cancel Payment? \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index be9076a..bfcc503 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,4 +10,5 @@ plugins { alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.jetbrains.kotlin.jvm) apply false alias(libs.plugins.jetbrains.dokka) apply false + alias(libs.plugins.composeMultiplatform) apply false } diff --git a/example-android/build.gradle.kts b/example-android/build.gradle.kts index 0a03c2b..6e34cda 100644 --- a/example-android/build.gradle.kts +++ b/example-android/build.gradle.kts @@ -14,7 +14,7 @@ val localProperties = Properties().apply { android { namespace = "com.komoju.android" - compileSdk = 34 + compileSdk = 35 defaultConfig { minSdk = 24 diff --git a/example-android/src/main/java/com/komoju/android/ui/remote/RemoteApiService.kt b/example-android/src/main/java/com/komoju/android/remote/RemoteApiService.kt similarity index 78% rename from example-android/src/main/java/com/komoju/android/ui/remote/RemoteApiService.kt rename to example-android/src/main/java/com/komoju/android/remote/RemoteApiService.kt index 2c09531..e14557a 100644 --- a/example-android/src/main/java/com/komoju/android/ui/remote/RemoteApiService.kt +++ b/example-android/src/main/java/com/komoju/android/remote/RemoteApiService.kt @@ -1,14 +1,13 @@ -package com.komoju.android.ui.remote +package com.komoju.android.remote import com.komoju.android.BuildConfig -import com.komoju.android.ui.remote.dtos.CreateSessionRequest -import com.komoju.android.ui.remote.dtos.CreateSessionResponse -import com.komoju.android.ui.remote.dtos.PublishableKeyResponse +import com.komoju.android.remote.dtos.CreateSessionRequest +import com.komoju.android.remote.dtos.CreateSessionResponse +import com.komoju.android.remote.dtos.PublishableKeyResponse import java.util.concurrent.TimeUnit import okhttp3.OkHttpClient import retrofit2.Response import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Headers @@ -31,7 +30,7 @@ interface RemoteApiService { .readTimeout(100, TimeUnit.SECONDS) // Set the read timeout to 100 seconds because the glitch server is slow .build(), ) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(retrofit2.converter.gson.GsonConverterFactory.create()) .baseUrl(BuildConfig.SERVER_URL) .build() .create(RemoteApiService::class.java) diff --git a/example-android/src/main/java/com/komoju/android/ui/remote/dtos/CreateSessionRequest.kt b/example-android/src/main/java/com/komoju/android/remote/dtos/CreateSessionRequest.kt similarity index 85% rename from example-android/src/main/java/com/komoju/android/ui/remote/dtos/CreateSessionRequest.kt rename to example-android/src/main/java/com/komoju/android/remote/dtos/CreateSessionRequest.kt index 12e0f3b..2f07cc2 100644 --- a/example-android/src/main/java/com/komoju/android/ui/remote/dtos/CreateSessionRequest.kt +++ b/example-android/src/main/java/com/komoju/android/remote/dtos/CreateSessionRequest.kt @@ -1,4 +1,4 @@ -package com.komoju.android.ui.remote.dtos +package com.komoju.android.remote.dtos import com.google.gson.annotations.SerializedName diff --git a/example-android/src/main/java/com/komoju/android/ui/remote/dtos/CreateSessionResponse.kt b/example-android/src/main/java/com/komoju/android/remote/dtos/CreateSessionResponse.kt similarity index 77% rename from example-android/src/main/java/com/komoju/android/ui/remote/dtos/CreateSessionResponse.kt rename to example-android/src/main/java/com/komoju/android/remote/dtos/CreateSessionResponse.kt index 6b7a983..4fba145 100644 --- a/example-android/src/main/java/com/komoju/android/ui/remote/dtos/CreateSessionResponse.kt +++ b/example-android/src/main/java/com/komoju/android/remote/dtos/CreateSessionResponse.kt @@ -1,4 +1,4 @@ -package com.komoju.android.ui.remote.dtos +package com.komoju.android.remote.dtos import com.google.gson.annotations.SerializedName diff --git a/example-android/src/main/java/com/komoju/android/ui/remote/dtos/PublishableKeyResponse.kt b/example-android/src/main/java/com/komoju/android/remote/dtos/PublishableKeyResponse.kt similarity index 79% rename from example-android/src/main/java/com/komoju/android/ui/remote/dtos/PublishableKeyResponse.kt rename to example-android/src/main/java/com/komoju/android/remote/dtos/PublishableKeyResponse.kt index 44535d5..94d91d4 100644 --- a/example-android/src/main/java/com/komoju/android/ui/remote/dtos/PublishableKeyResponse.kt +++ b/example-android/src/main/java/com/komoju/android/remote/dtos/PublishableKeyResponse.kt @@ -1,4 +1,4 @@ -package com.komoju.android.ui.remote.dtos +package com.komoju.android.remote.dtos import com.google.gson.annotations.SerializedName diff --git a/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeItemDetailScreen.kt b/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeItemDetailScreen.kt index b947bc6..c0b83e2 100644 --- a/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeItemDetailScreen.kt +++ b/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeItemDetailScreen.kt @@ -46,7 +46,7 @@ import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import com.komoju.android.R -import com.komoju.android.sdk.KomojuSDK +import com.komoju.android.sdk.KomojuAndroidSDK import com.komoju.android.sdk.canProcessPayment import com.komoju.android.ui.theme.KomojuDarkGreen @@ -60,7 +60,7 @@ data class FakeItemDetailScreen(private val index: Int) : Screen { val uiState by screenModel.uiState.collectAsStateWithLifecycle() val item by remember { derivedStateOf { uiState.items[index] } } val komojuSDKConfiguration by screenModel.komojuSDKConfiguration.collectAsStateWithLifecycle() - val komojuPaymentLauncher = rememberLauncherForActivityResult(KomojuSDK.KomojuPaymentResultContract) { + val komojuPaymentLauncher = rememberLauncherForActivityResult(KomojuAndroidSDK.activityResultContract) { screenModel.onKomojuPaymentCompleted() navigator.push(if (it.isSuccessFul) FakeOrderSuccessScreen() else FakeOrderFailedScreen()) } diff --git a/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeStoreScreen.kt b/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeStoreScreen.kt index dc3619a..41b281e 100644 --- a/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeStoreScreen.kt +++ b/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeStoreScreen.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons @@ -26,7 +25,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -46,6 +44,7 @@ import androidx.compose.ui.text.withStyle 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.rememberNavigatorScreenModel import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator @@ -61,7 +60,7 @@ class FakeStoreScreen : Screen { override fun Content() { val navigator = LocalNavigator.currentOrThrow val screenModel = navigator.rememberNavigatorScreenModel { FakeStoreScreenModel() } - val uiState by screenModel.uiState.collectAsState() + val uiState by screenModel.uiState.collectAsStateWithLifecycle() val context = LocalContext.current LaunchedEffect(uiState.error) { uiState.error?.let { diff --git a/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeStoreScreenModel.kt b/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeStoreScreenModel.kt index c174f4f..e2a424a 100644 --- a/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeStoreScreenModel.kt +++ b/example-android/src/main/java/com/komoju/android/ui/screens/store/FakeStoreScreenModel.kt @@ -3,12 +3,12 @@ package com.komoju.android.ui.screens.store import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope import com.komoju.android.BuildConfig -import com.komoju.android.sdk.ExperimentalKomojuPaymentApi -import com.komoju.android.sdk.KomojuSDK +import com.komoju.android.sdk.KomojuAndroidSDK +import com.komoju.android.sdk.annotations.ExperimentalKomojuPaymentApi import com.komoju.android.sdk.types.Currency import com.komoju.android.sdk.types.Language -import com.komoju.android.ui.remote.RemoteApiService -import com.komoju.android.ui.remote.dtos.CreateSessionRequest +import com.komoju.android.remote.RemoteApiService +import com.komoju.android.remote.dtos.CreateSessionRequest import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow @@ -21,7 +21,7 @@ class FakeStoreScreenModel : ScreenModel { private val remoteApiService = RemoteApiService.create() private var publishableKey: String? = null - private val _komojuSDKConfiguration = MutableStateFlow(null) + private val _komojuSDKConfiguration = MutableStateFlow(null) val komojuSDKConfiguration = _komojuSDKConfiguration.asStateFlow() private val _uiState = MutableStateFlow(FakeStoreUiState()) val uiState = _uiState.onStart { @@ -65,7 +65,7 @@ class FakeStoreScreenModel : ScreenModel { fun onBuyClicked(item: Item) { screenModelScope.launch { createSession(item)?.let { sessionId -> - KomojuSDK.Configuration.Builder( + KomojuAndroidSDK.Configuration.Builder( requireNotNull(publishableKey), sessionId, ).setLanguage(language) diff --git a/example-android/src/main/java/com/komoju/android/ui/screens/store/KomojuPaymentSDKConfigurableTheme.kt b/example-android/src/main/java/com/komoju/android/ui/screens/store/KomojuPaymentSDKConfigurableTheme.kt index a55692e..f318e3a 100644 --- a/example-android/src/main/java/com/komoju/android/ui/screens/store/KomojuPaymentSDKConfigurableTheme.kt +++ b/example-android/src/main/java/com/komoju/android/ui/screens/store/KomojuPaymentSDKConfigurableTheme.kt @@ -1,11 +1,11 @@ package com.komoju.android.ui.screens.store import androidx.compose.ui.graphics.toArgb -import com.komoju.android.sdk.ui.theme.ConfigurableTheme +import com.komoju.android.sdk.KomojuAndroidSDK import com.komoju.android.ui.theme.KomojuDarkGreen -val komojuConfigurableTheme = ConfigurableTheme.default.copy( - primaryButtonColor = KomojuDarkGreen.toArgb(), - primaryButtonCornerRadiusInDP = 24, - loaderColor = KomojuDarkGreen.toArgb(), +val komojuConfigurableTheme = KomojuAndroidSDK.ConfigurableTheme.default.copy( + primaryColorInt = KomojuDarkGreen.toArgb(), + primaryShapeCornerRadiusInDp = 24, + loaderColorInt = KomojuDarkGreen.toArgb(), ) diff --git a/gradle.properties b/gradle.properties index 5e788f3..bd8cf7b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ #Gradle -org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" +org.gradle.jvmargs=-Xmx8192M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx8192M" org.gradle.caching=true org.gradle.configuration-cache=true #Kotlin diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cd1fa78..92c3330 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,12 @@ [versions] agp = "8.7.2" browser = "1.8.0" +humanReadable = "1.10.0" kotlin = "2.0.21" leakcanaryAndroid = "2.14" nexus-publish = "2.0.0" android-minSdk = "24" -android-compileSdk = "34" +android-compileSdk = "35" coreKtx = "1.15.0" fragmentKtx = "1.8.5" junit = "4.13.2" @@ -21,18 +22,20 @@ ktor = "3.0.1" coroutines = "1.9.0" datetime = "0.6.1" uiTooling = "1.7.5" -composeWebView = "0.33.6" +composeWebView = "1.9.40-alpha04" konlinXJson = "1.7.3" +uriKmp = "0.0.18" voyager = "1.1.0-beta03" mavenPublish = "0.30.0" -nmcp = "0.0.7" dokka = "1.9.20" +compose-multiplatform = "1.7.0" +jetbrainsComposeLifecycle = "2.8.3" [libraries] androidx-browser = { module = "androidx.browser:browser", version.ref = "browser" } +human-readable = { module = "nl.jacobras:Human-Readable", version.ref = "humanReadable" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanaryAndroid" } -nexus-publish = { module = "io.github.gradle-nexus.publish-plugin:io.github.gradle-nexus.publish-plugin.gradle.plugin", version.ref = "nexus-publish" } core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" } junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -58,15 +61,18 @@ ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "kto ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "konlinXJson" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" } -compose-webview = { group = "io.github.kevinnzou", name = "compose-webview", version.ref = "composeWebView" } +compose-webview-multiplatform = { group = "io.github.kevinnzou", name = "compose-webview-multiplatform", version.ref = "composeWebView" } +uri-kmp = { module = "com.eygraber:uri-kmp", version.ref = "uriKmp" } voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-screenModel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" } voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" } +jetbrains-compose-lifecycle = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "jetbrainsComposeLifecycle" } [plugins] androidLibrary = { id = "com.android.library", version.ref = "agp" } @@ -75,7 +81,8 @@ kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } androidApplication = { id = "com.android.application", version.ref = "agp" } kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" } jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka"} +jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } +composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 6d53e5d..9983ab4 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -1,12 +1,16 @@ + import com.vanniktech.maven.publish.SonatypeHost import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) alias(libs.plugins.kotlin.plugin.serialization) alias(libs.plugins.maven.publish) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.composeMultiplatform) } kotlin { @@ -17,18 +21,47 @@ kotlin { jvmTarget.set(JvmTarget.JVM_17) } } - iosX64() - iosArm64() - iosSimulatorArm64() + val xcf = XCFramework() + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64(), + ).forEach { + it.binaries.framework { + linkerOpts("-make_mergeable") + xcf.add(this) + baseName = "komojuShared" + isStatic = true + } + } sourceSets { commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.jetbrains.compose.lifecycle) + implementation(libs.ktor.client.core) implementation(libs.ktor.client.core) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.ktor.client.logging) + implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) + + implementation(libs.voyager.navigator) + implementation(libs.voyager.screenModel) + implementation(libs.voyager.transitions) + + implementation(libs.compose.webview.multiplatform) + + implementation(libs.human.readable) + implementation(libs.uri.kmp) } commonTest.dependencies { implementation(libs.kotlin.test) @@ -36,6 +69,7 @@ kotlin { androidMain.dependencies { implementation(libs.ktor.client.okhttp) implementation(libs.kotlinx.coroutines.android) + implementation(libs.androidx.browser) } iosMain.dependencies { implementation(libs.ktor.client.darwin) @@ -59,6 +93,15 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + resourcePrefix = "komoju_" +} + +compose { + resources { + publicResClass = false + packageOfResClass = "com.komoju.mobile.sdk.shared.generated.resources" + generateResClass = auto + } } mavenPublishing { diff --git a/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/navigation/PlatformBackPress.android.kt b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/navigation/PlatformBackPress.android.kt new file mode 100644 index 0000000..73d2798 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/navigation/PlatformBackPress.android.kt @@ -0,0 +1,33 @@ +package com.komoju.mobile.sdk.navigation + +import androidx.activity.OnBackPressedDispatcher +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember + +@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +actual class PlatformBackPressDispatcher { + private var onBackPressedDispatcher: OnBackPressedDispatcher? = null + + @Composable + fun Create() { + onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + DisposableEffect(Unit) { + onDispose { + onBackPressedDispatcher = null + } + } + } + + actual fun onBackPressed() { + onBackPressedDispatcher?.onBackPressed() + } +} + +@Composable +actual fun rememberPlatformBackPressDispatcher(): PlatformBackPressDispatcher = remember { + PlatformBackPressDispatcher() +}.apply { + Create() +} diff --git a/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/ui/composables/ExternalScreenLauncherResult.android.kt b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/ui/composables/ExternalScreenLauncherResult.android.kt new file mode 100644 index 0000000..e4b0411 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/ui/composables/ExternalScreenLauncherResult.android.kt @@ -0,0 +1,10 @@ +package com.komoju.mobile.sdk.ui.composables + +import androidx.activity.result.ActivityResultLauncher + +@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +actual class Launcher(private val activityLauncher: ActivityResultLauncher) { + actual fun launch(input: I) { + activityLauncher.launch(input) + } +} diff --git a/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/ui/composables/RememberLauncherForActivityResult.android.kt b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/ui/composables/RememberLauncherForActivityResult.android.kt new file mode 100644 index 0000000..58bfa90 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/ui/composables/RememberLauncherForActivityResult.android.kt @@ -0,0 +1,9 @@ +package com.komoju.mobile.sdk.ui.composables + +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.compose.runtime.Composable +import com.komoju.mobile.sdk.utils.OffsiteCustomTabResultContract + +@Composable +actual fun launchCustomTab(onResult: (Int) -> Unit): Launcher = + Launcher(rememberLauncherForActivityResult(OffsiteCustomTabResultContract(), onResult = onResult)) diff --git a/android/src/main/java/com/komoju/android/sdk/utils/ModifierExt.kt b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/utils/ModifierExt.android.kt similarity index 65% rename from android/src/main/java/com/komoju/android/sdk/utils/ModifierExt.kt rename to shared/src/androidMain/kotlin/com/komoju/mobile/sdk/utils/ModifierExt.android.kt index 4ea3b94..589cd11 100644 --- a/android/src/main/java/com/komoju/android/sdk/utils/ModifierExt.kt +++ b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/utils/ModifierExt.android.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.utils +package com.komoju.mobile.sdk.utils import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -7,6 +7,6 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId @OptIn(ExperimentalComposeUiApi::class) -internal fun Modifier.testID(id: String) = this - .semantics { testTagsAsResourceId = true } - .testTag(id) +internal actual fun Modifier.testID(id: String) = semantics { + testTagsAsResourceId = true +}.testTag(id) diff --git a/android/src/main/java/com/komoju/android/sdk/utils/OffsiteCustomTabResultContract.kt b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/utils/OffsiteCustomTabResultContract.kt similarity index 97% rename from android/src/main/java/com/komoju/android/sdk/utils/OffsiteCustomTabResultContract.kt rename to shared/src/androidMain/kotlin/com/komoju/mobile/sdk/utils/OffsiteCustomTabResultContract.kt index da09ff2..9dd13e8 100644 --- a/android/src/main/java/com/komoju/android/sdk/utils/OffsiteCustomTabResultContract.kt +++ b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/utils/OffsiteCustomTabResultContract.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.utils +package com.komoju.mobile.sdk.utils import android.content.Context import android.content.Intent diff --git a/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/utils/Platform.android.kt b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/utils/Platform.android.kt new file mode 100644 index 0000000..dd96d81 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/komoju/mobile/sdk/utils/Platform.android.kt @@ -0,0 +1,3 @@ +package com.komoju.mobile.sdk.utils + +actual val currentPlatform: Platform = Platform.ANDROID diff --git a/android/src/main/res/drawable/komoju_ic_alipay.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_alipay.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_alipay.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_alipay.xml diff --git a/android/src/main/res/drawable/komoju_ic_amex.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_amex.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_amex.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_amex.xml diff --git a/android/src/main/res/drawable/komoju_ic_app_opens_info.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_app_opens_info.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_app_opens_info.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_app_opens_info.xml diff --git a/android/src/main/res/drawable/komoju_ic_au_pay.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_au_pay.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_au_pay.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_au_pay.xml diff --git a/android/src/main/res/drawable/komoju_ic_bank_transfer.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_bank_transfer.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_bank_transfer.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_bank_transfer.xml diff --git a/android/src/main/res/drawable/komoju_ic_bitcash.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_bitcash.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_bitcash.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_bitcash.xml diff --git a/android/src/main/res/drawable/komoju_ic_credit_card.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_credit_card.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_credit_card.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_credit_card.xml diff --git a/android/src/main/res/drawable/komoju_ic_cvv.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_cvv.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_cvv.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_cvv.xml diff --git a/android/src/main/res/drawable/komoju_ic_daily_yamazaki.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_daily_yamazaki.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_daily_yamazaki.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_daily_yamazaki.xml diff --git a/android/src/main/res/drawable/komoju_ic_diners.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_diners.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_diners.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_diners.xml diff --git a/android/src/main/res/drawable/komoju_ic_family_mart.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_family_mart.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_family_mart.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_family_mart.xml diff --git a/android/src/main/res/drawable/komoju_ic_jcb.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_jcb.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_jcb.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_jcb.xml diff --git a/android/src/main/res/drawable/komoju_ic_konbini.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_konbini.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_konbini.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_konbini.xml diff --git a/android/src/main/res/drawable/komoju_ic_lawson.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_lawson.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_lawson.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_lawson.xml diff --git a/android/src/main/res/drawable/komoju_ic_linepay.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_linepay.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_linepay.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_linepay.xml diff --git a/android/src/main/res/drawable/komoju_ic_master.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_master.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_master.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_master.xml diff --git a/android/src/main/res/drawable/komoju_ic_merpay.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_merpay.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_merpay.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_merpay.xml diff --git a/android/src/main/res/drawable/komoju_ic_ministop.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_ministop.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_ministop.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_ministop.xml diff --git a/android/src/main/res/drawable/komoju_ic_net_cash.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_net_cash.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_net_cash.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_net_cash.xml diff --git a/android/src/main/res/drawable/komoju_ic_paidy.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_paidy.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_paidy.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_paidy.xml diff --git a/android/src/main/res/drawable/komoju_ic_pay_easy.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_pay_easy.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_pay_easy.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_pay_easy.xml diff --git a/android/src/main/res/drawable/komoju_ic_payment_status_completed.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_payment_status_completed.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_payment_status_completed.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_payment_status_completed.xml diff --git a/android/src/main/res/drawable/komoju_ic_payment_status_failed.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_payment_status_failed.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_payment_status_failed.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_payment_status_failed.xml diff --git a/android/src/main/res/drawable/komoju_ic_payment_status_konbini_pending.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_payment_status_konbini_pending.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_payment_status_konbini_pending.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_payment_status_konbini_pending.xml diff --git a/android/src/main/res/drawable/komoju_ic_payment_status_prenidng.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_payment_status_prenidng.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_payment_status_prenidng.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_payment_status_prenidng.xml diff --git a/android/src/main/res/drawable/komoju_ic_paypay.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_paypay.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_paypay.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_paypay.xml diff --git a/android/src/main/res/drawable/komoju_ic_rakuten_pay.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_rakuten_pay.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_rakuten_pay.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_rakuten_pay.xml diff --git a/android/src/main/res/drawable/komoju_ic_seico_mart.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_seico_mart.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_seico_mart.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_seico_mart.xml diff --git a/android/src/main/res/drawable/komoju_ic_seven_eleven.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_seven_eleven.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_seven_eleven.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_seven_eleven.xml diff --git a/android/src/main/res/drawable/komoju_ic_visa.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_visa.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_visa.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_visa.xml diff --git a/android/src/main/res/drawable/komoju_ic_web_money.xml b/shared/src/commonMain/composeResources/drawable/komoju_ic_web_money.xml similarity index 100% rename from android/src/main/res/drawable/komoju_ic_web_money.xml rename to shared/src/commonMain/composeResources/drawable/komoju_ic_web_money.xml diff --git a/android/src/main/res/drawable/komoju_img_payment_footer.xml b/shared/src/commonMain/composeResources/drawable/komoju_img_payment_footer.xml similarity index 100% rename from android/src/main/res/drawable/komoju_img_payment_footer.xml rename to shared/src/commonMain/composeResources/drawable/komoju_img_payment_footer.xml diff --git a/android/src/main/res/font/komoju_font_inter_bold.ttf b/shared/src/commonMain/composeResources/font/komoju_font_inter_bold.ttf similarity index 100% rename from android/src/main/res/font/komoju_font_inter_bold.ttf rename to shared/src/commonMain/composeResources/font/komoju_font_inter_bold.ttf diff --git a/android/src/main/res/font/komoju_font_inter_light.ttf b/shared/src/commonMain/composeResources/font/komoju_font_inter_light.ttf similarity index 100% rename from android/src/main/res/font/komoju_font_inter_light.ttf rename to shared/src/commonMain/composeResources/font/komoju_font_inter_light.ttf diff --git a/android/src/main/res/font/komoju_font_inter_medium.ttf b/shared/src/commonMain/composeResources/font/komoju_font_inter_medium.ttf similarity index 100% rename from android/src/main/res/font/komoju_font_inter_medium.ttf rename to shared/src/commonMain/composeResources/font/komoju_font_inter_medium.ttf diff --git a/android/src/main/res/font/komoju_font_inter_regular.ttf b/shared/src/commonMain/composeResources/font/komoju_font_inter_regular.ttf similarity index 100% rename from android/src/main/res/font/komoju_font_inter_regular.ttf rename to shared/src/commonMain/composeResources/font/komoju_font_inter_regular.ttf diff --git a/android/src/main/res/font/komoju_font_inter_semibold.ttf b/shared/src/commonMain/composeResources/font/komoju_font_inter_semibold.ttf similarity index 100% rename from android/src/main/res/font/komoju_font_inter_semibold.ttf rename to shared/src/commonMain/composeResources/font/komoju_font_inter_semibold.ttf diff --git a/android/src/main/res/values-ja/strings.xml b/shared/src/commonMain/composeResources/values-ja/strings.xml similarity index 100% rename from android/src/main/res/values-ja/strings.xml rename to shared/src/commonMain/composeResources/values-ja/strings.xml diff --git a/shared/src/commonMain/composeResources/values/strings.xml b/shared/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 0000000..2bd2a0f --- /dev/null +++ b/shared/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,121 @@ + + + this-should-not-be-the-case + The entered cardholder name cannot be empty + The entered cardholder name is not valid + The entered card number is not valid + The entered expiry date is not valid + The entered CVV is not valid + Payment successful + Payment failed + Awaiting payment + Your payment has been processed successfully. + Your payment has failed. + You need to go to your local %1$s and make the payment to proceed. + Your payment is awaiting processing. + Error + Receipt Number + Confirmation Code + Done + Update Payment method + View instructions + Okay + Have a question? Contact us + I will do it later + We noticed that you’ve canceled the payment process. If this was a mistake, you can try again to complete your purchase. + We attempted to process your payment, but something went wrong. Please update your payment method below to continue. + We tried to charge your card but, something went wrong. Please update your payment method below to continue + Back to store + Payment Options + Pay %1$s + Cardholder name + Full name on card + Card Number + MM/YY + CVV + Save this card for future payments + Daily Yamazaki + Family Mart + Lawson + MiniStop + SeicoMart + 7-Eleven + Full name on receipt + Name (shown on receipt) + Email + Enter Your Email Address + NET CASH Information + NET CASH id + Payment via Alipay + Payment via AU Pay + Payment via Mer Pay + Payment via Paypay + Payment via Rakuten Pay + Payment via Line Pay + You will be redirected to Alipay to complete the payment + You will be redirected to AU Pay to complete the payment + You will be redirected to Mer Pay to complete the payment + You will be redirected to Paypay to complete the payment + You will be redirected to Rakuten to complete the payment + You will be redirected to Line Pay to complete the payment + Continue to Alipay + Continue to AuPay + Continue to MerPay + Continue to PayPay + Continue to Rakuten + Continue to LinePay + Continue to Paidy + Note, a web window will be opened. After purchase you will be redirected back. + Full Name + Enter your name + Phone Number + Enter your phone number + Last Name + First Name + Last Name (Phonetic) + First Name (Phonetic) + WebMoney Information + Prepaid number + Payment Success + Thank you for your order + BitCash Information + Hiragana id + Bank Transfer + BitCash + Credit card + Konbini + NetCash + au Pay + AliPay + Mer Pay + PayPay + Rakuten Pay + Line Pay + Unknown + Other + Paidy + PayEasy + WebMoney + Total Payment + The entered name cannot be empty + The entered email is not valid + Please select a konbini brand + The entered phone number cannot be empty + The entered phone number is not valid + The entered net cash id cannot be empty + The entered net cash id is not valid + The entered bit cash id cannot be empty + The entered bit cash id is not valid + The entered prepaid number cannot be empty + The entered prepaid number is not valid + The entered last name cannot be empty + The entered first name cannot be empty + The entered first name phonetic cannot be empty + The entered first name phonetic must be a kana + The entered last name phonetic cannot be empty + The entered last name phonetic must be a kana + Yes + No + Are you sure you want to cancel the payment? + Cancel Payment? + \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/KomojuMobileSDKConfiguration.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/KomojuMobileSDKConfiguration.kt new file mode 100644 index 0000000..b9838bc --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/KomojuMobileSDKConfiguration.kt @@ -0,0 +1,29 @@ +package com.komoju.mobile.sdk + +import com.komoju.mobile.sdk.ui.theme.ConfigurableTheme +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +data class KomojuMobileSDKConfiguration( + val language: String, // Language setting for the payment UI. 'en' for English, 'ja' for Japanese + val currency: String, // Currency used in the transaction. USD for United States Dollar, JPY for Japanese Yen + val publishableKey: String?, // Public API key for Komoju integration. + val isDebugMode: Boolean, // Debug mode flag for logging and testing. + val sessionId: String?, // Unique session ID for payment transaction. + val redirectURL: String, // URL to redirect after payment completion. + val appScheme: String, // App schema for deep links. + val configurableTheme: ConfigurableTheme, // Custom theme for UI elements. + val inlinedProcessing: Boolean, // Flag to enable inlined processing. +) + +/** + * Extension function to check if the current configuration is valid for processing a payment. + * @return True if the configuration is non-null and contains both publishableKey and sessionId. + */ +@OptIn(ExperimentalContracts::class) +fun KomojuMobileSDKConfiguration?.canProcessPayment(): Boolean { + contract { + returns(true) implies (this@canProcessPayment != null) + } + return this?.publishableKey != null && this.sessionId != null +} diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/KomojuMobileSDKPaymentResult.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/KomojuMobileSDKPaymentResult.kt new file mode 100644 index 0000000..63cd489 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/KomojuMobileSDKPaymentResult.kt @@ -0,0 +1,3 @@ +package com.komoju.mobile.sdk + +data class KomojuMobileSDKPaymentResult(val isSuccessFul: Boolean) diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/PaymentMethod.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/PaymentMethod.kt index af8bd06..5f8997e 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/PaymentMethod.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/PaymentMethod.kt @@ -2,7 +2,7 @@ package com.komoju.mobile.sdk.entities import com.komoju.mobile.sdk.types.OffSitePaymentType -sealed interface PaymentMethod { +internal sealed interface PaymentMethod { val hashedGateway: String val exchangeRate: Double val currency: String diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/PaymentRequest.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/PaymentRequest.kt index 646ff3c..ac407a2 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/PaymentRequest.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/PaymentRequest.kt @@ -2,7 +2,7 @@ package com.komoju.mobile.sdk.entities import com.komoju.mobile.sdk.entities.PaymentMethod.Konbini.KonbiniBrand -sealed interface PaymentRequest { +internal sealed interface PaymentRequest { val paymentMethod: PaymentMethod data class Konbini(override val paymentMethod: PaymentMethod.Konbini, val konbiniBrand: KonbiniBrand, val email: String) : diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/SecureTokenRequest.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/SecureTokenRequest.kt index 8237135..b81d6e0 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/SecureTokenRequest.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/SecureTokenRequest.kt @@ -1,6 +1,6 @@ package com.komoju.mobile.sdk.entities -data class SecureTokenRequest( +internal data class SecureTokenRequest( val amount: String, val currency: String, val returnUrl: String, diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/SecureTokenResponse.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/SecureTokenResponse.kt index b27f290..9b50c2e 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/SecureTokenResponse.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/SecureTokenResponse.kt @@ -1,6 +1,6 @@ package com.komoju.mobile.sdk.entities -data class SecureTokenResponse(val id: String, val status: Status, val authURL: String) { +internal data class SecureTokenResponse(val id: String, val status: Status, val authURL: String) { enum class Status { OK, SKIPPED, diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/Session.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/Session.kt index 322f7cc..044778e 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/Session.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/entities/Session.kt @@ -1,3 +1,3 @@ package com.komoju.mobile.sdk.entities -data class Session(val paymentMethods: List) +internal data class Session(val paymentMethods: List) diff --git a/android/src/main/java/com/komoju/android/sdk/navigation/PaymentResultScreenModel.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/navigation/PaymentResultScreenModel.kt similarity index 51% rename from android/src/main/java/com/komoju/android/sdk/navigation/PaymentResultScreenModel.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/navigation/PaymentResultScreenModel.kt index 141924d..9bd93c3 100644 --- a/android/src/main/java/com/komoju/android/sdk/navigation/PaymentResultScreenModel.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/navigation/PaymentResultScreenModel.kt @@ -1,22 +1,22 @@ -package com.komoju.android.sdk.navigation +package com.komoju.mobile.sdk.navigation import androidx.compose.runtime.Composable import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.rememberNavigatorScreenModel import cafe.adriel.voyager.navigator.Navigator -import com.komoju.android.sdk.KomojuSDK +import com.komoju.mobile.sdk.KomojuMobileSDKPaymentResult -internal class PaymentResultScreenModel : ScreenModel { - var result: KomojuSDK.PaymentResult? = null +class PaymentResultScreenModel internal constructor(): ScreenModel { + var result: KomojuMobileSDKPaymentResult? = null private set - fun setResult(result: KomojuSDK.PaymentResult) { + fun setResult(result: KomojuMobileSDKPaymentResult) { this.result = result } } @Composable -internal fun Navigator.paymentResultScreenModel() = rememberNavigatorScreenModel( +fun Navigator.paymentResultScreenModel() = rememberNavigatorScreenModel( tag = PaymentResultScreenModel::class.simpleName, factory = ::PaymentResultScreenModel, ) diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/navigation/PlatformBackPress.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/navigation/PlatformBackPress.kt new file mode 100644 index 0000000..955f14e --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/navigation/PlatformBackPress.kt @@ -0,0 +1,11 @@ +package com.komoju.mobile.sdk.navigation + +import androidx.compose.runtime.Composable + +@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +internal expect class PlatformBackPressDispatcher private constructor() { + fun onBackPressed() +} + +@Composable +internal expect fun rememberPlatformBackPressDispatcher(): PlatformBackPressDispatcher diff --git a/android/src/main/java/com/komoju/android/sdk/navigation/RouterStateScreenModel.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/navigation/RouterStateScreenModel.kt similarity index 88% rename from android/src/main/java/com/komoju/android/sdk/navigation/RouterStateScreenModel.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/navigation/RouterStateScreenModel.kt index 2516610..b91a52f 100644 --- a/android/src/main/java/com/komoju/android/sdk/navigation/RouterStateScreenModel.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/navigation/RouterStateScreenModel.kt @@ -1,7 +1,7 @@ -package com.komoju.android.sdk.navigation +package com.komoju.mobile.sdk.navigation import cafe.adriel.voyager.core.model.ScreenModel -import com.komoju.android.sdk.ui.screens.Router +import com.komoju.mobile.sdk.ui.screens.Router import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/NetworkClient.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/NetworkClient.kt index 70d1bd9..d773559 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/NetworkClient.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/NetworkClient.kt @@ -1,23 +1,34 @@ package com.komoju.mobile.sdk.remote +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration import io.ktor.client.HttpClient import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.DEFAULT +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging import io.ktor.serialization.kotlinx.json.json import io.ktor.util.encodeBase64 import kotlinx.serialization.json.Json private const val BASE_URL = "https://komoju.com/api/" -internal fun createNetworkClient(publishableKey: String?) = HttpClient { +internal fun createNetworkClient(configuration: KomojuMobileSDKConfiguration) = HttpClient { defaultRequest { url(BASE_URL) - headers.append("Authorization", value = "Basic ${publishableKey?.encodeBase64()}") + headers.append("Authorization", value = "Basic ${configuration.publishableKey?.encodeBase64()}") headers.append("KOMOJU-VIA", "mobile_${PLATFORM.lowercase()}") headers.append("X-KOMOJU-API-VERSION", "2024-07-15") headers.append("Accept", "application/json") headers.append("Content-Type", "application/json") } + if (configuration.isDebugMode) { + install(Logging) { + logger = Logger.DEFAULT + level = LogLevel.ALL + } + } install(ContentNegotiation) { json( Json { diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/KomojuRemoteApi.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/KomojuRemoteApi.kt index 687436b..81771e6 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/KomojuRemoteApi.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/KomojuRemoteApi.kt @@ -1,18 +1,19 @@ package com.komoju.mobile.sdk.remote.apis +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration import com.komoju.mobile.sdk.remote.createNetworkClient -interface KomojuRemoteApi : AutoCloseable { +internal interface KomojuRemoteApi : AutoCloseable { val sessions: SessionApi val tokens: TokensApi companion object { - fun create(publishableKey: String?): KomojuRemoteApi = KomojuRemoteApiImpl(publishableKey) + fun create(configuration: KomojuMobileSDKConfiguration): KomojuRemoteApi = KomojuRemoteApiImpl(configuration) } } -internal class KomojuRemoteApiImpl(publishableKey: String?) : KomojuRemoteApi { - private val networkClient by lazy { createNetworkClient(publishableKey) } +internal class KomojuRemoteApiImpl(configuration: KomojuMobileSDKConfiguration) : KomojuRemoteApi { + private val networkClient by lazy { createNetworkClient(configuration) } override val sessions: SessionApi by lazy { SessionApiImpl(networkClient) } diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/SessionApi.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/SessionApi.kt index b97d632..6f68308 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/SessionApi.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/SessionApi.kt @@ -20,7 +20,7 @@ import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.http.contentType -interface SessionApi { +internal interface SessionApi { suspend fun show(id: String): Result suspend fun pay(id: String, paymentRequest: PaymentRequest): Result suspend fun pay(sessionID: String, token: String, amount: String, currency: String): Result diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/TokensApi.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/TokensApi.kt index 26b0ae9..3e1521c 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/TokensApi.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/apis/TokensApi.kt @@ -14,7 +14,7 @@ import io.ktor.client.request.setBody import io.ktor.http.ContentType import io.ktor.http.contentType -interface TokensApi { +internal interface TokensApi { suspend fun generateSecureToken(request: SecureTokenRequest): Result suspend fun verifySecureToken(token: String): Result } diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/PayByTokenRequestDto.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/PayByTokenRequestDto.kt index 99b668e..a0cbe51 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/PayByTokenRequestDto.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/PayByTokenRequestDto.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class PayByTokenRequestDto( +internal data class PayByTokenRequestDto( @SerialName("amount") val amount: String? = null, @SerialName("currency") val currency: String? = null, @SerialName("payment_details") val paymentDetails: String? = null, diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/PaymentErrorResponseDto.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/PaymentErrorResponseDto.kt index 8a012bf..d898b89 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/PaymentErrorResponseDto.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/PaymentErrorResponseDto.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class PaymentErrorResponseDto(@SerialName("error") val error: Error? = null) { +internal data class PaymentErrorResponseDto(@SerialName("error") val error: Error? = null) { @Serializable data class Error(@SerialName("code") val code: String? = null, @SerialName("message") val message: String? = null) } diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/SecureTokenRequestDto.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/SecureTokenRequestDto.kt index 3db7420..b053ae0 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/SecureTokenRequestDto.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/SecureTokenRequestDto.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class SecureTokenRequestDto( +internal data class SecureTokenRequestDto( @SerialName("amount") val amount: String? = null, @SerialName("currency") val currency: String? = null, @SerialName("return_url") val returnUrl: String? = null, diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/SecureTokenVerificationResponseDto.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/SecureTokenVerificationResponseDto.kt index 579bf26..fb03809 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/SecureTokenVerificationResponseDto.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/remote/dtos/SecureTokenVerificationResponseDto.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class SecureTokenVerificationResponseDto( +internal data class SecureTokenVerificationResponseDto( @SerialName("secure_token") val secureToken: String? = null, @SerialName("verification_status") val verificationStatus: String? = null, ) diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/types/OffSitePaymentType.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/types/OffSitePaymentType.kt index 6f8f332..3f4065e 100644 --- a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/types/OffSitePaymentType.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/types/OffSitePaymentType.kt @@ -1,6 +1,6 @@ package com.komoju.mobile.sdk.types -enum class OffSitePaymentType(val id: String) { +internal enum class OffSitePaymentType(val id: String) { AU_PAY("aupay"), ALI_PAY("alipay"), MER_PAY("merpay"), diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/ExternalScreenLauncherResult.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/ExternalScreenLauncherResult.kt new file mode 100644 index 0000000..bfd6e2a --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/ExternalScreenLauncherResult.kt @@ -0,0 +1,11 @@ +package com.komoju.mobile.sdk.ui.composables + +import androidx.compose.runtime.Composable + +@Composable +expect fun launchCustomTab(onResult: (Int) -> Unit): Launcher + +@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +expect class Launcher { + fun launch(input: I) +} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedPaymentPrimaryButton.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/InlinedPaymentPrimaryButton.kt similarity index 86% rename from android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedPaymentPrimaryButton.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/InlinedPaymentPrimaryButton.kt index c45200d..735f2d4 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedPaymentPrimaryButton.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/InlinedPaymentPrimaryButton.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.composables +package com.komoju.mobile.sdk.ui.composables import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -24,16 +24,16 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme -import com.komoju.android.sdk.ui.theme.LocalConfigurableTheme -import kotlin.time.Duration.Companion.seconds +import com.komoju.mobile.sdk.ui.theme.KomojuMobileSdkTheme +import com.komoju.mobile.sdk.ui.theme.LocalConfigurableTheme +import com.komoju.mobile.sdk.ui.theme.toColor import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.jetbrains.compose.ui.tooling.preview.Preview +import kotlin.time.Duration.Companion.seconds @Composable internal fun InlinedPaymentPrimaryButton( @@ -47,10 +47,10 @@ internal fun InlinedPaymentPrimaryButton( modifier = modifier, onClick = onClick, colors = ButtonDefaults.buttonColors( - containerColor = Color(configurableTheme.primaryButtonColor), - contentColor = Color(configurableTheme.primaryButtonContentColor), + containerColor = configurableTheme.primaryColor.toColor(), + contentColor = configurableTheme.primaryContentColor.toColor(), ), - shape = RoundedCornerShape(configurableTheme.primaryButtonCornerRadiusInDP.dp), + shape = RoundedCornerShape(configurableTheme.primaryShapeCornerRadiusInDp.dp), ) { Box( modifier = Modifier @@ -85,7 +85,7 @@ internal fun InlinedPaymentPrimaryButton( } } -internal enum class InlinedPaymentPrimaryButtonState { +enum class InlinedPaymentPrimaryButtonState { LOADING, IDLE, SUCCESS, @@ -93,12 +93,12 @@ internal enum class InlinedPaymentPrimaryButtonState { } @Composable -internal fun rememberInlinedPaymentPrimaryButtonState( +fun rememberInlinedPaymentPrimaryButtonState( default: InlinedPaymentPrimaryButtonState = InlinedPaymentPrimaryButtonState.IDLE, ): MutableState = rememberSaveable { mutableStateOf(default) } @Composable -@Preview(showBackground = true, showSystemUi = true) +@Preview private fun PaymentButtonPreview() { var state by rememberInlinedPaymentPrimaryButtonState() val coroutineScope = rememberCoroutineScope() diff --git a/android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedWebView.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/InlinedWebView.kt similarity index 51% rename from android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedWebView.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/InlinedWebView.kt index d95d442..4971a86 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedWebView.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/InlinedWebView.kt @@ -1,44 +1,49 @@ -package com.komoju.android.sdk.ui.composables +package com.komoju.mobile.sdk.ui.composables -import android.annotation.SuppressLint -import android.graphics.Color -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView import androidx.compose.foundation.Image import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.Text -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.core.net.toUri -import com.kevinnzou.web.AccompanistWebViewClient -import com.kevinnzou.web.WebView -import com.kevinnzou.web.rememberWebViewState -import com.komoju.android.sdk.R +import com.eygraber.uri.Uri +import com.multiplatform.webview.request.RequestInterceptor +import com.multiplatform.webview.request.WebRequest +import com.multiplatform.webview.request.WebRequestInterceptResult +import com.multiplatform.webview.web.WebView +import com.multiplatform.webview.web.WebViewNavigator +import com.multiplatform.webview.web.rememberWebViewNavigator +import com.multiplatform.webview.web.rememberWebViewState +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.delay -@SuppressLint("SetJavaScriptEnabled") @Composable internal fun InlinedWebView( modifier: Modifier, url: String, + appScheme: String, onDone: (String) -> Unit, onChallengePresented: () -> Unit, onCloseButtonClicked: () -> Unit, ) { + LaunchedEffect(url) { + delay(5.seconds) // TODO: Workaround as of now, replace with actual challenge detection + onChallengePresented() + } val state = rememberWebViewState(url) Column(modifier = modifier) { Row( @@ -61,8 +66,6 @@ internal fun InlinedWebView( contentDescription = "Close Payment Sheet", modifier = Modifier .clickable( - indication = ripple(bounded = true, radius = 24.dp), - interactionSource = remember { MutableInteractionSource() }, onClick = { onCloseButtonClicked() }, @@ -71,47 +74,44 @@ internal fun InlinedWebView( ) } WebView( - modifier = Modifier.weight(1f), + modifier = Modifier.fillMaxSize(), state = state, - onCreated = { nativeWebView -> - nativeWebView.clipToOutline = true - nativeWebView.setBackgroundColor(Color.TRANSPARENT) - nativeWebView.settings.apply { - domStorageEnabled = true - javaScriptEnabled = true - } - }, captureBackPresses = false, - client = remember { InlinedWebViewClient(onDone, onChallengePresented) }, + navigator = rememberWebViewNavigator( + requestInterceptor = WebViewRequestInterceptor( + onDeeplinkCaptured = onDone, + appScheme = appScheme, + ), + ), ) + DisposableEffect(Unit) { + state.webSettings.apply { + isJavaScriptEnabled = true + androidWebSettings.apply { + backgroundColor = Color.Transparent + domStorageEnabled = true + } + } + onDispose { } + } } } -private class InlinedWebViewClient(private val onDeeplinkCaptured: (String) -> Unit, private val onChallengePresented: () -> Unit) : - AccompanistWebViewClient() { - @Deprecated("Deprecated in Java") - override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean = view.checkAndOpen(url) - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean = view.checkAndOpen(request.url.toString()) +private class WebViewRequestInterceptor(private val onDeeplinkCaptured: (String) -> Unit, private val appScheme: String) : + RequestInterceptor { + override fun onInterceptUrlRequest(request: WebRequest, navigator: WebViewNavigator): WebRequestInterceptResult = request.checkAndOpen() - private fun WebView.checkAndOpen(url: String): Boolean { + private fun WebRequest.checkAndOpen(): WebRequestInterceptResult { try { - val uri = url.toUri() - if (uri.scheme == resources.getString(R.string.komoju_consumer_app_scheme)) { + val uri = Uri.parse(url) + if (uri.scheme == appScheme) { onDeeplinkCaptured(url) - return true + return WebRequestInterceptResult.Reject } else { error("Unsupported scheme for deeplink, load in webView Instead.") } } catch (_: Exception) { - loadUrl(url) - return false - } - } - - override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? { - if (request?.url.toString().contains("acs-challenge.testlab.3dsecure.cloud")) { - onChallengePresented() + return WebRequestInterceptResult.Allow } - return super.shouldInterceptRequest(view, request) } } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/composables/PrimaryButton.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/PrimaryButton.kt similarity index 73% rename from android/src/main/java/com/komoju/android/sdk/ui/composables/PrimaryButton.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/PrimaryButton.kt index 9006016..0e33482 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/composables/PrimaryButton.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/PrimaryButton.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.composables +package com.komoju.mobile.sdk.ui.composables import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -11,13 +11,13 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme -import com.komoju.android.sdk.ui.theme.LocalConfigurableTheme +import com.komoju.mobile.sdk.ui.theme.KomojuMobileSdkTheme +import com.komoju.mobile.sdk.ui.theme.LocalConfigurableTheme +import com.komoju.mobile.sdk.ui.theme.toColor +import org.jetbrains.compose.ui.tooling.preview.Preview @Composable internal fun PrimaryButton(text: String, modifier: Modifier = Modifier, onClick: () -> Unit) { @@ -26,10 +26,10 @@ internal fun PrimaryButton(text: String, modifier: Modifier = Modifier, onClick: modifier = modifier, onClick = onClick, colors = ButtonDefaults.buttonColors( - containerColor = Color(configurableTheme.primaryButtonColor), - contentColor = Color(configurableTheme.primaryButtonContentColor), + containerColor = configurableTheme.primaryColor.toColor(), + contentColor = configurableTheme.primaryContentColor.toColor(), ), - shape = RoundedCornerShape(configurableTheme.primaryButtonCornerRadiusInDP.dp), + shape = RoundedCornerShape(configurableTheme.primaryShapeCornerRadiusInDp.dp), ) { Box( modifier = Modifier @@ -43,7 +43,7 @@ internal fun PrimaryButton(text: String, modifier: Modifier = Modifier, onClick: } @Composable -@Preview(showBackground = true, showSystemUi = true) +@Preview private fun PaymentButtonPreview() { KomojuMobileSdkTheme { PrimaryButton( diff --git a/android/src/main/java/com/komoju/android/sdk/ui/composables/TextButton.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/TextButton.kt similarity index 95% rename from android/src/main/java/com/komoju/android/sdk/ui/composables/TextButton.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/TextButton.kt index e5ad509..77e1ced 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/composables/TextButton.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/TextButton.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.composables +package com.komoju.mobile.sdk.ui.composables import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/ThemedCircularProgressIndicator.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/ThemedCircularProgressIndicator.kt new file mode 100644 index 0000000..8c42760 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/composables/ThemedCircularProgressIndicator.kt @@ -0,0 +1,12 @@ +package com.komoju.mobile.sdk.ui.composables + +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import com.komoju.mobile.sdk.ui.theme.LocalConfigurableTheme +import com.komoju.mobile.sdk.ui.theme.toColor + +@Composable +internal fun ThemedCircularProgressIndicator() { + val configuration = LocalConfigurableTheme.current + CircularProgressIndicator(color = configuration.loaderColor.toColor()) +} diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/KomojuPaymentEntryPoint.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/KomojuPaymentEntryPoint.kt new file mode 100644 index 0000000..eab9a2d --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/KomojuPaymentEntryPoint.kt @@ -0,0 +1,32 @@ +package com.komoju.mobile.sdk.ui.screens + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.transitions.SlideTransition +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration +import com.komoju.mobile.sdk.ui.screens.payment.KomojuPaymentScreen +import com.komoju.mobile.sdk.ui.theme.KomojuMobileSdkTheme +import com.komoju.mobile.sdk.utils.Logger + +@Composable +fun KomojuPaymentEntryPoint(configuration: KomojuMobileSDKConfiguration, onCreated: @Composable (Navigator) -> Unit) { + Logger.setDebugMode(configuration.isDebugMode) + KomojuMobileSdkTheme(configuration) { + Box( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(.9f), + ) { + Navigator( + KomojuPaymentScreen(configuration), + ) { navigator -> + onCreated(navigator) + SlideTransition(navigator) + } + } + } +} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/KomojuPaymentRoutes.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/KomojuPaymentRoutes.kt similarity index 57% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/KomojuPaymentRoutes.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/KomojuPaymentRoutes.kt index f47b37e..d6c371c 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/KomojuPaymentRoutes.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/KomojuPaymentRoutes.kt @@ -1,39 +1,45 @@ -package com.komoju.android.sdk.ui.screens +package com.komoju.mobile.sdk.ui.screens -import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import com.komoju.android.sdk.KomojuSDK -import com.komoju.android.sdk.navigation.paymentResultScreenModel -import com.komoju.android.sdk.ui.screens.awating.KonbiniAwaitingPaymentScreen -import com.komoju.android.sdk.ui.screens.failed.PaymentFailedScreen -import com.komoju.android.sdk.ui.screens.failed.Reason -import com.komoju.android.sdk.ui.screens.success.PaymentSuccessScreen -import com.komoju.android.sdk.ui.screens.verify.ProcessPaymentScreen -import com.komoju.android.sdk.ui.screens.webview.WebViewScreen +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration +import com.komoju.mobile.sdk.KomojuMobileSDKPaymentResult import com.komoju.mobile.sdk.entities.Payment +import com.komoju.mobile.sdk.navigation.paymentResultScreenModel +import com.komoju.mobile.sdk.navigation.rememberPlatformBackPressDispatcher +import com.komoju.mobile.sdk.ui.screens.awating.KonbiniAwaitingPaymentScreen +import com.komoju.mobile.sdk.ui.screens.failed.PaymentFailedScreen +import com.komoju.mobile.sdk.ui.screens.failed.Reason +import com.komoju.mobile.sdk.ui.screens.success.PaymentSuccessScreen +import com.komoju.mobile.sdk.ui.screens.verify.ProcessPaymentScreen +import com.komoju.mobile.sdk.ui.screens.webview.WebViewScreen -internal sealed class Router { +sealed class Router { data object Pop : Router() data object PopAll : Router() data object PopToRoot : Router() data class Push(val route: KomojuPaymentRoute) : Router() data class Replace(val route: KomojuPaymentRoute) : Router() data class ReplaceAll(val route: KomojuPaymentRoute) : Router() - data class SetPaymentResultAndPop(val result: KomojuSDK.PaymentResult = KomojuSDK.PaymentResult(false)) : Router() + data class SetPaymentResultAndPop(val result: KomojuMobileSDKPaymentResult = KomojuMobileSDKPaymentResult(false)) : Router() } -internal sealed interface KomojuPaymentRoute { - data class KonbiniAwaitingPayment(val configuration: KomojuSDK.Configuration, val payment: Payment) : KomojuPaymentRoute +sealed interface KomojuPaymentRoute { + data class KonbiniAwaitingPayment(val configuration: KomojuMobileSDKConfiguration, val payment: Payment) : KomojuPaymentRoute - data class WebView(val url: String, val canComeBack: Boolean = false, val isJavaScriptEnabled: Boolean = false) : KomojuPaymentRoute + data class WebView( + val configuration: KomojuMobileSDKConfiguration, + val url: String, + val canComeBack: Boolean = false, + val isJavaScriptEnabled: Boolean = false, + ) : KomojuPaymentRoute data object PaymentSuccess : KomojuPaymentRoute data class PaymentFailed(val reason: Reason) : KomojuPaymentRoute - data class ProcessPayment(val configuration: KomojuSDK.Configuration, val processType: ProcessType) : KomojuPaymentRoute { + data class ProcessPayment(val configuration: KomojuMobileSDKConfiguration, val processType: ProcessType) : KomojuPaymentRoute { sealed interface ProcessType { data object Session : ProcessType data class VerifyTokenAndPay(val token: String, val amount: String, val currency: String) : ProcessType @@ -53,14 +59,14 @@ internal sealed interface KomojuPaymentRoute { } @Composable -internal fun RouterEffect(routerState: State, onHandled: () -> Unit) { +fun RouterEffect(routerState: State, onHandled: () -> Unit) { val navigator = LocalNavigator.currentOrThrow val router = routerState.value - val backPressDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + val backPressDispatcher = rememberPlatformBackPressDispatcher() val resultScreenModel = navigator.paymentResultScreenModel() LaunchedEffect(router) { when (router) { - is Router.Pop -> if (navigator.pop().not()) backPressDispatcher?.onBackPressed() + 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) @@ -70,7 +76,7 @@ internal fun RouterEffect(routerState: State, onHandled: () -> Unit) { null -> Unit is Router.SetPaymentResultAndPop -> { resultScreenModel.setResult(router.result) - if (navigator.pop().not()) backPressDispatcher?.onBackPressed() + if (navigator.pop().not()) backPressDispatcher.onBackPressed() } } onHandled() diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt similarity index 53% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt index 5d45a79..4462443 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.awating +package com.komoju.mobile.sdk.ui.screens.awating import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -21,8 +21,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -32,19 +30,41 @@ 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.types.Currency -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.composables.TextButton -import com.komoju.android.sdk.ui.composables.ThemedCircularProgressIndicator -import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute -import com.komoju.android.sdk.ui.screens.RouterEffect -import com.komoju.android.sdk.ui.theme.Gray200 -import com.komoju.android.sdk.ui.theme.Gray50 -import com.komoju.android.sdk.ui.theme.Gray700 -import com.komoju.android.sdk.utils.AmountUtils import com.komoju.mobile.sdk.entities.Payment import com.komoju.mobile.sdk.entities.PaymentStatus +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_awaiting_payment +import com.komoju.mobile.sdk.shared.generated.resources.komoju_awaiting_payment_instruction +import com.komoju.mobile.sdk.shared.generated.resources.komoju_confirmation_code +import com.komoju.mobile.sdk.shared.generated.resources.komoju_done +import com.komoju.mobile.sdk.shared.generated.resources.komoju_error +import com.komoju.mobile.sdk.shared.generated.resources.komoju_have_a_question_contact_us +import com.komoju.mobile.sdk.shared.generated.resources.komoju_i_will_do_it_later +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_payment_status_completed +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_payment_status_failed +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_payment_status_konbini_pending +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_payment_status_prenidng +import com.komoju.mobile.sdk.shared.generated.resources.komoju_okay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_failed +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_successful +import com.komoju.mobile.sdk.shared.generated.resources.komoju_receipt_number +import com.komoju.mobile.sdk.shared.generated.resources.komoju_total_payment +import com.komoju.mobile.sdk.shared.generated.resources.komoju_update_payment_method +import com.komoju.mobile.sdk.shared.generated.resources.komoju_view_instructions +import com.komoju.mobile.sdk.shared.generated.resources.komoju_your_payment_has_been_processed_successfully +import com.komoju.mobile.sdk.shared.generated.resources.komoju_your_payment_has_failed +import com.komoju.mobile.sdk.shared.generated.resources.komoju_your_payment_is_awaiting_processing +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.composables.TextButton +import com.komoju.mobile.sdk.ui.composables.ThemedCircularProgressIndicator +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentRoute +import com.komoju.mobile.sdk.ui.screens.RouterEffect +import com.komoju.mobile.sdk.ui.theme.Gray200 +import com.komoju.mobile.sdk.ui.theme.Gray50 +import com.komoju.mobile.sdk.ui.theme.Gray700 +import com.komoju.mobile.sdk.utils.AmountUtils +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource internal data class KonbiniAwaitingPaymentScreen(val route: KomojuPaymentRoute.KonbiniAwaitingPayment) : Screen { @Composable @@ -53,9 +73,11 @@ internal data class KonbiniAwaitingPaymentScreen(val route: KomojuPaymentRoute.K val uiState by screenModel.state.collectAsStateWithLifecycle() RouterEffect(screenModel.router.collectAsStateWithLifecycle(), screenModel::onRouteConsumed) uiState.payment?.let { - PaymentStatus( + ProcessForPaymentStatus( payment = it, - onPrimaryButtonClicked = screenModel::onPrimaryButtonClicked, + onPrimaryButtonClicked = { + screenModel.onPrimaryButtonClicked(route.configuration) + }, onSecondaryButtonClicked = screenModel::onSecondaryButtonClicked, ) } @@ -74,10 +96,10 @@ internal data class KonbiniAwaitingPaymentScreen(val route: KomojuPaymentRoute.K } @Composable -private fun PaymentStatus(payment: Payment, onPrimaryButtonClicked: () -> Unit, onSecondaryButtonClicked: () -> Unit) { +private fun ProcessForPaymentStatus(payment: Payment, onPrimaryButtonClicked: () -> Unit, onSecondaryButtonClicked: () -> Unit) { val displayPayableAmount by remember(payment.amount) { derivedStateOf { - AmountUtils.formatToDecimal(Currency.parse(payment.currency), payment.amount) + AmountUtils.formatToDecimal(payment.currency, payment.amount) } } Column( @@ -102,7 +124,7 @@ private fun PaymentStatus(payment: Payment, onPrimaryButtonClicked: () -> Unit, .padding(vertical = 16.dp), ) { Column(modifier = Modifier.background(Gray50, RoundedCornerShape(8.dp))) { - InformationItem(title = stringResource(R.string.komoju_total_payment), displayPayableAmount) + InformationItem(title = stringResource(Res.string.komoju_total_payment), displayPayableAmount) payment.additionalInformation.forEach { HorizontalDivider(color = Gray200, modifier = Modifier.padding(horizontal = 16.dp)) InformationItem(it.first, it.second) @@ -137,37 +159,37 @@ private fun InformationItem(title: String, description: String) { private val Payment.icon get() = when { - status == PaymentStatus.COMPLETED -> R.drawable.komoju_ic_payment_status_completed - status == PaymentStatus.FAILED -> R.drawable.komoju_ic_payment_status_failed - this is Payment.Konbini && status == PaymentStatus.AUTHORIZED -> R.drawable.komoju_ic_payment_status_konbini_pending - else -> R.drawable.komoju_ic_payment_status_prenidng + status == PaymentStatus.COMPLETED -> Res.drawable.komoju_ic_payment_status_completed + status == PaymentStatus.FAILED -> Res.drawable.komoju_ic_payment_status_failed + this is Payment.Konbini && status == PaymentStatus.AUTHORIZED -> Res.drawable.komoju_ic_payment_status_konbini_pending + else -> Res.drawable.komoju_ic_payment_status_prenidng } private val Payment.title @Composable get() = when (status) { - PaymentStatus.COMPLETED -> stringResource(R.string.komoju_payment_successful) - PaymentStatus.FAILED -> stringResource(R.string.komoju_payment_failed) - else -> stringResource(R.string.komoju_awaiting_payment) + PaymentStatus.COMPLETED -> stringResource(Res.string.komoju_payment_successful) + PaymentStatus.FAILED -> stringResource(Res.string.komoju_payment_failed) + else -> stringResource(Res.string.komoju_awaiting_payment) } private val Payment.description @Composable get() = when { - status == PaymentStatus.COMPLETED -> stringResource(R.string.komoju_your_payment_has_been_processed_successfully) - status == PaymentStatus.FAILED -> stringResource(R.string.komoju_your_payment_has_failed) + status == PaymentStatus.COMPLETED -> stringResource(Res.string.komoju_your_payment_has_been_processed_successfully) + status == PaymentStatus.FAILED -> stringResource(Res.string.komoju_your_payment_has_failed) this is Payment.Konbini && status == PaymentStatus.AUTHORIZED -> - stringResource(R.string.komoju_awaiting_payment_instruction, this.konbiniStoreKey) - else -> stringResource(R.string.komoju_your_payment_is_awaiting_processing) + stringResource(Res.string.komoju_awaiting_payment_instruction, this.konbiniStoreKey) + else -> stringResource(Res.string.komoju_your_payment_is_awaiting_processing) } private val Payment.additionalInformation @Composable get() = when { - this is Payment.Error -> listOf(stringResource(R.string.komoju_error) to code + message) + this is Payment.Error -> listOf(stringResource(Res.string.komoju_error) to code + message) this is Payment.Konbini && status == PaymentStatus.AUTHORIZED -> listOfNotNull( - receiptNumber?.let { stringResource(R.string.komoju_receipt_number) to it }, - confirmationCode?.let { stringResource(R.string.komoju_confirmation_code) to it }, + receiptNumber?.let { stringResource(Res.string.komoju_receipt_number) to it }, + confirmationCode?.let { stringResource(Res.string.komoju_confirmation_code) to it }, ) else -> emptyList() @@ -176,16 +198,16 @@ private val Payment.additionalInformation private val Payment.primaryButtonText @Composable get() = when { - status == PaymentStatus.COMPLETED -> stringResource(R.string.komoju_done) - status == PaymentStatus.FAILED -> stringResource(R.string.komoju_update_payment_method) - this is Payment.Konbini && status == PaymentStatus.AUTHORIZED -> stringResource(R.string.komoju_view_instructions) - else -> stringResource(R.string.komoju_okay) + status == PaymentStatus.COMPLETED -> stringResource(Res.string.komoju_done) + status == PaymentStatus.FAILED -> stringResource(Res.string.komoju_update_payment_method) + this is Payment.Konbini && status == PaymentStatus.AUTHORIZED -> stringResource(Res.string.komoju_view_instructions) + else -> stringResource(Res.string.komoju_okay) } private val Payment.secondaryButtonText @Composable get() = when { - status == PaymentStatus.FAILED -> stringResource(R.string.komoju_have_a_question_contact_us) - this is Payment.Konbini && status == PaymentStatus.AUTHORIZED -> stringResource(R.string.komoju_i_will_do_it_later) + status == PaymentStatus.FAILED -> stringResource(Res.string.komoju_have_a_question_contact_us) + this is Payment.Konbini && status == PaymentStatus.AUTHORIZED -> stringResource(Res.string.komoju_i_will_do_it_later) else -> null } diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreenModel.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreenModel.kt new file mode 100644 index 0000000..12c4a70 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreenModel.kt @@ -0,0 +1,26 @@ +package com.komoju.mobile.sdk.ui.screens.awating + +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration +import com.komoju.mobile.sdk.entities.Payment +import com.komoju.mobile.sdk.navigation.RouterStateScreenModel +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentRoute +import com.komoju.mobile.sdk.ui.screens.Router + +internal class KonbiniAwaitingPaymentScreenModel(payment: Payment? = null) : + RouterStateScreenModel( + KonbiniAwaitingPaymentUiState(payment), + ) { + + fun onPrimaryButtonClicked(configuration: KomojuMobileSDKConfiguration) { + when (val payment = state.value.payment) { + is Payment.Konbini -> + mutableRouter.value = + Router.Push(KomojuPaymentRoute.WebView(configuration, payment.instructionURL, canComeBack = true)) + else -> Unit + } + } + + fun onSecondaryButtonClicked() { + mutableRouter.value = Router.Pop + } +} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentUiState.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/awating/KonbiniAwaitingPaymentUiState.kt similarity index 75% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentUiState.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/awating/KonbiniAwaitingPaymentUiState.kt index c56f7c6..671daa5 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentUiState.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/awating/KonbiniAwaitingPaymentUiState.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.awating +package com.komoju.mobile.sdk.ui.screens.awating import com.komoju.mobile.sdk.entities.Payment diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/failed/PaymentFailedScreen.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/failed/PaymentFailedScreen.kt similarity index 62% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/failed/PaymentFailedScreen.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/failed/PaymentFailedScreen.kt index d7c2477..2bbc34b 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/failed/PaymentFailedScreen.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/failed/PaymentFailedScreen.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.failed +package com.komoju.mobile.sdk.ui.screens.failed import androidx.compose.foundation.Image import androidx.compose.foundation.clickable @@ -15,8 +15,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -24,10 +22,18 @@ 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.screens.RouterEffect +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_back_to_store +import com.komoju.mobile.sdk.shared.generated.resources.komoju_credit_card_error +import com.komoju.mobile.sdk.shared.generated.resources.komoju_error_other +import com.komoju.mobile.sdk.shared.generated.resources.komoju_error_user_cancel +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_payment_status_failed +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_failed +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentRoute +import com.komoju.mobile.sdk.ui.screens.RouterEffect +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource internal class PaymentFailedScreen(private val route: KomojuPaymentRoute.PaymentFailed) : Screen { @Composable @@ -50,14 +56,14 @@ private fun Screen.PaymentFailedScreenContent(route: KomojuPaymentRoute.PaymentF .clickable(onClick = screenModel::onCloseButtonClicked), ) } - Image(painterResource(R.drawable.komoju_ic_payment_status_failed), "status_icon") + Image(painterResource(Res.drawable.komoju_ic_payment_status_failed), "status_icon") Spacer(Modifier.height(16.dp)) - Text(stringResource(R.string.komoju_payment_failed), fontSize = 24.sp, fontWeight = FontWeight.Bold) + Text(stringResource(Res.string.komoju_payment_failed), fontSize = 24.sp, fontWeight = FontWeight.Bold) Text( text = when (route.reason) { - Reason.USER_CANCEL -> stringResource(R.string.komoju_error_user_cancel) - Reason.OTHER -> stringResource(R.string.komoju_error_other) - Reason.CREDIT_CARD_ERROR -> stringResource(R.string.komoju_credit_card_error) + Reason.USER_CANCEL -> stringResource(Res.string.komoju_error_user_cancel) + Reason.OTHER -> stringResource(Res.string.komoju_error_other) + Reason.CREDIT_CARD_ERROR -> stringResource(Res.string.komoju_credit_card_error) }, modifier = Modifier.padding(16.dp), textAlign = TextAlign.Center, @@ -67,7 +73,7 @@ private fun Screen.PaymentFailedScreenContent(route: KomojuPaymentRoute.PaymentF modifier = Modifier .fillMaxWidth() .padding(16.dp), - text = stringResource(R.string.komoju_back_to_store), + text = stringResource(Res.string.komoju_back_to_store), ) { screenModel.onBackToStoreButtonClicked() } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/failed/PaymentFailedScreenModel.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/failed/PaymentFailedScreenModel.kt similarity index 66% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/failed/PaymentFailedScreenModel.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/failed/PaymentFailedScreenModel.kt index 8f1fdcc..f212801 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/failed/PaymentFailedScreenModel.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/failed/PaymentFailedScreenModel.kt @@ -1,6 +1,6 @@ -package com.komoju.android.sdk.ui.screens.failed +package com.komoju.mobile.sdk.ui.screens.failed -import com.komoju.android.sdk.navigation.RouterStateScreenModel +import com.komoju.mobile.sdk.navigation.RouterStateScreenModel internal class PaymentFailedScreenModel : RouterStateScreenModel(Unit) { fun onCloseButtonClicked() { diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/failed/Reason.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/failed/Reason.kt similarity index 95% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/failed/Reason.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/failed/Reason.kt index cdc9fb3..0b97d7f 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/failed/Reason.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/failed/Reason.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.failed +package com.komoju.mobile.sdk.ui.screens.failed /** * Enum class representing the reasons for a payment failure. diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreen.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/KomojuPaymentScreen.kt similarity index 84% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreen.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/KomojuPaymentScreen.kt index 7f58185..bcffcc8 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreen.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/KomojuPaymentScreen.kt @@ -1,7 +1,5 @@ -package com.komoju.android.sdk.ui.screens.payment +package com.komoju.mobile.sdk.ui.screens.payment -import android.os.Parcelable -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.animation.animateContentSize import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -26,35 +24,33 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen -import com.komoju.android.sdk.KomojuSDK -import com.komoju.android.sdk.R -import com.komoju.android.sdk.ui.composables.InlinedWebView -import com.komoju.android.sdk.ui.composables.ThemedCircularProgressIndicator -import com.komoju.android.sdk.ui.screens.RouterEffect -import com.komoju.android.sdk.ui.screens.payment.composables.PaymentMethodForm -import com.komoju.android.sdk.ui.screens.payment.composables.PaymentMethodsRow -import com.komoju.android.sdk.ui.screens.payment.composables.PaymentSheetHandle -import com.komoju.android.sdk.utils.OffsiteCustomTabResultContract +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration import com.komoju.mobile.sdk.entities.PaymentMethod -import kotlinx.parcelize.Parcelize +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_img_payment_footer +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_options +import com.komoju.mobile.sdk.ui.composables.InlinedWebView +import com.komoju.mobile.sdk.ui.composables.ThemedCircularProgressIndicator +import com.komoju.mobile.sdk.ui.composables.launchCustomTab +import com.komoju.mobile.sdk.ui.screens.RouterEffect +import com.komoju.mobile.sdk.ui.screens.payment.composables.PaymentMethodForm +import com.komoju.mobile.sdk.ui.screens.payment.composables.PaymentMethodsRow +import com.komoju.mobile.sdk.ui.screens.payment.composables.PaymentSheetHandle +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource -@Parcelize -internal data class KomojuPaymentScreen(private val sdkConfiguration: KomojuSDK.Configuration) : - Screen, - Parcelable { +internal data class KomojuPaymentScreen(private val sdkConfiguration: KomojuMobileSDKConfiguration) : Screen { @Composable override fun Content() { val screenViewModel = rememberScreenModel { KomojuPaymentScreenModel(sdkConfiguration) } val uiState by screenViewModel.state.collectAsStateWithLifecycle() val offSitePaymentURL by screenViewModel.offSitePaymentURL.collectAsStateWithLifecycle() - val offsitePaymentLauncher = rememberLauncherForActivityResult(OffsiteCustomTabResultContract()) { + val offsitePaymentLauncher = launchCustomTab { screenViewModel.onOffsitePaymentResult() } LaunchedEffect(sdkConfiguration.sessionId) { @@ -74,7 +70,7 @@ internal data class KomojuPaymentScreen(private val sdkConfiguration: KomojuSDK. if (uiState.session != null) { Column { PaymentSheetHandle( - stringResource(R.string.komoju_payment_options), + stringResource(Res.string.komoju_payment_options), onCloseClicked = { screenViewModel.onCloseClicked() }, @@ -109,7 +105,7 @@ internal data class KomojuPaymentScreen(private val sdkConfiguration: KomojuSDK. } Image( modifier = Modifier.fillMaxWidth().height(54.dp).padding(horizontal = 16.dp), - painter = painterResource(R.drawable.komoju_img_payment_footer), + painter = painterResource(Res.drawable.komoju_img_payment_footer), contentDescription = "payment footer", ) } @@ -146,6 +142,7 @@ internal data class KomojuPaymentScreen(private val sdkConfiguration: KomojuSDK. }, ), url = inlineWebViewURL, + appScheme = sdkConfiguration.appScheme, onDone = { screenViewModel.onInlinedDeeplinkCaptured(it) }, diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt similarity index 68% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt index cc84d37..41fe29b 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt @@ -1,22 +1,8 @@ -package com.komoju.android.sdk.ui.screens.payment +package com.komoju.mobile.sdk.ui.screens.payment -import androidx.core.text.isDigitsOnly import cafe.adriel.voyager.core.model.screenModelScope -import com.komoju.android.sdk.KomojuSDK -import com.komoju.android.sdk.R -import com.komoju.android.sdk.navigation.RouterStateScreenModel -import com.komoju.android.sdk.ui.composables.InlinedPaymentPrimaryButtonState -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 -import com.komoju.android.sdk.utils.CreditCardUtils.isValidCVV -import com.komoju.android.sdk.utils.CreditCardUtils.isValidCardHolderNameChar -import com.komoju.android.sdk.utils.CreditCardUtils.isValidCardNumber -import com.komoju.android.sdk.utils.CreditCardUtils.isValidExpiryDate -import com.komoju.android.sdk.utils.DeeplinkEntity -import com.komoju.android.sdk.utils.isKatakanaOnly -import com.komoju.android.sdk.utils.isValidEmail -import com.komoju.android.sdk.utils.verifyTokenAndProcessPayment +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration +import com.komoju.mobile.sdk.KomojuMobileSDKPaymentResult import com.komoju.mobile.sdk.entities.Payment import com.komoju.mobile.sdk.entities.PaymentMethod import com.komoju.mobile.sdk.entities.PaymentRequest @@ -28,17 +14,57 @@ import com.komoju.mobile.sdk.entities.SecureTokenResponse.Status.NEEDS_VERIFY 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.navigation.RouterStateScreenModel import com.komoju.mobile.sdk.remote.apis.KomojuRemoteApi -import kotlin.time.Duration.Companion.milliseconds +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_cadrholder_name_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_please_select_a_konbini_brand +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_bit_cash_id_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_bit_cash_id_is_not_valid +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_card_number_is_not_valid +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_cardholder_name_is_not_valid +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_cvv_is_not_valid +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_email_is_not_valid +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_expiry_date_is_not_valid +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_first_name_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_first_name_phonetic_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_first_name_phonetic_must_be_a_kana +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_last_name_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_last_name_phonetic_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_last_name_phonetic_must_be_a_kana +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_name_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_net_cash_id_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_net_cash_id_is_not_valid +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_phone_number_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_phone_number_is_not_valid +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_prepaid_number_cannot_be_empty +import com.komoju.mobile.sdk.shared.generated.resources.komoju_the_entered_prepaid_number_is_not_valid +import com.komoju.mobile.sdk.ui.composables.InlinedPaymentPrimaryButtonState +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentRoute +import com.komoju.mobile.sdk.ui.screens.Router +import com.komoju.mobile.sdk.ui.screens.failed.Reason +import com.komoju.mobile.sdk.utils.CreditCardUtils.isValidCVV +import com.komoju.mobile.sdk.utils.CreditCardUtils.isValidCardHolderNameChar +import com.komoju.mobile.sdk.utils.CreditCardUtils.isValidCardNumber +import com.komoju.mobile.sdk.utils.CreditCardUtils.isValidExpiryDate +import com.komoju.mobile.sdk.utils.DeeplinkEntity +import com.komoju.mobile.sdk.utils.Logger +import com.komoju.mobile.sdk.utils.Platform +import com.komoju.mobile.sdk.utils.currentPlatform +import com.komoju.mobile.sdk.utils.isDigitsOnly +import com.komoju.mobile.sdk.utils.isKanaOnly +import com.komoju.mobile.sdk.utils.isValidEmail +import com.komoju.mobile.sdk.utils.verifyTokenAndProcessPayment import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds -internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configuration) : +internal class KomojuPaymentScreenModel(private val config: KomojuMobileSDKConfiguration) : RouterStateScreenModel(KomojuPaymentUIState()) { - private val komojuApi: KomojuRemoteApi = KomojuRemoteApi.create(config.publishableKey) + private val komojuApi: KomojuRemoteApi = KomojuRemoteApi.create(config) private val _offSitePaymentURL = MutableStateFlow(null) val offSitePaymentURL = _offSitePaymentURL.asStateFlow() @@ -71,10 +97,7 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat } fun onCreditCardDisplayDataChange(creditCardDisplayData: CreditCardDisplayData) { - if (creditCardDisplayData.creditCardNumber.length <= 16 && - creditCardDisplayData.creditCardExpiryDate.length <= 4 && - creditCardDisplayData.creditCardCvv.length <= 7 - ) { + if (creditCardDisplayData.creditCardNumber.length <= 16 && creditCardDisplayData.creditCardExpiryDate.length <= 4 && creditCardDisplayData.creditCardCvv.length <= 7) { mutableState.update { it.copy( creditCardDisplayData = creditCardDisplayData.copy( @@ -160,13 +183,17 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat mutableState.update { it.copy(inlinedCreditCardProcessingURL = tokens.authURL) } } else { mutableRouter.value = - Router.ReplaceAll(KomojuPaymentRoute.WebView(url = tokens.authURL, isJavaScriptEnabled = true)) + Router.ReplaceAll(KomojuPaymentRoute.WebView(config, url = tokens.authURL, isJavaScriptEnabled = true)) } } - ERRORED, UNKNOWN -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.CREDIT_CARD_ERROR)) + ERRORED, UNKNOWN -> { + Logger.d("Error creating secure tokens status ${tokens.status}") + mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.CREDIT_CARD_ERROR)) + } } }.onFailure { + Logger.e(it) mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.CREDIT_CARD_ERROR)) } } @@ -196,8 +223,8 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat }, onSuccess = { changeInlinePaymentState(InlinedPaymentPrimaryButtonState.SUCCESS) - delay(400.milliseconds) - mutableRouter.value = Router.SetPaymentResultAndPop(KomojuSDK.PaymentResult(isSuccessFul = it.isSuccessful())) + delay(400.milliseconds) // Wait till Check icon is at-least visible to the User + mutableRouter.value = Router.SetPaymentResultAndPop(KomojuMobileSDKPaymentResult(isSuccessFul = it.isSuccessful())) }, ) } @@ -214,12 +241,19 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat private fun Payment.handle() { when (this) { is Payment.Konbini -> mutableRouter.value = Router.Replace(KomojuPaymentRoute.KonbiniAwaitingPayment(config, payment = this)) - is Payment.OffSitePayment -> _offSitePaymentURL.value = redirectURL - is Payment.Completed -> - mutableRouter.value = - Router.SetPaymentResultAndPop(KomojuSDK.PaymentResult(isSuccessFul = status == PaymentStatus.CAPTURED)) - is Payment.BankTransfer -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.WebView(url = instructionURL)) - is Payment.PayEasy -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.WebView(url = instructionURL)) + is Payment.OffSitePayment -> when (currentPlatform) { + // we will use WebView in IOS first. + Platform.IOS -> mutableRouter.value = + Router.Replace(KomojuPaymentRoute.WebView(config, url = redirectURL, isJavaScriptEnabled = true, canComeBack = false)) + + else -> _offSitePaymentURL.value = redirectURL + } + + is Payment.Completed -> mutableRouter.value = + Router.SetPaymentResultAndPop(KomojuMobileSDKPaymentResult(isSuccessFul = status == PaymentStatus.CAPTURED)) + + is Payment.BankTransfer -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.WebView(config, url = instructionURL)) + is Payment.PayEasy -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.WebView(config, url = instructionURL)) else -> Unit } } @@ -233,30 +267,30 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat is PaymentMethod.WebMoney -> state.value.webMoneyDisplayData.validate() is PaymentMethod.BankTransfer, is PaymentMethod.PayEasy, - -> state.value.commonDisplayData.validate() + -> state.value.commonDisplayData.validate() is PaymentMethod.OffSitePayment -> true // No input required for Offsite payment else -> false } private fun CommonDisplayData.validate(): Boolean { - val lastNameError = if (lastName.isBlank()) R.string.komoju_the_entered_last_name_cannot_be_empty else null - val firstNameError = if (firstName.isBlank()) R.string.komoju_the_entered_first_name_cannot_be_empty else null + val lastNameError = if (lastName.isBlank()) Res.string.komoju_the_entered_last_name_cannot_be_empty else null + val firstNameError = if (firstName.isBlank()) Res.string.komoju_the_entered_first_name_cannot_be_empty else null val firstNamePhoneticError = when { - firstNamePhonetic.isBlank() -> R.string.komoju_the_entered_first_name_phonetic_cannot_be_empty - firstNamePhonetic.isKatakanaOnly.not() -> R.string.komoju_the_entered_first_name_phonetic_must_be_a_kana + firstNamePhonetic.isBlank() -> Res.string.komoju_the_entered_first_name_phonetic_cannot_be_empty + firstNamePhonetic.isKanaOnly.not() -> Res.string.komoju_the_entered_first_name_phonetic_must_be_a_kana else -> null } val lastNamePhoneticError = when { - lastNamePhonetic.isBlank() -> R.string.komoju_the_entered_last_name_phonetic_cannot_be_empty - lastNamePhonetic.isKatakanaOnly.not() -> R.string.komoju_the_entered_last_name_phonetic_must_be_a_kana + lastNamePhonetic.isBlank() -> Res.string.komoju_the_entered_last_name_phonetic_cannot_be_empty + lastNamePhonetic.isKanaOnly.not() -> Res.string.komoju_the_entered_last_name_phonetic_must_be_a_kana else -> null } - val emailError = if (email.isValidEmail.not()) R.string.komoju_the_entered_email_is_not_valid else null + val emailError = if (email.isValidEmail.not()) Res.string.komoju_the_entered_email_is_not_valid else null val phoneNumberError = when { - phoneNumber.isBlank() -> R.string.komoju_the_entered_phone_number_cannot_be_empty - phoneNumber.length < 7 -> R.string.komoju_the_entered_phone_number_is_not_valid - phoneNumber.isDigitsOnly().not() -> R.string.komoju_the_entered_phone_number_is_not_valid + phoneNumber.isBlank() -> Res.string.komoju_the_entered_phone_number_cannot_be_empty + phoneNumber.length < 7 -> Res.string.komoju_the_entered_phone_number_is_not_valid + phoneNumber.isDigitsOnly.not() -> Res.string.komoju_the_entered_phone_number_is_not_valid else -> null } mutableState.update { @@ -271,18 +305,13 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat ), ) } - return lastNameError == null && - firstNameError == null && - firstNamePhoneticError == null && - lastNamePhoneticError == null && - emailError == null && - phoneNumberError == null + return lastNameError == null && firstNameError == null && firstNamePhoneticError == null && lastNamePhoneticError == null && emailError == null && phoneNumberError == null } private fun WebMoneyDisplayData.validate(): Boolean { val prepaidNumberError = when { - prepaidNumber.isBlank() -> R.string.komoju_the_entered_prepaid_number_cannot_be_empty - prepaidNumber.length != 16 -> R.string.komoju_the_entered_prepaid_number_is_not_valid + prepaidNumber.isBlank() -> Res.string.komoju_the_entered_prepaid_number_cannot_be_empty + prepaidNumber.length != 16 -> Res.string.komoju_the_entered_prepaid_number_is_not_valid else -> null } mutableState.update { @@ -297,8 +326,8 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat private fun BitCashDisplayData.validate(): Boolean { val idError = when { - bitCashId.isBlank() -> R.string.komoju_the_entered_bit_cash_id_cannot_be_empty - bitCashId.length != 16 -> R.string.komoju_the_entered_bit_cash_id_is_not_valid + bitCashId.isBlank() -> Res.string.komoju_the_entered_bit_cash_id_cannot_be_empty + bitCashId.length != 16 -> Res.string.komoju_the_entered_bit_cash_id_is_not_valid else -> null } mutableState.update { @@ -313,8 +342,8 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat private fun NetCashDisplayData.validate(): Boolean { val idError = when { - netCashId.isBlank() -> R.string.komoju_the_entered_net_cash_id_cannot_be_empty - netCashId.length !in 16..20 -> R.string.komoju_the_entered_net_cash_id_is_not_valid + netCashId.isBlank() -> Res.string.komoju_the_entered_net_cash_id_cannot_be_empty + netCashId.length !in 16..20 -> Res.string.komoju_the_entered_net_cash_id_is_not_valid else -> null } mutableState.update { @@ -329,13 +358,13 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat private fun PaidyDisplayData.validate(): Boolean { val fullNameError = when { - fullName.isBlank() -> R.string.komoju_the_entered_name_cannot_be_empty + fullName.isBlank() -> Res.string.komoju_the_entered_name_cannot_be_empty else -> null } val phoneNumberError = when { - phoneNumber.isBlank() -> R.string.komoju_the_entered_phone_number_cannot_be_empty - phoneNumber.length < 7 -> R.string.komoju_the_entered_phone_number_is_not_valid - phoneNumber.isDigitsOnly().not() -> R.string.komoju_the_entered_phone_number_is_not_valid + phoneNumber.isBlank() -> Res.string.komoju_the_entered_phone_number_cannot_be_empty + phoneNumber.length < 7 -> Res.string.komoju_the_entered_phone_number_is_not_valid + phoneNumber.isDigitsOnly.not() -> Res.string.komoju_the_entered_phone_number_is_not_valid else -> null } mutableState.update { @@ -351,15 +380,15 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat private fun CreditCardDisplayData.validate(): Boolean { val fullNameOnCardError = when { - fullNameOnCard.isBlank() -> R.string.komoju_cadrholder_name_cannot_be_empty + fullNameOnCard.isBlank() -> Res.string.komoju_cadrholder_name_cannot_be_empty fullNameOnCard.all { char -> char.isValidCardHolderNameChar() } -> null - else -> R.string.komoju_the_entered_cardholder_name_is_not_valid + else -> Res.string.komoju_the_entered_cardholder_name_is_not_valid } val creditCardError = run { when { - creditCardNumber.isValidCardNumber().not() -> R.string.komoju_the_entered_card_number_is_not_valid - creditCardExpiryDate.isValidExpiryDate().not() -> R.string.komoju_the_entered_expiry_date_is_not_valid - creditCardCvv.isValidCVV().not() -> R.string.komoju_the_entered_cvv_is_not_valid + creditCardNumber.isValidCardNumber().not() -> Res.string.komoju_the_entered_card_number_is_not_valid + creditCardExpiryDate.isValidExpiryDate().not() -> Res.string.komoju_the_entered_expiry_date_is_not_valid + creditCardCvv.isValidCVV().not() -> Res.string.komoju_the_entered_cvv_is_not_valid else -> null } } @@ -375,9 +404,9 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat } private fun KonbiniDisplayData.validate(commonDisplayData: CommonDisplayData): Boolean { - val nameError = if (receiptName.trim().isEmpty()) R.string.komoju_the_entered_name_cannot_be_empty else null - val emailError = if (commonDisplayData.email.isValidEmail.not()) R.string.komoju_the_entered_email_is_not_valid else null - val konbiniBrandNullError = if (selectedKonbiniBrand == null) R.string.komoju_please_select_a_konbini_brand else null + val nameError = if (receiptName.trim().isEmpty()) Res.string.komoju_the_entered_name_cannot_be_empty else null + val emailError = if (commonDisplayData.email.isValidEmail.not()) Res.string.komoju_the_entered_email_is_not_valid else null + val konbiniBrandNullError = if (selectedKonbiniBrand == null) Res.string.komoju_please_select_a_konbini_brand else null mutableState.update { it.copy( konbiniDisplayData = it.konbiniDisplayData.copy( @@ -443,6 +472,7 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat email = state.value.commonDisplayData.email, phoneNumber = state.value.commonDisplayData.phoneNumber, ) + is PaymentMethod.WebMoney -> PaymentRequest.WebMoney( paymentMethod = this, prepaidNumber = state.value.webMoneyDisplayData.prepaidNumber, diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentUIState.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/KomojuPaymentUIState.kt similarity index 59% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentUIState.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/KomojuPaymentUIState.kt index 89f8f2b..2d7d7ec 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentUIState.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/KomojuPaymentUIState.kt @@ -1,9 +1,10 @@ -package com.komoju.android.sdk.ui.screens.payment +package com.komoju.mobile.sdk.ui.screens.payment -import com.komoju.android.sdk.ui.composables.InlinedPaymentPrimaryButtonState -import com.komoju.android.sdk.utils.empty import com.komoju.mobile.sdk.entities.PaymentMethod import com.komoju.mobile.sdk.entities.Session +import com.komoju.mobile.sdk.ui.composables.InlinedPaymentPrimaryButtonState +import com.komoju.mobile.sdk.utils.empty +import org.jetbrains.compose.resources.StringResource internal data class KomojuPaymentUIState( val isLoading: Boolean = true, @@ -28,22 +29,22 @@ internal data class CommonDisplayData( val firstNamePhonetic: String = String.empty, val email: String = String.empty, val phoneNumber: String = String.empty, - val fullNameErrorStringResource: Int? = null, - val lastNameErrorStringResource: Int? = null, - val firstNameErrorStringResource: Int? = null, - val lastNamePhoneticErrorStringResource: Int? = null, - val firstNamePhoneticErrorStringResource: Int? = null, - val emailErrorStringResource: Int? = null, - val phoneNumberErrorStringResource: Int? = null, + val fullNameErrorStringResource: StringResource? = null, + val lastNameErrorStringResource: StringResource? = null, + val firstNameErrorStringResource: StringResource? = null, + val lastNamePhoneticErrorStringResource: StringResource? = null, + val firstNamePhoneticErrorStringResource: StringResource? = null, + val emailErrorStringResource: StringResource? = null, + val phoneNumberErrorStringResource: StringResource? = null, ) internal data class CreditCardDisplayData( val fullNameOnCard: String = String.empty, - val fullNameOnCardErrorStringResource: Int? = null, + val fullNameOnCardErrorStringResource: StringResource? = null, val creditCardNumber: String = String.empty, val creditCardExpiryDate: String = String.empty, val creditCardCvv: String = String.empty, - val creditCardErrorStringResource: Int? = null, + val creditCardErrorStringResource: StringResource? = null, val canSaveCard: Boolean = false, val saveCard: Boolean = false, val inlinePaymentEnabled: Boolean = false, @@ -52,21 +53,24 @@ internal data class CreditCardDisplayData( internal data class KonbiniDisplayData( val receiptName: String = String.empty, - val receiptNameErrorStringResource: Int? = null, - val receiptEmailErrorStringResource: Int? = null, + val receiptNameErrorStringResource: StringResource? = null, + val receiptEmailErrorStringResource: StringResource? = null, val selectedKonbiniBrand: PaymentMethod.Konbini.KonbiniBrand? = null, - val konbiniBrandNullErrorStringResource: Int? = null, + val konbiniBrandNullErrorStringResource: StringResource? = null, ) -internal data class BitCashDisplayData(val bitCashId: String = String.empty, val bitCashErrorStringResource: Int? = null) +internal data class BitCashDisplayData(val bitCashId: String = String.empty, val bitCashErrorStringResource: StringResource? = null) -internal data class NetCashDisplayData(val netCashId: String = String.empty, val netCashErrorStringResource: Int? = null) +internal data class NetCashDisplayData(val netCashId: String = String.empty, val netCashErrorStringResource: StringResource? = null) -internal data class WebMoneyDisplayData(val prepaidNumber: String = String.empty, val prepaidNumberErrorStringResource: Int? = null) +internal data class WebMoneyDisplayData( + val prepaidNumber: String = String.empty, + val prepaidNumberErrorStringResource: StringResource? = null, +) internal data class PaidyDisplayData( val fullName: String = String.empty, - val fullNameErrorStringResource: Int? = null, + val fullNameErrorStringResource: StringResource? = null, val phoneNumber: String = String.empty, - val phoneNumberErrorStringResource: Int? = null, + val phoneNumberErrorStringResource: StringResource? = null, ) diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/BankForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/BankForm.kt similarity index 70% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/BankForm.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/BankForm.kt index 6f2d165..933a768 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/BankForm.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/BankForm.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -12,19 +12,27 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.types.Currency -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.screens.payment.CommonDisplayData -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme -import com.komoju.android.sdk.utils.AmountUtils import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_email +import com.komoju.mobile.sdk.shared.generated.resources.komoju_enter_your_email_address +import com.komoju.mobile.sdk.shared.generated.resources.komoju_enter_your_phone_number +import com.komoju.mobile.sdk.shared.generated.resources.komoju_first_name +import com.komoju.mobile.sdk.shared.generated.resources.komoju_first_name_phonetic +import com.komoju.mobile.sdk.shared.generated.resources.komoju_last_name +import com.komoju.mobile.sdk.shared.generated.resources.komoju_last_name_phonetic +import com.komoju.mobile.sdk.shared.generated.resources.komoju_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_phone_number +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.payment.CommonDisplayData +import com.komoju.mobile.sdk.ui.theme.KomojuMobileSdkTheme +import com.komoju.mobile.sdk.utils.AmountUtils +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview internal val JAPANESE_LOCALE = Locale("ja") @@ -37,7 +45,7 @@ internal fun BankForm( ) { val displayPayableAmount by remember(bankTransfer.amount) { derivedStateOf { - AmountUtils.formatToDecimal(Currency.parse(bankTransfer.currency), bankTransfer.amount) + AmountUtils.formatToDecimal(bankTransfer.currency, bankTransfer.amount) } } Column { @@ -46,8 +54,8 @@ internal fun BankForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.lastName, - title = stringResource(R.string.komoju_last_name), - placeholder = stringResource(R.string.komoju_last_name), + title = stringResource(Res.string.komoju_last_name), + placeholder = stringResource(Res.string.komoju_last_name), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(lastName = it)) }, @@ -61,8 +69,8 @@ internal fun BankForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.firstName, - title = stringResource(R.string.komoju_first_name), - placeholder = stringResource(R.string.komoju_first_name), + title = stringResource(Res.string.komoju_first_name), + placeholder = stringResource(Res.string.komoju_first_name), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(firstName = it)) }, @@ -76,8 +84,8 @@ internal fun BankForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.lastNamePhonetic, - title = stringResource(R.string.komoju_last_name_phonetic), - placeholder = stringResource(R.string.komoju_last_name_phonetic), + title = stringResource(Res.string.komoju_last_name_phonetic), + placeholder = stringResource(Res.string.komoju_last_name_phonetic), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(lastNamePhonetic = it)) }, @@ -92,8 +100,8 @@ internal fun BankForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.firstNamePhonetic, - title = stringResource(R.string.komoju_first_name_phonetic), - placeholder = stringResource(R.string.komoju_first_name_phonetic), + title = stringResource(Res.string.komoju_first_name_phonetic), + placeholder = stringResource(Res.string.komoju_first_name_phonetic), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(firstNamePhonetic = it)) }, @@ -108,8 +116,8 @@ internal fun BankForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.email, - title = stringResource(R.string.komoju_email), - placeholder = stringResource(R.string.komoju_enter_your_email_address), + title = stringResource(Res.string.komoju_email), + placeholder = stringResource(Res.string.komoju_enter_your_email_address), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(email = it)) }, @@ -123,8 +131,8 @@ internal fun BankForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.phoneNumber, - title = stringResource(R.string.komoju_phone_number), - placeholder = stringResource(R.string.komoju_enter_your_phone_number), + title = stringResource(Res.string.komoju_phone_number), + placeholder = stringResource(Res.string.komoju_enter_your_phone_number), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(phoneNumber = it)) }, @@ -138,14 +146,14 @@ internal fun BankForm( modifier = Modifier .padding(16.dp) .fillMaxWidth(), - text = stringResource(R.string.komoju_pay, displayPayableAmount), + text = stringResource(Res.string.komoju_pay, displayPayableAmount), onClick = onPayButtonClicked, ) } } @Composable -@Preview(showBackground = true) +@Preview private fun BankFormPreview() { val bankTransfer = PaymentMethod.BankTransfer( hashedGateway = "hashedGateway", diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/BitCashForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/BitCashForm.kt similarity index 61% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/BitCashForm.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/BitCashForm.kt index a3f66b3..231da31 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/BitCashForm.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/BitCashForm.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -10,14 +10,16 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.types.Currency -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.screens.payment.BitCashDisplayData -import com.komoju.android.sdk.utils.AmountUtils import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_bitcash_information +import com.komoju.mobile.sdk.shared.generated.resources.komoju_hiragana_id +import com.komoju.mobile.sdk.shared.generated.resources.komoju_pay +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.payment.BitCashDisplayData +import com.komoju.mobile.sdk.utils.AmountUtils +import org.jetbrains.compose.resources.stringResource @Composable internal fun BitCashForm( @@ -28,14 +30,14 @@ internal fun BitCashForm( ) { val displayPayableAmount by remember(bitCash.amount) { derivedStateOf { - AmountUtils.formatToDecimal(Currency.parse(bitCash.currency), bitCash.amount) + AmountUtils.formatToDecimal(bitCash.currency, bitCash.amount) } } Column { TextField( value = bitCashDisplayData.bitCashId, - title = stringResource(R.string.komoju_bitcash_information), - placeholder = stringResource(R.string.komoju_hiragana_id), + title = stringResource(Res.string.komoju_bitcash_information), + placeholder = stringResource(Res.string.komoju_hiragana_id), onValueChange = { onBitCashDisplayDataChange(bitCashDisplayData.copy(bitCashId = it)) }, @@ -46,7 +48,7 @@ internal fun BitCashForm( modifier = Modifier .padding(16.dp) .fillMaxWidth(), - text = stringResource(R.string.komoju_pay, displayPayableAmount), + text = stringResource(Res.string.komoju_pay, displayPayableAmount), onClick = onPayButtonClicked, ) } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/CompatTextField.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/CompatTextField.kt similarity index 51% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/CompatTextField.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/CompatTextField.kt index d1f156f..112b65f 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/CompatTextField.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/CompatTextField.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions @@ -7,16 +7,16 @@ import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.intl.LocaleList import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.ui.theme.Gray200 -import com.komoju.android.sdk.ui.theme.LocalConfigurableTheme -import com.komoju.android.sdk.ui.theme.Red600 +import com.komoju.mobile.sdk.ui.theme.Gray200 +import com.komoju.mobile.sdk.ui.theme.LocalConfigurableTheme +import com.komoju.mobile.sdk.ui.theme.Red600 +import com.komoju.mobile.sdk.ui.theme.toColor @Composable internal fun CompatTextField( @@ -59,46 +59,9 @@ internal fun CompatTextField( ), colors = OutlinedTextFieldDefaults.colors() .copy( - focusedLabelColor = Color(configurableTheme.primaryButtonColor), - focusedIndicatorColor = Color(configurableTheme.primaryButtonColor), + focusedLabelColor = configurableTheme.primaryColor.toColor(), + focusedIndicatorColor = configurableTheme.primaryColor.toColor(), unfocusedIndicatorColor = Gray200, ), ) -// Column { -// Text( -// modifier = Modifier -// .fillMaxWidth() -// .padding(horizontal = 16.dp), -// text = LocalI18nTexts.current[titleKey], -// ) -// Box( -// modifier = Modifier -// .fillMaxWidth() -// .padding(horizontal = 16.dp, vertical = 8.dp) -// .border(1.dp, if (error != null) Red600 else Gray200, shape = RoundedCornerShape(8.dp)) -// .padding(16.dp), -// ) { -// BasicTextField( -// modifier = Modifier.fillMaxWidth(), -// value = value, -// onValueChange = onValueChange, -// textStyle = TextStyle(fontSize = 16.sp, color = Color.Black), -// singleLine = true, -// keyboardOptions = KeyboardOptions(keyboardType = keyboardType, capitalization = capitalization), -// ) -// if (value.isEmpty()) { -// Text( -// text = LocalI18nTexts.current[placeholderKey], -// style = TextStyle(fontSize = 16.sp, color = Gray500), -// ) -// } -// } -// Text( -// modifier = Modifier -// .fillMaxWidth() -// .padding(horizontal = 16.dp), -// text = LocalI18nTexts.current[error.orEmpty()], -// style = TextStyle(fontSize = 16.sp, color = Red600), -// ) -// } } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/CreditCardForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/CreditCardForm.kt similarity index 80% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/CreditCardForm.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/CreditCardForm.kt index e939935..e7a14b3 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/CreditCardForm.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/CreditCardForm.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.Image import androidx.compose.foundation.border @@ -29,34 +29,41 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.toUpperCase -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.types.Currency -import com.komoju.android.sdk.ui.composables.InlinedPaymentPrimaryButton -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.screens.payment.CreditCardDisplayData -import com.komoju.android.sdk.ui.theme.Gray200 -import com.komoju.android.sdk.ui.theme.Gray500 -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme -import com.komoju.android.sdk.ui.theme.Red600 -import com.komoju.android.sdk.utils.AmountUtils -import com.komoju.android.sdk.utils.CardScheme -import com.komoju.android.sdk.utils.CreditCardUtils.formatAmex -import com.komoju.android.sdk.utils.CreditCardUtils.formatDinnersClub -import com.komoju.android.sdk.utils.CreditCardUtils.formatOtherCardNumbers -import com.komoju.android.sdk.utils.CreditCardUtils.identifyCardScheme -import com.komoju.android.sdk.utils.CreditCardUtils.makeExpirationFilter -import com.komoju.android.sdk.utils.testID import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_card_number +import com.komoju.mobile.sdk.shared.generated.resources.komoju_cardholder_name +import com.komoju.mobile.sdk.shared.generated.resources.komoju_cvv +import com.komoju.mobile.sdk.shared.generated.resources.komoju_full_name_on_card +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_cvv +import com.komoju.mobile.sdk.shared.generated.resources.komoju_mm_yy +import com.komoju.mobile.sdk.shared.generated.resources.komoju_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_save_this_card_for_future_payments +import com.komoju.mobile.sdk.ui.composables.InlinedPaymentPrimaryButton +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.payment.CreditCardDisplayData +import com.komoju.mobile.sdk.ui.theme.Gray200 +import com.komoju.mobile.sdk.ui.theme.Gray500 +import com.komoju.mobile.sdk.ui.theme.KomojuMobileSdkTheme +import com.komoju.mobile.sdk.ui.theme.Red600 +import com.komoju.mobile.sdk.utils.AmountUtils +import com.komoju.mobile.sdk.utils.CardScheme +import com.komoju.mobile.sdk.utils.CreditCardUtils.formatAmex +import com.komoju.mobile.sdk.utils.CreditCardUtils.formatDinnersClub +import com.komoju.mobile.sdk.utils.CreditCardUtils.formatOtherCardNumbers +import com.komoju.mobile.sdk.utils.CreditCardUtils.identifyCardScheme +import com.komoju.mobile.sdk.utils.CreditCardUtils.makeExpirationFilter +import com.komoju.mobile.sdk.utils.testID +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview @Composable internal fun CreditCardForm( @@ -70,15 +77,15 @@ internal fun CreditCardForm( val localDensity = LocalDensity.current val displayPayableAmount by remember(creditCard.amount) { derivedStateOf { - AmountUtils.formatToDecimal(Currency.parse(creditCard.currency), creditCard.amount) + AmountUtils.formatToDecimal(creditCard.currency, creditCard.amount) } } val dividerColor = if (creditCardDisplayData.creditCardErrorStringResource == null) Gray200 else Red600 Column { TextField( creditCardDisplayData.fullNameOnCard, - title = stringResource(R.string.komoju_cardholder_name), - placeholder = stringResource(R.string.komoju_full_name_on_card), + title = stringResource(Res.string.komoju_cardholder_name), + placeholder = stringResource(Res.string.komoju_full_name_on_card), capitalization = KeyboardCapitalization.Characters, error = creditCardDisplayData.fullNameOnCardErrorStringResource?.let { stringResource(it) }, onValueChange = { @@ -90,7 +97,7 @@ internal fun CreditCardForm( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), - text = stringResource(R.string.komoju_card_number), + text = stringResource(Res.string.komoju_card_number), ) Box( @@ -165,7 +172,7 @@ internal fun CreditCardForm( ) if (creditCardDisplayData.creditCardExpiryDate.isEmpty()) { Text( - text = stringResource(R.string.komoju_mm_yy), + text = stringResource(Res.string.komoju_mm_yy), style = TextStyle(fontSize = 16.sp, color = Gray500), ) } @@ -185,14 +192,14 @@ internal fun CreditCardForm( ) if (creditCardDisplayData.creditCardCvv.isEmpty()) { Text( - text = stringResource(R.string.komoju_cvv), + text = stringResource(Res.string.komoju_cvv), style = TextStyle(fontSize = 16.sp, color = Gray500), ) } } Image( - painter = painterResource(R.drawable.komoju_ic_cvv), + painter = painterResource(Res.drawable.komoju_ic_cvv), contentDescription = null, modifier = Modifier.padding(start = 16.dp), ) @@ -216,7 +223,7 @@ internal fun CreditCardForm( .testID("credit_card_pay") .padding(horizontal = 16.dp) .fillMaxWidth(), - text = stringResource(R.string.komoju_pay, displayPayableAmount), + text = stringResource(Res.string.komoju_pay, displayPayableAmount), onClick = onPayButtonClicked, state = creditCardDisplayData.inlinedPaymentPrimaryButtonState, ) @@ -226,7 +233,7 @@ internal fun CreditCardForm( .testID("credit_card_pay") .padding(horizontal = 16.dp) .fillMaxWidth(), - text = stringResource(R.string.komoju_pay, displayPayableAmount), + text = stringResource(Res.string.komoju_pay, displayPayableAmount), onClick = onPayButtonClicked, ) } @@ -240,16 +247,14 @@ internal fun CreditCardForm( }, colors = CheckboxDefaults.colors(checkedColor = Color.Black, uncheckedColor = Color.Black), ) - Text(stringResource(R.string.komoju_save_this_card_for_future_payments)) + Text(stringResource(Res.string.komoju_save_this_card_for_future_payments)) } } } } @Composable -@Preview( - showBackground = true, -) +@Preview private fun CreditCardFormPreview() { val creditCard = PaymentMethod.CreditCard( hashedGateway = "", diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/CreditCardSchemeIcons.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/CreditCardSchemeIcons.kt new file mode 100644 index 0000000..eac74f0 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/CreditCardSchemeIcons.kt @@ -0,0 +1,55 @@ +package com.komoju.mobile.sdk.ui.screens.payment.composables + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_amex +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_diners +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_jcb +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_master +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_visa +import com.komoju.mobile.sdk.utils.CardScheme +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +internal fun CreditCardSchemeIcons(cardScheme: CardScheme) { + Row { + AnimatedVisibility(visible = cardScheme == CardScheme.UNKNOWN) { + Row { + Image(painter = painterResource(Res.drawable.komoju_ic_visa), contentDescription = "visa_icon") + Spacer(Modifier.width(8.dp)) + Image(painter = painterResource(Res.drawable.komoju_ic_master), contentDescription = "mastercard_icon") + Spacer(Modifier.width(8.dp)) + Image(painter = painterResource(Res.drawable.komoju_ic_amex), contentDescription = "amex_icon") + } + } + AnimatedVisibility(visible = cardScheme == CardScheme.VISA) { + Image(painter = painterResource(Res.drawable.komoju_ic_visa), contentDescription = "visa_icon") + } + AnimatedVisibility(visible = cardScheme == CardScheme.MASTERCARD) { + Image(painter = painterResource(Res.drawable.komoju_ic_master), contentDescription = "mastercard_icon") + } + AnimatedVisibility(visible = cardScheme == CardScheme.AMEX) { + Image(painter = painterResource(Res.drawable.komoju_ic_amex), contentDescription = "amex_icon") + } + AnimatedVisibility(visible = cardScheme == CardScheme.DINERS_CLUB) { + Image(painter = painterResource(Res.drawable.komoju_ic_diners), contentDescription = "diners_icon") + } + AnimatedVisibility(visible = cardScheme == CardScheme.JCB) { + Image(painter = painterResource(Res.drawable.komoju_ic_jcb), contentDescription = "jcb_icon") + } + } +} + +@Composable +@Preview +private fun CreditCardSchemeIconsPreview() { + CreditCardSchemeIcons(CardScheme.UNKNOWN) +} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/KonbiniBrandsRow.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/KonbiniBrandsRow.kt similarity index 56% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/KonbiniBrandsRow.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/KonbiniBrandsRow.kt index f151b98..2f5f2a2 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/KonbiniBrandsRow.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/KonbiniBrandsRow.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.Image import androidx.compose.foundation.border @@ -17,16 +17,28 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.ui.theme.Gray200 -import com.komoju.android.sdk.ui.theme.KomojuDarkGreen -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme import com.komoju.mobile.sdk.entities.PaymentMethod.Konbini.KonbiniBrand +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju__7_eleven +import com.komoju.mobile.sdk.shared.generated.resources.komoju_daily_yamazaki +import com.komoju.mobile.sdk.shared.generated.resources.komoju_family_mart +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_daily_yamazaki +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_family_mart +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_lawson +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_ministop +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_seico_mart +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_seven_eleven +import com.komoju.mobile.sdk.shared.generated.resources.komoju_lawson +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ministop +import com.komoju.mobile.sdk.shared.generated.resources.komoju_seicomart +import com.komoju.mobile.sdk.ui.theme.Gray200 +import com.komoju.mobile.sdk.ui.theme.KomojuDarkGreen +import com.komoju.mobile.sdk.ui.theme.KomojuMobileSdkTheme +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview @Composable internal fun KonbiniBrandsRow(konbiniBrands: List, selectedKonbiniBrand: KonbiniBrand?, onSelected: (KonbiniBrand) -> Unit) { @@ -67,22 +79,22 @@ private fun KonbiniBrand(konbiniBrand: KonbiniBrand, isSelected: Boolean, onSele private val KonbiniBrand.displayText @Composable get() = when (this) { - is KonbiniBrand.DailyYamazaki -> stringResource(R.string.komoju_daily_yamazaki) - is KonbiniBrand.FamilyMart -> stringResource(R.string.komoju_family_mart) - is KonbiniBrand.Lawson -> stringResource(R.string.komoju_lawson) - is KonbiniBrand.MiniStop -> stringResource(R.string.komoju_ministop) - is KonbiniBrand.SeicoMart -> stringResource(R.string.komoju_seicomart) - is KonbiniBrand.SevenEleven -> stringResource(R.string.komoju__7_eleven) + is KonbiniBrand.DailyYamazaki -> stringResource(Res.string.komoju_daily_yamazaki) + is KonbiniBrand.FamilyMart -> stringResource(Res.string.komoju_family_mart) + is KonbiniBrand.Lawson -> stringResource(Res.string.komoju_lawson) + is KonbiniBrand.MiniStop -> stringResource(Res.string.komoju_ministop) + is KonbiniBrand.SeicoMart -> stringResource(Res.string.komoju_seicomart) + is KonbiniBrand.SevenEleven -> stringResource(Res.string.komoju__7_eleven) } private val KonbiniBrand.displayIcon get() = when (this) { - is KonbiniBrand.DailyYamazaki -> R.drawable.komoju_ic_daily_yamazaki - is KonbiniBrand.FamilyMart -> R.drawable.komoju_ic_family_mart - is KonbiniBrand.Lawson -> R.drawable.komoju_ic_lawson - is KonbiniBrand.MiniStop -> R.drawable.komoju_ic_ministop - is KonbiniBrand.SeicoMart -> R.drawable.komoju_ic_seico_mart - is KonbiniBrand.SevenEleven -> R.drawable.komoju_ic_seven_eleven + is KonbiniBrand.DailyYamazaki -> Res.drawable.komoju_ic_daily_yamazaki + is KonbiniBrand.FamilyMart -> Res.drawable.komoju_ic_family_mart + is KonbiniBrand.Lawson -> Res.drawable.komoju_ic_lawson + is KonbiniBrand.MiniStop -> Res.drawable.komoju_ic_ministop + is KonbiniBrand.SeicoMart -> Res.drawable.komoju_ic_seico_mart + is KonbiniBrand.SevenEleven -> Res.drawable.komoju_ic_seven_eleven } @Composable diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/KonbiniForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/KonbiniForm.kt similarity index 73% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/KonbiniForm.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/KonbiniForm.kt index a380977..4809f60 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/KonbiniForm.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/KonbiniForm.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -13,22 +13,26 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.types.Currency -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.screens.payment.CommonDisplayData -import com.komoju.android.sdk.ui.screens.payment.KonbiniDisplayData -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme -import com.komoju.android.sdk.ui.theme.Red600 -import com.komoju.android.sdk.utils.AmountUtils import com.komoju.mobile.sdk.entities.PaymentMethod import com.komoju.mobile.sdk.entities.PaymentMethod.Konbini.KonbiniBrand +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_email +import com.komoju.mobile.sdk.shared.generated.resources.komoju_enter_your_email_address +import com.komoju.mobile.sdk.shared.generated.resources.komoju_full_name_on_receipt +import com.komoju.mobile.sdk.shared.generated.resources.komoju_name_shown_on_receipt +import com.komoju.mobile.sdk.shared.generated.resources.komoju_pay +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.payment.CommonDisplayData +import com.komoju.mobile.sdk.ui.screens.payment.KonbiniDisplayData +import com.komoju.mobile.sdk.ui.theme.KomojuMobileSdkTheme +import com.komoju.mobile.sdk.ui.theme.Red600 +import com.komoju.mobile.sdk.utils.AmountUtils +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview @Composable internal fun KonbiniForm( @@ -41,14 +45,14 @@ internal fun KonbiniForm( ) { val displayPayableAmount by remember { derivedStateOf { - AmountUtils.formatToDecimal(Currency.parse(konbini.currency), konbini.amount) + AmountUtils.formatToDecimal(konbini.currency, konbini.amount) } } Column { TextField( konbiniDisplayData.receiptName, - title = stringResource(R.string.komoju_name_shown_on_receipt), - placeholder = stringResource(R.string.komoju_full_name_on_receipt), + title = stringResource(Res.string.komoju_name_shown_on_receipt), + placeholder = stringResource(Res.string.komoju_full_name_on_receipt), error = konbiniDisplayData.receiptNameErrorStringResource?.let { stringResource(it) }, onValueChange = { onKonbiniDisplayDataChange(konbiniDisplayData.copy(receiptName = it)) @@ -56,8 +60,8 @@ internal fun KonbiniForm( ) TextField( commonDisplayData.email, - title = stringResource(R.string.komoju_email), - placeholder = stringResource(R.string.komoju_enter_your_email_address), + title = stringResource(Res.string.komoju_email), + placeholder = stringResource(Res.string.komoju_enter_your_email_address), error = konbiniDisplayData.receiptEmailErrorStringResource?.let { stringResource(it) }, onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(email = it)) @@ -83,14 +87,14 @@ internal fun KonbiniForm( modifier = Modifier .padding(16.dp) .fillMaxWidth(), - text = stringResource(R.string.komoju_pay, displayPayableAmount), + text = stringResource(Res.string.komoju_pay, displayPayableAmount), onClick = onPayButtonClicked, ) } } @Composable -@Preview(showBackground = true) +@Preview private fun KonbiniFormPreview() { val konbini = PaymentMethod.Konbini( hashedGateway = "", diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/NetCashForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/NetCashForm.kt similarity index 61% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/NetCashForm.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/NetCashForm.kt index f005f57..442adc3 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/NetCashForm.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/NetCashForm.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -10,14 +10,16 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.types.Currency -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.screens.payment.NetCashDisplayData -import com.komoju.android.sdk.utils.AmountUtils import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_net_cash_id +import com.komoju.mobile.sdk.shared.generated.resources.komoju_net_cash_information +import com.komoju.mobile.sdk.shared.generated.resources.komoju_pay +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.payment.NetCashDisplayData +import com.komoju.mobile.sdk.utils.AmountUtils +import org.jetbrains.compose.resources.stringResource @Composable internal fun NetCashForm( @@ -28,14 +30,14 @@ internal fun NetCashForm( ) { val displayPayableAmount by remember(netCash.amount) { derivedStateOf { - AmountUtils.formatToDecimal(Currency.parse(netCash.currency), netCash.amount) + AmountUtils.formatToDecimal(netCash.currency, netCash.amount) } } Column { TextField( value = netCashDisplayData.netCashId, - title = stringResource(R.string.komoju_net_cash_information), - placeholder = stringResource(R.string.komoju_net_cash_id), + title = stringResource(Res.string.komoju_net_cash_information), + placeholder = stringResource(Res.string.komoju_net_cash_id), onValueChange = { onNetCashDisplayDataChange(netCashDisplayData.copy(netCashId = it)) }, @@ -46,7 +48,7 @@ internal fun NetCashForm( modifier = Modifier .padding(16.dp) .fillMaxWidth(), - text = stringResource(R.string.komoju_pay, displayPayableAmount), + text = stringResource(Res.string.komoju_pay, displayPayableAmount), onClick = onPayButtonClicked, ) } diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/OffSitePayForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/OffSitePayForm.kt new file mode 100644 index 0000000..d3067cd --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/OffSitePayForm.kt @@ -0,0 +1,132 @@ +package com.komoju.mobile.sdk.ui.screens.payment.composables + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_continue_to_alipay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_continue_to_aupay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_continue_to_linepay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_continue_to_merpay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_continue_to_paypay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_continue_to_rakuten +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_app_opens_info +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_via_alipay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_via_au_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_via_line_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_via_mer_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_via_paypay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_via_rakuten_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_webview_open_info +import com.komoju.mobile.sdk.shared.generated.resources.komoju_you_will_be_redirected_to_alipay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_you_will_be_redirected_to_au_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_you_will_be_redirected_to_line_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_you_will_be_redirected_to_mer_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_you_will_be_redirected_to_paypay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_you_will_be_redirected_to_rakuten +import com.komoju.mobile.sdk.types.OffSitePaymentType +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.theme.KomojuMobileSdkTheme +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +internal fun OffSitePayForm(paymentMethod: PaymentMethod.OffSitePayment, onPayButtonClicked: () -> Unit) { + val titleKey = remember(paymentMethod) { + when (paymentMethod.type) { + OffSitePaymentType.ALI_PAY -> Res.string.komoju_payment_via_alipay + OffSitePaymentType.AU_PAY -> Res.string.komoju_payment_via_au_pay + OffSitePaymentType.MER_PAY -> Res.string.komoju_payment_via_mer_pay + OffSitePaymentType.PAY_PAY -> Res.string.komoju_payment_via_paypay + OffSitePaymentType.RAKUTEN_PAY -> Res.string.komoju_payment_via_rakuten_pay + OffSitePaymentType.LINE_PAY -> Res.string.komoju_payment_via_line_pay + else -> null + } + } + + val messageKey = remember(paymentMethod) { + when (paymentMethod.type) { + OffSitePaymentType.ALI_PAY -> Res.string.komoju_you_will_be_redirected_to_alipay + OffSitePaymentType.AU_PAY -> Res.string.komoju_you_will_be_redirected_to_au_pay + OffSitePaymentType.MER_PAY -> Res.string.komoju_you_will_be_redirected_to_mer_pay + OffSitePaymentType.PAY_PAY -> Res.string.komoju_you_will_be_redirected_to_paypay + OffSitePaymentType.RAKUTEN_PAY -> Res.string.komoju_you_will_be_redirected_to_rakuten + OffSitePaymentType.LINE_PAY -> Res.string.komoju_you_will_be_redirected_to_line_pay + else -> null + } + } + + val paymentButtonKey = remember(paymentMethod) { + when (paymentMethod.type) { + OffSitePaymentType.ALI_PAY -> Res.string.komoju_continue_to_alipay + OffSitePaymentType.AU_PAY -> Res.string.komoju_continue_to_aupay + OffSitePaymentType.MER_PAY -> Res.string.komoju_continue_to_merpay + OffSitePaymentType.PAY_PAY -> Res.string.komoju_continue_to_paypay + OffSitePaymentType.RAKUTEN_PAY -> Res.string.komoju_continue_to_rakuten + OffSitePaymentType.LINE_PAY -> Res.string.komoju_continue_to_linepay + else -> null + } + } + + if (titleKey != null && messageKey != null && paymentButtonKey != null) { + Column(modifier = Modifier.padding(all = 16.dp)) { + Text(text = stringResource(titleKey), style = TextStyle(fontWeight = FontWeight.Bold, fontSize = 24.sp)) + Spacer(modifier = Modifier.height(12.dp)) + Text(text = stringResource(messageKey)) + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = Modifier + .background(Color(0xFFF3F7F9), shape = RoundedCornerShape(8.dp)) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(Res.drawable.komoju_ic_app_opens_info), + contentDescription = "app_opens_info", + modifier = Modifier.size(32.dp), + ) + Spacer(modifier = Modifier.width(16.dp)) + Text(text = stringResource(Res.string.komoju_webview_open_info)) + } + Spacer(modifier = Modifier.height(32.dp)) + PrimaryButton(stringResource(paymentButtonKey), modifier = Modifier.fillMaxWidth(), onPayButtonClicked) + } + } +} + +@Composable +@Preview +private fun AppPayFormPreview() { + KomojuMobileSdkTheme { + OffSitePayForm( + PaymentMethod.OffSitePayment( + hashedGateway = "paypay", + exchangeRate = 1.0, + currency = "JPY", + amount = "100", + additionalFields = listOf(), + type = OffSitePaymentType.PAY_PAY, + ), + ) { } + } +} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaidyForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaidyForm.kt similarity index 58% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaidyForm.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaidyForm.kt index 2a311a9..3e2fa78 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaidyForm.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaidyForm.kt @@ -1,23 +1,30 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.screens.payment.PaidyDisplayData -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_enter_your_name +import com.komoju.mobile.sdk.shared.generated.resources.komoju_enter_your_phone_number +import com.komoju.mobile.sdk.shared.generated.resources.komoju_full_name +import com.komoju.mobile.sdk.shared.generated.resources.komoju_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_phone_number +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.payment.PaidyDisplayData +import com.komoju.mobile.sdk.ui.theme.KomojuMobileSdkTheme +import com.komoju.mobile.sdk.utils.AmountUtils +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview @Composable internal fun PaidyForm( @@ -26,11 +33,16 @@ internal fun PaidyForm( onPaidyDisplayDataChange: (PaidyDisplayData) -> Unit, onPayButtonClicked: () -> Unit, ) { + val displayPayableAmount by remember(paidy.amount) { + derivedStateOf { + AmountUtils.formatToDecimal(paidy.currency, paidy.amount) + } + } Column { TextField( value = paidyDisplayData.fullName, - title = stringResource(R.string.komoju_full_name), - placeholder = stringResource(R.string.komoju_enter_your_name), + title = stringResource(Res.string.komoju_full_name), + placeholder = stringResource(Res.string.komoju_enter_your_name), onValueChange = { onPaidyDisplayDataChange(paidyDisplayData.copy(fullName = it)) }, @@ -38,8 +50,8 @@ internal fun PaidyForm( ) TextField( value = paidyDisplayData.phoneNumber, - title = stringResource(R.string.komoju_phone_number), - placeholder = stringResource(R.string.komoju_enter_your_phone_number), + title = stringResource(Res.string.komoju_phone_number), + placeholder = stringResource(Res.string.komoju_enter_your_phone_number), keyboardType = KeyboardType.Phone, onValueChange = { onPaidyDisplayDataChange(paidyDisplayData.copy(phoneNumber = it)) @@ -47,17 +59,17 @@ internal fun PaidyForm( error = paidyDisplayData.phoneNumberErrorStringResource?.let { stringResource(it) }, ) PrimaryButton( - stringResource(R.string.komoju_continue_to_paidy), modifier = Modifier - .fillMaxWidth() - .padding(all = 16.dp), - onPayButtonClicked, + .padding(16.dp) + .fillMaxWidth(), + text = stringResource(Res.string.komoju_pay, displayPayableAmount), + onClick = onPayButtonClicked, ) } } @Composable -@Preview(showBackground = true) +@Preview private fun PaidyFormPreview() { val paidy = PaymentMethod.Paidy( hashedGateway = "hashedGateway", diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PayEasyForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PayEasyForm.kt similarity index 68% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PayEasyForm.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PayEasyForm.kt index e3ec888..f406b48 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PayEasyForm.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PayEasyForm.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -9,18 +9,25 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.types.Currency -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.screens.payment.CommonDisplayData -import com.komoju.android.sdk.utils.AmountUtils import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_email +import com.komoju.mobile.sdk.shared.generated.resources.komoju_enter_your_email_address +import com.komoju.mobile.sdk.shared.generated.resources.komoju_enter_your_phone_number +import com.komoju.mobile.sdk.shared.generated.resources.komoju_first_name +import com.komoju.mobile.sdk.shared.generated.resources.komoju_first_name_phonetic +import com.komoju.mobile.sdk.shared.generated.resources.komoju_last_name +import com.komoju.mobile.sdk.shared.generated.resources.komoju_last_name_phonetic +import com.komoju.mobile.sdk.shared.generated.resources.komoju_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_phone_number +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.payment.CommonDisplayData +import com.komoju.mobile.sdk.utils.AmountUtils +import org.jetbrains.compose.resources.stringResource @Composable internal fun PayEasyForm( @@ -31,7 +38,7 @@ internal fun PayEasyForm( ) { val displayPayableAmount by remember(payEasy.amount) { derivedStateOf { - AmountUtils.formatToDecimal(Currency.parse(payEasy.currency), payEasy.amount) + AmountUtils.formatToDecimal(payEasy.currency, payEasy.amount) } } Column { @@ -40,8 +47,8 @@ internal fun PayEasyForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.lastName, - title = stringResource(R.string.komoju_last_name), - placeholder = stringResource(R.string.komoju_last_name), + title = stringResource(Res.string.komoju_last_name), + placeholder = stringResource(Res.string.komoju_last_name), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(lastName = it)) }, @@ -55,8 +62,8 @@ internal fun PayEasyForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.firstName, - title = stringResource(R.string.komoju_first_name), - placeholder = stringResource(R.string.komoju_first_name), + title = stringResource(Res.string.komoju_first_name), + placeholder = stringResource(Res.string.komoju_first_name), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(firstName = it)) }, @@ -70,8 +77,8 @@ internal fun PayEasyForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.lastNamePhonetic, - title = stringResource(R.string.komoju_last_name_phonetic), - placeholder = stringResource(R.string.komoju_last_name_phonetic), + title = stringResource(Res.string.komoju_last_name_phonetic), + placeholder = stringResource(Res.string.komoju_last_name_phonetic), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(lastNamePhonetic = it)) }, @@ -86,8 +93,8 @@ internal fun PayEasyForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.firstNamePhonetic, - title = stringResource(R.string.komoju_first_name_phonetic), - placeholder = stringResource(R.string.komoju_first_name_phonetic), + title = stringResource(Res.string.komoju_first_name_phonetic), + placeholder = stringResource(Res.string.komoju_first_name_phonetic), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(firstNamePhonetic = it)) }, @@ -102,8 +109,8 @@ internal fun PayEasyForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.email, - title = stringResource(R.string.komoju_email), - placeholder = stringResource(R.string.komoju_enter_your_email_address), + title = stringResource(Res.string.komoju_email), + placeholder = stringResource(Res.string.komoju_enter_your_email_address), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(email = it)) }, @@ -117,8 +124,8 @@ internal fun PayEasyForm( .fillMaxWidth() .padding(horizontal = 16.dp), value = commonDisplayData.phoneNumber, - title = stringResource(R.string.komoju_phone_number), - placeholder = stringResource(R.string.komoju_enter_your_phone_number), + title = stringResource(Res.string.komoju_phone_number), + placeholder = stringResource(Res.string.komoju_enter_your_phone_number), onValueChange = { onCommonDisplayDataChange(commonDisplayData.copy(phoneNumber = it)) }, @@ -132,7 +139,7 @@ internal fun PayEasyForm( modifier = Modifier .padding(16.dp) .fillMaxWidth(), - text = stringResource(R.string.komoju_pay, displayPayableAmount), + text = stringResource(Res.string.komoju_pay, displayPayableAmount), onClick = onPayButtonClicked, ) } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaymentMethodForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaymentMethodForm.kt similarity index 88% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaymentMethodForm.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaymentMethodForm.kt index 5dfea37..18e81c4 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaymentMethodForm.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaymentMethodForm.kt @@ -1,14 +1,14 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.runtime.Composable -import com.komoju.android.sdk.ui.screens.payment.BitCashDisplayData -import com.komoju.android.sdk.ui.screens.payment.CommonDisplayData -import com.komoju.android.sdk.ui.screens.payment.CreditCardDisplayData -import com.komoju.android.sdk.ui.screens.payment.KonbiniDisplayData -import com.komoju.android.sdk.ui.screens.payment.NetCashDisplayData -import com.komoju.android.sdk.ui.screens.payment.PaidyDisplayData -import com.komoju.android.sdk.ui.screens.payment.WebMoneyDisplayData import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.ui.screens.payment.BitCashDisplayData +import com.komoju.mobile.sdk.ui.screens.payment.CommonDisplayData +import com.komoju.mobile.sdk.ui.screens.payment.CreditCardDisplayData +import com.komoju.mobile.sdk.ui.screens.payment.KonbiniDisplayData +import com.komoju.mobile.sdk.ui.screens.payment.NetCashDisplayData +import com.komoju.mobile.sdk.ui.screens.payment.PaidyDisplayData +import com.komoju.mobile.sdk.ui.screens.payment.WebMoneyDisplayData @Composable internal fun PaymentMethodForm( diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaymentMethodsRow.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaymentMethodsRow.kt new file mode 100644 index 0000000..14fb1fb --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaymentMethodsRow.kt @@ -0,0 +1,179 @@ +package com.komoju.mobile.sdk.ui.screens.payment.composables + +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_alipay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_aupay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_bank_transfer +import com.komoju.mobile.sdk.shared.generated.resources.komoju_bitcash +import com.komoju.mobile.sdk.shared.generated.resources.komoju_credit_card +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_alipay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_au_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_bank_transfer +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_bitcash +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_credit_card +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_konbini +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_linepay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_merpay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_net_cash +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_paidy +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_pay_easy +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_paypay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_rakuten_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_web_money +import com.komoju.mobile.sdk.shared.generated.resources.komoju_konbini +import com.komoju.mobile.sdk.shared.generated.resources.komoju_line_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_merpay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_netcash +import com.komoju.mobile.sdk.shared.generated.resources.komoju_other +import com.komoju.mobile.sdk.shared.generated.resources.komoju_paidy +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payeasy +import com.komoju.mobile.sdk.shared.generated.resources.komoju_paypay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_rakuten_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_unknown +import com.komoju.mobile.sdk.shared.generated.resources.komoju_webmoney +import com.komoju.mobile.sdk.types.OffSitePaymentType +import com.komoju.mobile.sdk.ui.theme.Gray200 +import com.komoju.mobile.sdk.ui.theme.KomojuDarkGreen +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +internal fun PaymentMethodsRow( + paymentMethods: List, + selectedPaymentMethod: PaymentMethod?, + onSelected: (PaymentMethod) -> Unit, +) { + LazyRow(contentPadding = PaddingValues(horizontal = 8.dp)) { + items(paymentMethods) { paymentMethod -> + PaymentMethodComposable( + paymentMethod, + paymentMethod == selectedPaymentMethod, + onSelected = { + onSelected(paymentMethod) + }, + ) + } + } +} + +@Composable +private fun PaymentMethodComposable(paymentMethod: PaymentMethod, isSelected: Boolean, onSelected: () -> Unit) { + Column( + modifier = Modifier + .defaultMinSize(minWidth = 120.dp) + .padding(4.dp) + .clip(RoundedCornerShape(8.dp)) + .border(1.dp, if (isSelected) KomojuDarkGreen else Gray200, RoundedCornerShape(8.dp)) + .clickable(onClick = onSelected) + .padding(start = 12.dp, end = 12.dp, top = 12.dp, bottom = 8.dp), + ) { + Image( + painter = painterResource(paymentMethod.displayIcon), + contentDescription = "${paymentMethod.displayName} icon", + modifier = Modifier.height(32.dp), + ) + Spacer(modifier = Modifier.height(4.dp)) + Text(paymentMethod.displayName, color = Color.Black, fontWeight = FontWeight.SemiBold, fontSize = 14.sp) + } +} + +private val PaymentMethod.displayName + @Composable + get() = when (this) { + is PaymentMethod.BankTransfer -> stringResource(Res.string.komoju_bank_transfer) + is PaymentMethod.BitCash -> stringResource(Res.string.komoju_bitcash) + is PaymentMethod.CreditCard -> stringResource(Res.string.komoju_credit_card) + is PaymentMethod.Konbini -> stringResource(Res.string.komoju_konbini) + is PaymentMethod.NetCash -> stringResource(Res.string.komoju_netcash) + is PaymentMethod.OffSitePayment -> when (type) { + OffSitePaymentType.AU_PAY -> stringResource(Res.string.komoju_aupay) + OffSitePaymentType.ALI_PAY -> stringResource(Res.string.komoju_alipay) + OffSitePaymentType.MER_PAY -> stringResource(Res.string.komoju_merpay) + OffSitePaymentType.PAY_PAY -> stringResource(Res.string.komoju_paypay) + OffSitePaymentType.RAKUTEN_PAY -> stringResource(Res.string.komoju_rakuten_pay) + OffSitePaymentType.LINE_PAY -> stringResource(Res.string.komoju_line_pay) + OffSitePaymentType.UNKNOWN -> stringResource(Res.string.komoju_unknown) + } + is PaymentMethod.Other -> stringResource(Res.string.komoju_other) + is PaymentMethod.Paidy -> stringResource(Res.string.komoju_paidy) + is PaymentMethod.PayEasy -> stringResource(Res.string.komoju_payeasy) + is PaymentMethod.WebMoney -> stringResource(Res.string.komoju_webmoney) + } + +private val PaymentMethod.displayIcon + get() = when (this) { + is PaymentMethod.OffSitePayment -> when (type) { + OffSitePaymentType.ALI_PAY -> Res.drawable.komoju_ic_alipay + OffSitePaymentType.AU_PAY -> Res.drawable.komoju_ic_au_pay + OffSitePaymentType.MER_PAY -> Res.drawable.komoju_ic_merpay + OffSitePaymentType.PAY_PAY -> Res.drawable.komoju_ic_paypay + OffSitePaymentType.RAKUTEN_PAY -> Res.drawable.komoju_ic_rakuten_pay + OffSitePaymentType.LINE_PAY -> Res.drawable.komoju_ic_linepay + OffSitePaymentType.UNKNOWN -> Res.drawable.komoju_ic_credit_card + } + + is PaymentMethod.BankTransfer -> Res.drawable.komoju_ic_bank_transfer + is PaymentMethod.BitCash -> Res.drawable.komoju_ic_bitcash + is PaymentMethod.CreditCard -> Res.drawable.komoju_ic_credit_card + is PaymentMethod.Konbini -> Res.drawable.komoju_ic_konbini + is PaymentMethod.NetCash -> Res.drawable.komoju_ic_net_cash + is PaymentMethod.Paidy -> Res.drawable.komoju_ic_paidy + is PaymentMethod.PayEasy -> Res.drawable.komoju_ic_pay_easy + is PaymentMethod.WebMoney -> Res.drawable.komoju_ic_web_money + is PaymentMethod.Other -> Res.drawable.komoju_ic_credit_card + } + +@Composable +@Preview +private fun PaymentMethodComposablePreview() { + val paymentMethods = listOf( + PaymentMethod.CreditCard( + hashedGateway = "", + exchangeRate = 0.0, + currency = "", + amount = "0", + additionalFields = listOf(), + brands = listOf(), + ), + PaymentMethod.Konbini( + hashedGateway = "", + exchangeRate = 0.0, + currency = "", + amount = "0", + additionalFields = listOf(), + customerFee = 0, + brands = listOf(), + ), + PaymentMethod.OffSitePayment( + hashedGateway = "", + exchangeRate = 0.0, + currency = "", + amount = "0", + additionalFields = listOf(), + type = OffSitePaymentType.PAY_PAY, + ), + ) + PaymentMethodsRow(paymentMethods, paymentMethods.first()) {} +} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaymentSheetHandle.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaymentSheetHandle.kt similarity index 80% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaymentSheetHandle.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaymentSheetHandle.kt index 73a1334..71e88e6 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/PaymentSheetHandle.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/PaymentSheetHandle.kt @@ -1,17 +1,14 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.Image import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.Text -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -36,8 +33,6 @@ internal fun PaymentSheetHandle(title: String, onCloseClicked: () -> Unit) { contentDescription = "Close Payment Sheet", modifier = Modifier.align(Alignment.CenterEnd) .clickable( - indication = ripple(bounded = true, radius = 24.dp), - interactionSource = remember { MutableInteractionSource() }, onClick = onCloseClicked, ) .padding(16.dp), diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/TextField.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/TextField.kt similarity index 89% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/TextField.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/TextField.kt index 68e01cd..fc9ee79 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/TextField.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/TextField.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box @@ -17,9 +17,9 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.komoju.android.sdk.ui.theme.Gray200 -import com.komoju.android.sdk.ui.theme.Gray500 -import com.komoju.android.sdk.ui.theme.Red600 +import com.komoju.mobile.sdk.ui.theme.Gray200 +import com.komoju.mobile.sdk.ui.theme.Gray500 +import com.komoju.mobile.sdk.ui.theme.Red600 @Composable internal fun TextField( @@ -28,8 +28,8 @@ internal fun TextField( placeholder: String, onValueChange: (String) -> Unit, error: String? = null, - keyboardType: KeyboardType = KeyboardType.Unspecified, - capitalization: KeyboardCapitalization = KeyboardCapitalization.Unspecified, + keyboardType: KeyboardType = KeyboardType.Text, + capitalization: KeyboardCapitalization = KeyboardCapitalization.None, ) { Column { Text( diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/WebMoneyForm.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/WebMoneyForm.kt similarity index 61% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/WebMoneyForm.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/WebMoneyForm.kt index 2ad256b..fd3c44b 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/composables/WebMoneyForm.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/payment/composables/WebMoneyForm.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.payment.composables +package com.komoju.mobile.sdk.ui.screens.payment.composables import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -10,14 +10,16 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.komoju.android.sdk.R -import com.komoju.android.sdk.types.Currency -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.screens.payment.WebMoneyDisplayData -import com.komoju.android.sdk.utils.AmountUtils import com.komoju.mobile.sdk.entities.PaymentMethod +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_pay +import com.komoju.mobile.sdk.shared.generated.resources.komoju_prepaid_number +import com.komoju.mobile.sdk.shared.generated.resources.komoju_webmoney_information +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.payment.WebMoneyDisplayData +import com.komoju.mobile.sdk.utils.AmountUtils +import org.jetbrains.compose.resources.stringResource @Composable internal fun WebMoneyForm( @@ -28,14 +30,14 @@ internal fun WebMoneyForm( ) { val displayPayableAmount by remember(webMoney.amount) { derivedStateOf { - AmountUtils.formatToDecimal(Currency.parse(webMoney.currency), webMoney.amount) + AmountUtils.formatToDecimal(webMoney.currency, webMoney.amount) } } Column { TextField( value = webMoneyDisplayData.prepaidNumber, - title = stringResource(R.string.komoju_webmoney_information), - placeholder = stringResource(R.string.komoju_prepaid_number), + title = stringResource(Res.string.komoju_webmoney_information), + placeholder = stringResource(Res.string.komoju_prepaid_number), onValueChange = { onWebMoneyDisplayDataChange(webMoneyDisplayData.copy(prepaidNumber = it)) }, @@ -46,7 +48,7 @@ internal fun WebMoneyForm( modifier = Modifier .padding(16.dp) .fillMaxWidth(), - text = stringResource(R.string.komoju_pay, displayPayableAmount), + text = stringResource(Res.string.komoju_pay, displayPayableAmount), onClick = onPayButtonClicked, ) } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/success/PaymentSuccessScreen.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/success/PaymentSuccessScreen.kt similarity index 66% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/success/PaymentSuccessScreen.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/success/PaymentSuccessScreen.kt index 66aaf06..13ad771 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/success/PaymentSuccessScreen.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/success/PaymentSuccessScreen.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.screens.success +package com.komoju.mobile.sdk.ui.screens.success import androidx.compose.foundation.Image import androidx.compose.foundation.clickable @@ -16,10 +16,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -27,13 +24,17 @@ import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import com.komoju.android.sdk.KomojuSDK -import com.komoju.android.sdk.R -import com.komoju.android.sdk.navigation.paymentResultScreenModel -import com.komoju.android.sdk.ui.composables.PrimaryButton -import com.komoju.android.sdk.ui.screens.RouterEffect -import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme -import com.komoju.android.sdk.utils.PreviewScreen +import com.komoju.mobile.sdk.KomojuMobileSDKPaymentResult +import com.komoju.mobile.sdk.navigation.paymentResultScreenModel +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_back_to_store +import com.komoju.mobile.sdk.shared.generated.resources.komoju_ic_payment_status_completed +import com.komoju.mobile.sdk.shared.generated.resources.komoju_payment_success +import com.komoju.mobile.sdk.shared.generated.resources.komoju_thank_you_for_your_order +import com.komoju.mobile.sdk.ui.composables.PrimaryButton +import com.komoju.mobile.sdk.ui.screens.RouterEffect +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource internal class PaymentSuccessScreen : Screen { @Composable @@ -49,7 +50,7 @@ private fun Screen.PaymentSuccessScreenContent() { val navigator = LocalNavigator.currentOrThrow val resultScreenModel = navigator.paymentResultScreenModel() LaunchedEffect(Unit) { - resultScreenModel.setResult(KomojuSDK.PaymentResult(isSuccessFul = true)) + resultScreenModel.setResult(KomojuMobileSDKPaymentResult(isSuccessFul = true)) } Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) { @@ -63,27 +64,19 @@ private fun Screen.PaymentSuccessScreenContent() { }, ) } - Image(painterResource(R.drawable.komoju_ic_payment_status_completed), "status_icon") + Image(painterResource(Res.drawable.komoju_ic_payment_status_completed), "status_icon") Spacer(Modifier.height(16.dp)) - Text(stringResource(R.string.komoju_payment_success), fontSize = 24.sp, fontWeight = FontWeight.Bold) + Text(stringResource(Res.string.komoju_payment_success), fontSize = 24.sp, fontWeight = FontWeight.Bold) Spacer(Modifier.height(16.dp)) - Text(stringResource(R.string.komoju_thank_you_for_your_order)) + Text(stringResource(Res.string.komoju_thank_you_for_your_order)) Spacer(Modifier.weight(1f)) PrimaryButton( modifier = Modifier .fillMaxWidth() .padding(16.dp), - text = stringResource(R.string.komoju_back_to_store), + text = stringResource(Res.string.komoju_back_to_store), ) { screenModel.onBackToStoreButtonClicked() } } } - -@Composable -@Preview -private fun PaymentSuccessScreenContentPreview() { - KomojuMobileSdkTheme { - PreviewScreen.PaymentSuccessScreenContent() - } -} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/success/PaymentSuccessScreenModel.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/success/PaymentSuccessScreenModel.kt similarity index 66% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/success/PaymentSuccessScreenModel.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/success/PaymentSuccessScreenModel.kt index 61c2f88..54fa28c 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/success/PaymentSuccessScreenModel.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/success/PaymentSuccessScreenModel.kt @@ -1,6 +1,6 @@ -package com.komoju.android.sdk.ui.screens.success +package com.komoju.mobile.sdk.ui.screens.success -import com.komoju.android.sdk.navigation.RouterStateScreenModel +import com.komoju.mobile.sdk.navigation.RouterStateScreenModel internal class PaymentSuccessScreenModel : RouterStateScreenModel(Unit) { fun onCloseButtonClicked() { diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/verify/ProcessPaymentScreen.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/verify/ProcessPaymentScreen.kt similarity index 80% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/verify/ProcessPaymentScreen.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/verify/ProcessPaymentScreen.kt index 8df74ed..5bfa718 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/verify/ProcessPaymentScreen.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/verify/ProcessPaymentScreen.kt @@ -1,18 +1,17 @@ -package com.komoju.android.sdk.ui.screens.verify +package com.komoju.mobile.sdk.ui.screens.verify import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen -import com.komoju.android.sdk.ui.composables.ThemedCircularProgressIndicator -import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute -import com.komoju.android.sdk.ui.screens.RouterEffect +import com.komoju.mobile.sdk.ui.composables.ThemedCircularProgressIndicator +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentRoute +import com.komoju.mobile.sdk.ui.screens.RouterEffect internal class ProcessPaymentScreen(private val route: KomojuPaymentRoute.ProcessPayment) : Screen { @Composable diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt similarity index 85% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt index 46ff9ad..a502e78 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt @@ -1,19 +1,20 @@ -package com.komoju.android.sdk.ui.screens.verify +package com.komoju.mobile.sdk.ui.screens.verify 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 +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration +import com.komoju.mobile.sdk.KomojuMobileSDKPaymentResult import com.komoju.mobile.sdk.entities.PaymentStatus import com.komoju.mobile.sdk.entities.PaymentStatus.Companion.isSuccessful +import com.komoju.mobile.sdk.navigation.RouterStateScreenModel import com.komoju.mobile.sdk.remote.apis.KomojuRemoteApi +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentRoute +import com.komoju.mobile.sdk.ui.screens.Router +import com.komoju.mobile.sdk.ui.screens.failed.Reason import kotlinx.coroutines.launch -internal class VerifyPaymentScreenModel(private val config: KomojuSDK.Configuration) : RouterStateScreenModel(Unit) { +internal class VerifyPaymentScreenModel(private val config: KomojuMobileSDKConfiguration) : RouterStateScreenModel(Unit) { - private val komojuApi: KomojuRemoteApi = KomojuRemoteApi.create(config.publishableKey) + private val komojuApi: KomojuRemoteApi = KomojuRemoteApi.create(config) fun process(type: KomojuPaymentRoute.ProcessPayment.ProcessType) { screenModelScope.launch { @@ -29,7 +30,7 @@ internal class VerifyPaymentScreenModel(private val config: KomojuSDK.Configurat komojuApi.sessions.verifyPaymentBySessionID(config.sessionId.orEmpty()).onSuccess { paymentDetails -> mutableRouter.value = when { config.inlinedProcessing -> Router.SetPaymentResultAndPop( - KomojuSDK.PaymentResult( + KomojuMobileSDKPaymentResult( isSuccessFul = paymentDetails.status.isSuccessful(), ), ) diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/webview/WebViewRequestInterceptor.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/webview/WebViewRequestInterceptor.kt new file mode 100644 index 0000000..66c4614 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/webview/WebViewRequestInterceptor.kt @@ -0,0 +1,26 @@ +package com.komoju.mobile.sdk.ui.screens.webview + +import com.eygraber.uri.Uri +import com.multiplatform.webview.request.RequestInterceptor +import com.multiplatform.webview.request.WebRequest +import com.multiplatform.webview.request.WebRequestInterceptResult +import com.multiplatform.webview.web.WebViewNavigator + +internal class WebViewRequestInterceptor(private val appScheme: String, private val onDeeplinkCaptured: (String) -> Unit) : RequestInterceptor { + override fun onInterceptUrlRequest(request: WebRequest, navigator: WebViewNavigator): WebRequestInterceptResult = + request.checkAndOpen(appScheme, onDeeplinkCaptured) + + private fun WebRequest.checkAndOpen(appScheme: String, onDeeplinkCaptured: (String) -> Unit): WebRequestInterceptResult { + try { + val uri = Uri.parse(url) + if (uri.scheme == appScheme) { + onDeeplinkCaptured(url) + return WebRequestInterceptResult.Reject + } else { + error("Unsupported scheme for deeplink, load in webView Instead.") + } + } catch (_: Exception) { + return WebRequestInterceptResult.Allow + } + } +} diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebViewScreen.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/webview/WebViewScreen.kt similarity index 66% rename from android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebViewScreen.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/webview/WebViewScreen.kt index 514ff45..1636524 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/webview/WebViewScreen.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/webview/WebViewScreen.kt @@ -1,10 +1,7 @@ -package com.komoju.android.sdk.ui.screens.webview +package com.komoju.mobile.sdk.ui.screens.webview -import androidx.activity.compose.BackHandler import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -17,9 +14,7 @@ import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -27,22 +22,28 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow -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.annotation.InternalVoyagerApi import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen -import com.kevinnzou.web.LoadingState -import com.kevinnzou.web.WebView -import com.kevinnzou.web.rememberWebViewState -import com.komoju.android.sdk.R -import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute -import com.komoju.android.sdk.ui.screens.RouterEffect -import com.komoju.android.sdk.ui.theme.LocalConfigurableTheme +import cafe.adriel.voyager.navigator.internal.BackHandler +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_are_you_sure_you_want_to_cancel_the_payment +import com.komoju.mobile.sdk.shared.generated.resources.komoju_cancel_payment +import com.komoju.mobile.sdk.shared.generated.resources.komoju_no +import com.komoju.mobile.sdk.shared.generated.resources.komoju_yes +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentRoute +import com.komoju.mobile.sdk.ui.screens.RouterEffect +import com.komoju.mobile.sdk.ui.theme.LocalConfigurableTheme +import com.komoju.mobile.sdk.ui.theme.toColor +import com.multiplatform.webview.web.LoadingState +import com.multiplatform.webview.web.WebView +import com.multiplatform.webview.web.rememberWebViewNavigator +import com.multiplatform.webview.web.rememberWebViewState +import org.jetbrains.compose.resources.stringResource internal data class WebViewScreen(val route: KomojuPaymentRoute.WebView) : Screen { @Composable @@ -51,16 +52,15 @@ internal data class WebViewScreen(val route: KomojuPaymentRoute.WebView) : Scree } } +@OptIn(InternalVoyagerApi::class) @Composable private fun Screen.WebViewScreenContent(route: KomojuPaymentRoute.WebView) { val state = rememberWebViewState(route.url) var showBackPressDialog by remember { mutableStateOf(false) } val screenModel = rememberScreenModel { WebViewScreenModel() } RouterEffect(screenModel.router.collectAsStateWithLifecycle(), screenModel::onRouteConsumed) - if (route.canComeBack.not() && showBackPressDialog.not()) { - BackHandler { - showBackPressDialog = true - } + BackHandler(enabled = route.canComeBack.not() && showBackPressDialog.not()) { + showBackPressDialog = true } Column(modifier = Modifier) { Row( @@ -74,8 +74,6 @@ private fun Screen.WebViewScreenContent(route: KomojuPaymentRoute.WebView) { contentDescription = "Back", modifier = Modifier .clickable( - indication = ripple(bounded = true, radius = 24.dp), - interactionSource = remember { MutableInteractionSource() }, onClick = { screenModel.onBackPressed() }, @@ -99,8 +97,6 @@ private fun Screen.WebViewScreenContent(route: KomojuPaymentRoute.WebView) { contentDescription = "Close Payment Sheet", modifier = Modifier .clickable( - indication = ripple(bounded = true, radius = 24.dp), - interactionSource = remember { MutableInteractionSource() }, onClick = { showBackPressDialog = true }, @@ -114,25 +110,19 @@ private fun Screen.WebViewScreenContent(route: KomojuPaymentRoute.WebView) { LinearProgressIndicator( progress = { loadingState.progress }, modifier = Modifier.fillMaxWidth(), - color = Color(LocalConfigurableTheme.current.loaderColor), + color = LocalConfigurableTheme.current.loaderColor.toColor(), ) } WebView( modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.surface), + .fillMaxSize(), state = state, - onCreated = { nativeWebView -> - nativeWebView.clipToOutline = true - nativeWebView.setBackgroundColor(android.graphics.Color.TRANSPARENT) - nativeWebView.settings.apply { - domStorageEnabled = true - javaScriptEnabled = route.isJavaScriptEnabled - } - }, captureBackPresses = false, - chromeClient = remember { WebChromeClient() }, - client = remember { WebViewClient() }, + navigator = rememberWebViewNavigator( + requestInterceptor = WebViewRequestInterceptor(route.configuration.appScheme) { + screenModel.onNewDeeplink(route.configuration, it) + }, + ), ) if (showBackPressDialog) { AlertDialog( @@ -141,7 +131,7 @@ private fun Screen.WebViewScreenContent(route: KomojuPaymentRoute.WebView) { }, confirmButton = { Text( - text = stringResource(R.string.komoju_yes), + text = stringResource(Res.string.komoju_yes), modifier = Modifier .clickable { screenModel.onBackPressed() @@ -151,7 +141,7 @@ private fun Screen.WebViewScreenContent(route: KomojuPaymentRoute.WebView) { }, dismissButton = { Text( - text = stringResource(R.string.komoju_no), + text = stringResource(Res.string.komoju_no), modifier = Modifier .clickable { showBackPressDialog = false @@ -161,20 +151,14 @@ private fun Screen.WebViewScreenContent(route: KomojuPaymentRoute.WebView) { }, text = { Text( - text = stringResource(R.string.komoju_are_you_sure_you_want_to_cancel_the_payment), + text = stringResource(Res.string.komoju_are_you_sure_you_want_to_cancel_the_payment), modifier = Modifier.padding(8.dp), ) }, title = { - Text(text = stringResource(R.string.komoju_cancel_payment)) + Text(text = stringResource(Res.string.komoju_cancel_payment)) }, ) } } } - -@Composable -@Preview(showBackground = true, showSystemUi = true) -private fun WebViewScreenPreview() { - WebViewScreen(KomojuPaymentRoute.WebView("https://www.komoju.com/")) -} diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/webview/WebViewScreenModel.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/webview/WebViewScreenModel.kt new file mode 100644 index 0000000..625005e --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/screens/webview/WebViewScreenModel.kt @@ -0,0 +1,32 @@ +package com.komoju.mobile.sdk.ui.screens.webview + +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration +import com.komoju.mobile.sdk.navigation.RouterStateScreenModel +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentRoute +import com.komoju.mobile.sdk.ui.screens.Router +import com.komoju.mobile.sdk.utils.DeeplinkEntity + +internal class WebViewScreenModel : RouterStateScreenModel(Unit) { + + fun onBackPressed() { + mutableRouter.pop() + } + + fun onNewDeeplink(configuration: KomojuMobileSDKConfiguration, deeplink: String) { + val deeplinkEntity = DeeplinkEntity.from(deeplink) + mutableRouter.value = Router.ReplaceAll( + KomojuPaymentRoute.ProcessPayment( + configuration = configuration, + processType = when (deeplinkEntity) { + is DeeplinkEntity.Verify.BySecureToken -> KomojuPaymentRoute.ProcessPayment.ProcessType.VerifyTokenAndPay( + deeplinkEntity.secureTokenId, + amount = deeplinkEntity.amount, + currency = deeplinkEntity.currency, + ) + + DeeplinkEntity.Verify.BySessionId -> KomojuPaymentRoute.ProcessPayment.ProcessType.Session + }, + ), + ) + } +} diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/theme/ConfigurableTheme.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/theme/ConfigurableTheme.kt new file mode 100644 index 0000000..31884ff --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/theme/ConfigurableTheme.kt @@ -0,0 +1,63 @@ +package com.komoju.mobile.sdk.ui.theme + +import androidx.compose.ui.graphics.Color + +/** + * Interface representing a configurable theme with customizable UI properties. + */ +interface ConfigurableTheme { + + /** + * primary color of the SDK + * + * @return A value representing a color value annotated with [Color]. + */ + val primaryColor: Long + + /** + * Color value for the content (e.g., text or icon) inside the primary button. + * + * @return A value representing a color value annotated with [Color]. + */ + val primaryContentColor: Long + + /** + * Corner radius for the primary button in density-independent pixels (DP). + * + * @return A value representing the corner radius in DP annotated with [Int]. + */ + val primaryShapeCornerRadiusInDp: Int + + /** + * Color value for the loader/spinner. + * + * @return A value representing a color value annotated with [Color]. + */ + val loaderColor: Long + + companion object { + /** + * Provides a default implementation of the [ConfigurableTheme]. + */ + val default = DefaultConfigurableTheme() + } +} + +/** + * Data class representing the default implementation of [ConfigurableTheme]. + * This class uses default values for UI elements such as buttons and loaders. + * + * @param primaryColor The background color of the primary button. + * @param primaryContentColor The color of the content inside the primary button. + * @param primaryShapeCornerRadiusInDp The corner radius of the primary button in DP. + * @param loaderColor The color of the loader/spinner. + */ + +data class DefaultConfigurableTheme( + override val primaryColor: Long = 0xFF297FE7, // Default blue color for primary button. + override val primaryContentColor: Long = 0xFFFFFFFF, // Default white color for primary button content. + override val primaryShapeCornerRadiusInDp: Int = 8, // Default corner radius of 8 DP for primary button. + override val loaderColor: Long = 0xFF3CC239, // Default green color for loader. +) : ConfigurableTheme + +fun Long.toColor(): Color = Color(this) diff --git a/android/src/main/java/com/komoju/android/sdk/ui/theme/Theme.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/theme/Theme.kt similarity index 54% rename from android/src/main/java/com/komoju/android/sdk/ui/theme/Theme.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/theme/Theme.kt index e204735..8461b53 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/theme/Theme.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/theme/Theme.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.ui.theme +package com.komoju.mobile.sdk.ui.theme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -7,7 +7,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.graphics.Color -import com.komoju.android.sdk.KomojuSDK +import com.komoju.mobile.sdk.KomojuMobileSDKConfiguration internal val KomojuLightGreen = Color(0xFF3CC239) internal val KomojuDarkGreen = Color(0xFF172E44) @@ -30,16 +30,26 @@ private val LightColorScheme = lightColorScheme( ) @Composable -internal fun KomojuMobileSdkTheme(configuration: KomojuSDK.Configuration = EmptyConfiguration, content: @Composable () -> Unit) { - CompositionLocalProvider(LocalConfigurableTheme provides configuration.configurableTheme) { - Surface(color = Color.White) { - MaterialTheme( - colorScheme = LightColorScheme, - content = content, - typography = interTypography(), - ) - } +internal fun KomojuMobileSdkTheme(configuration: KomojuMobileSDKConfiguration = EmptyConfiguration, content: @Composable () -> Unit) { + Surface(color = Color.White) { + MaterialTheme( + colorScheme = LightColorScheme, + typography = interTypography(), + content = { + CompositionLocalProvider(LocalConfigurableTheme provides configuration.configurableTheme, content = content) + }, + ) } } -private val EmptyConfiguration = KomojuSDK.Configuration.Builder("", "").build() +internal val EmptyConfiguration = KomojuMobileSDKConfiguration( + language = "ja", + currency = "jpy", + publishableKey = null, + isDebugMode = false, + sessionId = null, + redirectURL = "", + appScheme = "", + configurableTheme = DefaultConfigurableTheme(), + inlinedProcessing = false, +) diff --git a/android/src/main/java/com/komoju/android/sdk/ui/theme/Typography.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/theme/Typography.kt similarity index 59% rename from android/src/main/java/com/komoju/android/sdk/ui/theme/Typography.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/theme/Typography.kt index 582825c..e86b962 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/theme/Typography.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/ui/theme/Typography.kt @@ -1,19 +1,24 @@ -package com.komoju.android.sdk.ui.theme +package com.komoju.mobile.sdk.ui.theme import androidx.compose.material3.Typography import androidx.compose.runtime.Composable -import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight -import com.komoju.android.sdk.R +import com.komoju.mobile.sdk.shared.generated.resources.Res +import com.komoju.mobile.sdk.shared.generated.resources.komoju_font_inter_bold +import com.komoju.mobile.sdk.shared.generated.resources.komoju_font_inter_light +import com.komoju.mobile.sdk.shared.generated.resources.komoju_font_inter_medium +import com.komoju.mobile.sdk.shared.generated.resources.komoju_font_inter_regular +import com.komoju.mobile.sdk.shared.generated.resources.komoju_font_inter_semibold +import org.jetbrains.compose.resources.Font @Composable private fun interFontFamily() = FontFamily( - Font(R.font.komoju_font_inter_light, weight = FontWeight.Light), - Font(R.font.komoju_font_inter_regular, weight = FontWeight.Normal), - Font(R.font.komoju_font_inter_medium, weight = FontWeight.Medium), - Font(R.font.komoju_font_inter_semibold, weight = FontWeight.SemiBold), - Font(R.font.komoju_font_inter_bold, weight = FontWeight.Bold), + Font(Res.font.komoju_font_inter_light, weight = FontWeight.Light), + Font(Res.font.komoju_font_inter_regular, weight = FontWeight.Normal), + Font(Res.font.komoju_font_inter_medium, weight = FontWeight.Medium), + Font(Res.font.komoju_font_inter_semibold, weight = FontWeight.SemiBold), + Font(Res.font.komoju_font_inter_bold, weight = FontWeight.Bold), ) @Composable diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/AmountUtils.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/AmountUtils.kt new file mode 100644 index 0000000..620f5e1 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/AmountUtils.kt @@ -0,0 +1,15 @@ +package com.komoju.mobile.sdk.utils + +import nl.jacobras.humanreadable.HumanReadable + +internal object AmountUtils { + fun formatToDecimal(currency: String, amount: String): String { + if (amount.isBlank()) return "" + return currency.symbol() + HumanReadable.number(amount.toInt(), decimals = 0) + } +} + +private fun String.symbol(): String = when (this.uppercase()) { + "JPY" -> "¥" + else -> "$" +} diff --git a/android/src/main/java/com/komoju/android/sdk/utils/CreditCardUtils.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/CreditCardUtils.kt similarity index 98% rename from android/src/main/java/com/komoju/android/sdk/utils/CreditCardUtils.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/CreditCardUtils.kt index 2ea3805..1878461 100644 --- a/android/src/main/java/com/komoju/android/sdk/utils/CreditCardUtils.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/CreditCardUtils.kt @@ -1,4 +1,4 @@ -package com.komoju.android.sdk.utils +package com.komoju.mobile.sdk.utils import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.OffsetMapping @@ -153,7 +153,7 @@ internal object CreditCardUtils { var sum = 0 var alternate = false for (i in sanitizedCardNumber.length - 1 downTo 0) { - var digit = Character.getNumericValue(sanitizedCardNumber[i]) + var digit = sanitizedCardNumber[i].digitToInt() if (alternate) { digit *= 2 if (digit > 9) { diff --git a/android/src/main/java/com/komoju/android/sdk/utils/DeeplinkEntity.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/DeeplinkEntity.kt similarity index 82% rename from android/src/main/java/com/komoju/android/sdk/utils/DeeplinkEntity.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/DeeplinkEntity.kt index 19eae8a..f54ac51 100644 --- a/android/src/main/java/com/komoju/android/sdk/utils/DeeplinkEntity.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/DeeplinkEntity.kt @@ -1,8 +1,8 @@ -package com.komoju.android.sdk.utils +package com.komoju.mobile.sdk.utils -import androidx.core.net.toUri +import com.eygraber.uri.Uri -internal sealed interface DeeplinkEntity { +sealed interface DeeplinkEntity { sealed interface Verify : DeeplinkEntity { data class BySecureToken(val secureTokenId: String, val amount: String, val currency: String) : Verify @@ -11,7 +11,7 @@ internal sealed interface DeeplinkEntity { companion object { fun from(rawDeeplink: String): DeeplinkEntity { - val uri = rawDeeplink.toUri() + val uri = Uri.parse(rawDeeplink) val token = uri.getQueryParameter("secure_token_id") return when { token != null -> Verify.BySecureToken( diff --git a/android/src/main/java/com/komoju/android/sdk/utils/InlinedPaymentProcessor.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/InlinedPaymentProcessor.kt similarity index 94% rename from android/src/main/java/com/komoju/android/sdk/utils/InlinedPaymentProcessor.kt rename to shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/InlinedPaymentProcessor.kt index 8043127..5c72729 100644 --- a/android/src/main/java/com/komoju/android/sdk/utils/InlinedPaymentProcessor.kt +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/InlinedPaymentProcessor.kt @@ -1,8 +1,8 @@ -package com.komoju.android.sdk.utils +package com.komoju.mobile.sdk.utils -import com.komoju.android.sdk.ui.screens.failed.Reason import com.komoju.mobile.sdk.entities.PaymentStatus import com.komoju.mobile.sdk.remote.apis.KomojuRemoteApi +import com.komoju.mobile.sdk.ui.screens.failed.Reason internal suspend fun KomojuRemoteApi.verifyTokenAndProcessPayment( sessionId: String, diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/Logger.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/Logger.kt new file mode 100644 index 0000000..adb8e91 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/Logger.kt @@ -0,0 +1,17 @@ +package com.komoju.mobile.sdk.utils + +internal object Logger { + private var shouldPrintLogs = false + + fun setDebugMode(debugMode: Boolean) { + shouldPrintLogs = debugMode + } + + fun d(message: String?) { + if (shouldPrintLogs) println(message) + } + + fun e(t: Throwable) { + if (shouldPrintLogs) println(t) + } +} diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/ModifierExt.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/ModifierExt.kt new file mode 100644 index 0000000..92e392f --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/ModifierExt.kt @@ -0,0 +1,5 @@ +package com.komoju.mobile.sdk.utils + +import androidx.compose.ui.Modifier + +internal expect fun Modifier.testID(id: String): Modifier diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/Platform.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/Platform.kt new file mode 100644 index 0000000..8baca64 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/Platform.kt @@ -0,0 +1,7 @@ +package com.komoju.mobile.sdk.utils + +enum class Platform { + ANDROID, IOS +} + +expect val currentPlatform: Platform diff --git a/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/StringExt.kt b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/StringExt.kt new file mode 100644 index 0000000..9b97401 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/komoju/mobile/sdk/utils/StringExt.kt @@ -0,0 +1,24 @@ +package com.komoju.mobile.sdk.utils + +private val EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$".toRegex() + +internal inline val String.Companion.empty get() = "" + +internal inline val String.isValidEmail: Boolean get() = matches(EMAIL_REGEX) + +internal inline val String.isDigitsOnly get() = all { it.isDigit() } + +// japanese detection Stuff below. +internal inline val Char.isKana get() = isHiragana || isKatakana +internal inline val String.isKanaOnly: Boolean get() = all { it.isKana } + +// Source: https://github.com/esnaultdev/wanakana-kt +private const val KATAKANA_START = 0x30a1 +private const val KATAKANA_END = 0x30fc +private const val HIRAGANA_START = 0x3041 +private const val HIRAGANA_END = 0x3096 +private const val PROLONGED_SOUND_MARK = 0x30fc +private val KATAKANA_RANGE = KATAKANA_START..KATAKANA_END +private val HIRAGANA_RANGE = HIRAGANA_START..HIRAGANA_END +private val Char.isHiragana: Boolean get() = code == PROLONGED_SOUND_MARK || code in HIRAGANA_RANGE +private val Char.isKatakana: Boolean get() = code in KATAKANA_RANGE diff --git a/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/.keep b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/MainViewController.kt b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/MainViewController.kt new file mode 100644 index 0000000..0c4efff --- /dev/null +++ b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/MainViewController.kt @@ -0,0 +1,55 @@ +package com.komoju.mobile.sdk + +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.ExperimentalComposeApi +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.uikit.ComposeUIViewControllerDelegate +import androidx.compose.ui.uikit.OnFocusBehavior +import androidx.compose.ui.window.ComposeUIViewController +import com.komoju.mobile.sdk.navigation.PaymentResultScreenModel +import com.komoju.mobile.sdk.navigation.paymentResultScreenModel +import com.komoju.mobile.sdk.ui.screens.KomojuPaymentEntryPoint +import platform.UIKit.UIViewController + +internal val LocalViewController = compositionLocalOf<() -> UIViewController> { + error("LocalViewController not provided") +} + +fun MainViewController( + configuration: KomojuMobileSDKConfiguration, + onDismiss: (KomojuMobileSDKPaymentResult) -> Unit +) = extendedComposeViewController(configuration, onDismiss) + +@OptIn(ExperimentalComposeApi::class) +private fun extendedComposeViewController( + configuration: KomojuMobileSDKConfiguration, + onDismiss: (KomojuMobileSDKPaymentResult) -> Unit, +): UIViewController { + var platformResultScreenModel : PaymentResultScreenModel? = null + var localViewController: UIViewController? = null + return ComposeUIViewController( + configure = { + onFocusBehavior = OnFocusBehavior.FocusableAboveKeyboard + opaque = false + delegate = object : ComposeUIViewControllerDelegate { + override fun viewWillDisappear(animated: Boolean){ + super.viewWillDisappear(animated) + onDismiss(platformResultScreenModel?.result ?: KomojuMobileSDKPaymentResult(isSuccessFul = false)) + } + } + }, + content = { + CompositionLocalProvider(LocalViewController provides { localViewController!! }) { + KomojuPaymentEntryPoint( + configuration = configuration, + onCreated = { navigator -> + platformResultScreenModel = navigator.paymentResultScreenModel() + }, + ) + + } + }, + ).also { + localViewController = it + } +} diff --git a/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/navigation/PlatformBackPress.ios.kt b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/navigation/PlatformBackPress.ios.kt new file mode 100644 index 0000000..662e4aa --- /dev/null +++ b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/navigation/PlatformBackPress.ios.kt @@ -0,0 +1,30 @@ +package com.komoju.mobile.sdk.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.interop.LocalUIViewController +import platform.UIKit.UIViewController + +@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +internal actual class PlatformBackPressDispatcher { + private var uiViewController: UIViewController? = null + + @Composable + fun Create() { + uiViewController = LocalUIViewController.current + } + + actual fun onBackPressed() { + uiViewController?.dismissViewControllerAnimated(true) { + // Nothing to do here + } + } +} + + +@Composable +internal actual fun rememberPlatformBackPressDispatcher(): PlatformBackPressDispatcher = remember { + PlatformBackPressDispatcher() +}.apply { + Create() +} diff --git a/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/ui/composables/ExternalScreenLauncherResult.ios.kt b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/ui/composables/ExternalScreenLauncherResult.ios.kt new file mode 100644 index 0000000..76f0a7b --- /dev/null +++ b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/ui/composables/ExternalScreenLauncherResult.ios.kt @@ -0,0 +1,8 @@ +package com.komoju.mobile.sdk.ui.composables + +@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") +actual class Launcher { + actual fun launch(input: I) { + error("IOS Launcher is not implemented") + } +} diff --git a/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/ui/composables/RememberLauncherForActivityResult.ios.kt b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/ui/composables/RememberLauncherForActivityResult.ios.kt new file mode 100644 index 0000000..2f31215 --- /dev/null +++ b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/ui/composables/RememberLauncherForActivityResult.ios.kt @@ -0,0 +1,6 @@ +package com.komoju.mobile.sdk.ui.composables + +import androidx.compose.runtime.Composable + +@Composable +actual fun launchCustomTab(onResult: (Int) -> Unit): Launcher = Launcher() diff --git a/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/utils/ModifierExt.ios.kt b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/utils/ModifierExt.ios.kt new file mode 100644 index 0000000..3c3a625 --- /dev/null +++ b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/utils/ModifierExt.ios.kt @@ -0,0 +1,8 @@ +package com.komoju.mobile.sdk.utils + +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.semantics + +internal actual fun Modifier.testID(id: String) = semantics { +}.testTag(id) diff --git a/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/utils/Platform.ios.kt b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/utils/Platform.ios.kt new file mode 100644 index 0000000..d3ed78b --- /dev/null +++ b/shared/src/iosMain/kotlin/com/komoju/mobile/sdk/utils/Platform.ios.kt @@ -0,0 +1,3 @@ +package com.komoju.mobile.sdk.utils + +actual val currentPlatform: Platform = Platform.IOS