diff --git a/android/src/main/java/com/komoju/android/sdk/ExperimentalKomojuPaymentApi.kt b/android/src/main/java/com/komoju/android/sdk/ExperimentalKomojuPaymentApi.kt index 3b50fdf..3566258 100644 --- a/android/src/main/java/com/komoju/android/sdk/ExperimentalKomojuPaymentApi.kt +++ b/android/src/main/java/com/komoju/android/sdk/ExperimentalKomojuPaymentApi.kt @@ -1,22 +1,28 @@ package com.komoju.android.sdk /** - * This annotation means that this option is experimental and subject to change in Future. - * Please test your behaviour overall once you update the komoju SDK Version + * Annotation used to mark APIs in the Komoju SDK as experimental. + * This indicates that the annotated feature or API is subject to change in future releases. + * Users should be cautious and thoroughly test their code after updating the SDK version. + * + * - The annotation requires explicit opt-in to use the marked elements. + * - A warning will be shown when using experimental APIs. + * + * @see [RequiresOptIn] to learn more about Kotlin's opt-in mechanism. */ @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Retention(AnnotationRetention.BINARY) @Target( - AnnotationTarget.CLASS, - AnnotationTarget.ANNOTATION_CLASS, - AnnotationTarget.PROPERTY, - AnnotationTarget.FIELD, - AnnotationTarget.LOCAL_VARIABLE, - AnnotationTarget.VALUE_PARAMETER, - AnnotationTarget.CONSTRUCTOR, - AnnotationTarget.FUNCTION, - AnnotationTarget.PROPERTY_GETTER, - AnnotationTarget.PROPERTY_SETTER, - AnnotationTarget.TYPEALIAS, + AnnotationTarget.CLASS, // Can annotate classes + AnnotationTarget.ANNOTATION_CLASS, // Can annotate other annotations + AnnotationTarget.PROPERTY, // Can annotate properties + AnnotationTarget.FIELD, // Can annotate fields + AnnotationTarget.LOCAL_VARIABLE, // Can annotate local variables + AnnotationTarget.VALUE_PARAMETER, // Can annotate function parameters + AnnotationTarget.CONSTRUCTOR, // Can annotate constructors + AnnotationTarget.FUNCTION, // Can annotate functions + AnnotationTarget.PROPERTY_GETTER, // Can annotate property getters + AnnotationTarget.PROPERTY_SETTER, // Can annotate property setters + AnnotationTarget.TYPEALIAS, // Can annotate type aliases ) annotation class ExperimentalKomojuPaymentApi 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 0f76199..b116936 100644 --- a/android/src/main/java/com/komoju/android/sdk/KomojuPaymentActivity.kt +++ b/android/src/main/java/com/komoju/android/sdk/KomojuPaymentActivity.kt @@ -113,7 +113,7 @@ internal class KomojuPaymentActivity : ComponentActivity() { } @Composable - fun NewIntentEffect(context: Context, onNewDeeplink: (String) -> Unit) { + private fun NewIntentEffect(context: Context, onNewDeeplink: (String) -> Unit) { LaunchedEffect(Unit) { callbackFlow { val componentActivity = context as ComponentActivity diff --git a/android/src/main/java/com/komoju/android/sdk/KomojuSDK.kt b/android/src/main/java/com/komoju/android/sdk/KomojuSDK.kt index 94d6fb0..bccdcbf 100644 --- a/android/src/main/java/com/komoju/android/sdk/KomojuSDK.kt +++ b/android/src/main/java/com/komoju/android/sdk/KomojuSDK.kt @@ -8,6 +8,7 @@ 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 @@ -29,7 +30,7 @@ object KomojuSDK { 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 = "", // URL to redirect after payment completion. + 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 { diff --git a/android/src/main/java/com/komoju/android/sdk/navigation/PaymentResultScreenModel.kt b/android/src/main/java/com/komoju/android/sdk/navigation/PaymentResultScreenModel.kt index ce993da..141924d 100644 --- a/android/src/main/java/com/komoju/android/sdk/navigation/PaymentResultScreenModel.kt +++ b/android/src/main/java/com/komoju/android/sdk/navigation/PaymentResultScreenModel.kt @@ -16,6 +16,7 @@ internal class PaymentResultScreenModel : ScreenModel { } @Composable -internal fun Navigator.paymentResultScreenModel() = rememberNavigatorScreenModel(PaymentResultScreenModel::class.simpleName) { - PaymentResultScreenModel() -} +internal fun Navigator.paymentResultScreenModel() = rememberNavigatorScreenModel( + tag = PaymentResultScreenModel::class.simpleName, + factory = ::PaymentResultScreenModel, +) 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 c01a4d7..2381b3d 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 @@ -2,17 +2,43 @@ 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. + */ enum class Currency(val currencyCode: String) { + + /** + * Japanese Yen currency with code "JPY". + */ JPY(currencyCode = "JPY"), + + /** + * United States Dollar currency with code "USD". + */ 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. + * If the code does not match any currency, USD is returned as the default. + * + * @param languageCode A string representing the currency code (e.g., "JPY", "USD"). + * @return The matching [Currency] enum constant or [USD] if no match is found. + */ fun parse(languageCode: String): Currency = entries.firstOrNull { it.currencyCode == languageCode } ?: USD } } 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 7799991..3c7528e 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 @@ -2,16 +2,43 @@ package com.komoju.android.sdk.types import java.util.Locale +/** + * Enum class representing supported languages with their respective language codes. + * Each language is associated with a specific locale. + */ enum class Language(val languageCode: String) { + + /** + * English language with code "en". + */ ENGLISH(languageCode = "en"), + + /** + * Japanese language with code "ja". + */ 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 } + companion object { + /** + * Provides the default [Language] based on the system's default locale. + * + * @return The corresponding [Language] based on the system's default language: + * - Returns [JAPANESE] if the default language is Japanese. + * - Returns [ENGLISH] for all other cases. + */ val default get() = when (Locale.getDefault().language) { Locale.JAPANESE.language -> JAPANESE diff --git a/android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedPaymentPrimaryButton.kt b/android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedPaymentPrimaryButton.kt index 448213e..c45200d 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedPaymentPrimaryButton.kt +++ b/android/src/main/java/com/komoju/android/sdk/ui/composables/InlinedPaymentPrimaryButton.kt @@ -85,7 +85,7 @@ internal fun InlinedPaymentPrimaryButton( } } -enum class InlinedPaymentPrimaryButtonState { +internal enum class InlinedPaymentPrimaryButtonState { LOADING, IDLE, SUCCESS, @@ -93,7 +93,7 @@ enum class InlinedPaymentPrimaryButtonState { } @Composable -fun rememberInlinedPaymentPrimaryButtonState( +internal fun rememberInlinedPaymentPrimaryButtonState( default: InlinedPaymentPrimaryButtonState = InlinedPaymentPrimaryButtonState.IDLE, ): MutableState = rememberSaveable { mutableStateOf(default) } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt index b3738c2..54687b2 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt +++ b/android/src/main/java/com/komoju/android/sdk/ui/screens/awating/KonbiniAwaitingPaymentScreen.kt @@ -51,7 +51,7 @@ import com.komoju.mobile.sdk.entities.PaymentStatus internal data class KonbiniAwaitingPaymentScreen(val route: KomojuPaymentRoute.KonbiniAwaitingPayment) : Screen { @Composable override fun Content() { - val screenModel = rememberScreenModel { KonbiniAwaitingPaymentScreenModel(route.configuration, route.payment) } + val screenModel = rememberScreenModel { KonbiniAwaitingPaymentScreenModel(route.payment) } val uiState by screenModel.state.collectAsStateWithLifecycle() RouterEffect(screenModel.router.collectAsStateWithLifecycle(), screenModel::onRouteConsumed) uiState.payment?.let { 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 index a5cf71c..ed76f3b 100644 --- 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 @@ -1,18 +1,14 @@ package com.komoju.android.sdk.ui.screens.awating -import cafe.adriel.voyager.core.model.screenModelScope -import com.komoju.android.sdk.KomojuSDK import com.komoju.android.sdk.navigation.RouterStateScreenModel import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute import com.komoju.android.sdk.ui.screens.Router import com.komoju.mobile.sdk.entities.Payment -import com.komoju.mobile.sdk.remote.apis.KomojuRemoteApi -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -internal class KonbiniAwaitingPaymentScreenModel(private val config: KomojuSDK.Configuration, private val payment: Payment? = null) : - RouterStateScreenModel(KonbiniAwaitingPaymentUiState(payment)) { - private val komojuApi = KomojuRemoteApi(config.publishableKey, config.language.languageCode) +internal class KonbiniAwaitingPaymentScreenModel(payment: Payment? = null) : + RouterStateScreenModel( + KonbiniAwaitingPaymentUiState(payment), + ) { fun onPrimaryButtonClicked() { when (val payment = state.value.payment) { @@ -22,28 +18,6 @@ internal class KonbiniAwaitingPaymentScreenModel(private val config: KomojuSDK.C } fun onSecondaryButtonClicked() { -// when (state.value.payment) { -// is Payment.Konbini -> refreshPayment() -// else -> Unit -// } mutableRouter.value = Router.Pop } - - fun refreshPayment() { - screenModelScope.launch { - val sessionId = config.sessionId ?: return@launch - mutableState.update { - it.copy(isLoading = true) - } - komojuApi.sessions.verifyPaymentBySessionID(sessionId).onSuccess { payment -> - mutableState.update { - it.copy(payment = payment, isLoading = false) - } - }.onFailure { - mutableState.update { - it.copy(isLoading = false) - } - } - } - } } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt index f9f9697..a688f30 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt +++ b/android/src/main/java/com/komoju/android/sdk/ui/screens/payment/KomojuPaymentScreenModel.kt @@ -36,7 +36,7 @@ import kotlinx.coroutines.launch internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configuration) : RouterStateScreenModel(KomojuPaymentUIState()) { - private val komojuApi: KomojuRemoteApi = KomojuRemoteApi(config.publishableKey, config.language.languageCode) + private val komojuApi: KomojuRemoteApi = KomojuRemoteApi.create(config.publishableKey, config.language.languageCode) private val _offSitePaymentURL = MutableStateFlow(null) val offSitePaymentURL = _offSitePaymentURL.asStateFlow() @@ -467,4 +467,9 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat fun onOffSitePaymentURLConsumed() { _offSitePaymentURL.value = null } + + override fun onDispose() { + komojuApi.close() + super.onDispose() + } } diff --git a/android/src/main/java/com/komoju/android/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt b/android/src/main/java/com/komoju/android/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt index 7cb13c8..49d16f5 100644 --- a/android/src/main/java/com/komoju/android/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt +++ b/android/src/main/java/com/komoju/android/sdk/ui/screens/verify/VerifyPaymentScreenModel.kt @@ -13,7 +13,7 @@ import kotlinx.coroutines.launch internal class VerifyPaymentScreenModel(private val config: KomojuSDK.Configuration) : RouterStateScreenModel(Unit) { - private val komojuApi: KomojuRemoteApi = KomojuRemoteApi(config.publishableKey, config.language.languageCode) + private val komojuApi: KomojuRemoteApi = KomojuRemoteApi.create(config.publishableKey, config.language.languageCode) fun process(type: KomojuPaymentRoute.ProcessPayment.ProcessType) { screenModelScope.launch { @@ -70,4 +70,9 @@ internal class VerifyPaymentScreenModel(private val config: KomojuSDK.Configurat mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.CREDIT_CARD_ERROR)) } } + + override fun onDispose() { + komojuApi.close() + super.onDispose() + } } 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 index bc49c08..864953d 100644 --- 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 @@ -5,24 +5,66 @@ 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 { - @get:ColorInt val primaryButtonColor: Int - @get:ColorInt val primaryButtonContentColor: Int + /** + * Color value for the primary button background. + * + * @return An integer representing a color value annotated with [ColorInt]. + */ + @get:ColorInt + val primaryButtonColor: Int - @get:ColorInt val primaryButtonCornerRadiusInDP: 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 - @get:ColorInt val loaderColor: 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"), - override val primaryButtonContentColor: Int = Color.WHITE, - override val primaryButtonCornerRadiusInDP: Int = 8, - override val loaderColor: Int = Color.parseColor("#FF3CC239"), + 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/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 8d3a53a..f0392a0 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 @@ -3,11 +3,25 @@ package com.komoju.mobile.sdk.remote.apis import com.komoju.mobile.sdk.i18n.I18nTexts import com.komoju.mobile.sdk.remote.createNetworkClient -class KomojuRemoteApi(publishableKey: String?, language: String) { +interface KomojuRemoteApi : AutoCloseable { + val sessions: SessionApi + val tokens: TokensApi + + companion object { + fun create(publishableKey: String?, language: String): KomojuRemoteApi = KomojuRemoteApiImpl(publishableKey, language) + } +} + +internal class KomojuRemoteApiImpl(publishableKey: String?, language: String) : KomojuRemoteApi { private val networkClient by lazy { createNetworkClient(publishableKey) } private val i18nTexts by lazy { I18nTexts(language) } - val sessions: SessionApi by lazy { SessionApiImpl(i18nTexts, networkClient) } - val tokens: TokensApi by lazy { TokenApiImpl(networkClient) } + override val sessions: SessionApi by lazy { SessionApiImpl(i18nTexts, networkClient) } + + override val tokens: TokensApi by lazy { TokenApiImpl(networkClient) } + + override fun close() { + networkClient.close() + } }