diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d6045d6..9c0f9bc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,7 +11,7 @@ jobs: runs-on: macOS-latest steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v5 @@ -26,11 +26,10 @@ jobs: uses: gradle/actions/setup-gradle@v5 - name: Publish to MavenCentral - run: ./gradlew publishToMavenCentral --no-configuration-cache + run: ./gradlew publishToMavenCentral --no-configuration-cache -x allTests -x check -x test -x jvmTest -x jsTest -x wasmJsTest -x jsBrowserTest -x jsNodeTest -x wasmJsBrowserTest env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY_CONTENTS }} - diff --git a/README.md b/README.md index cae779b..aa4796b 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Type-safe currency formatting for Kotlin Multiplatform with Compose support and ```kotlin dependencies { - implementation("org.kimplify:kurrency:1.0.0") + implementation("org.kimplify:kurrency-core:0.2.1") } ``` @@ -47,8 +47,8 @@ dependencies { ```kotlin dependencies { - implementation("org.kimplify:kurrency:1.0.0") - implementation("org.kimplify:kurrency-compose:1.0.0") + implementation("org.kimplify:kurrency-core:0.2.1") + implementation("org.kimplify:kurrency-compose:0.2.1") } ``` diff --git a/build.gradle.kts b/build.gradle.kts index a7ad3cf..9447134 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidLibrary) apply false alias(libs.plugins.composeMultiplatform) apply false - alias(libs.plugins.composeHotReload) apply false alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.maven.publish) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f1ed5e6..9bb5ed9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,27 +1,26 @@ [versions] appVersionCode = "5" -appVersionName = "0.2.3" +appVersionName = "0.2.1" # SDK Versions -minSdk = "21" +minSdk = "24" compileSdk = "36" targetSdk = "36" javaVersion = "21" jvmVersion = "JVM_21" -agp = "8.13.1" -androidx-activity = "1.12.0" +agp = "8.13.2" +androidx-activity = "1.12.2" androidx-appcompat = "1.7.1" androidx-core = "1.17.0" androidx-espresso = "3.7.0" androidx-lifecycle = "2.9.6" androidx-testExt = "1.3.0" -composeHotReload = "1.0.0-rc04" composeMultiplatform = "1.9.3" junit = "4.13.2" -kotlin = "2.2.21" +kotlin = "2.3.0" kotlinx-coroutines = "1.10.2" -kotlinStdlib = "2.2.21" +kotlinStdlib = "2.3.0" runner = "1.7.0" core = "1.7.0" maven-publish = "0.35.0" @@ -29,6 +28,7 @@ kotlinx-datetime = "0.7.1" cedar-logging = "0.2.2" [libraries] +androidx-rules = { module = "androidx.test:rules", version.ref = "runner" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } junit = { module = "junit:junit", version.ref = "junit" } @@ -52,7 +52,6 @@ cedar-logging = { group = "org.kimplify", name = "cedar-logging", version.ref = [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } -composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 8fefba8..025e396 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -1024,10 +1024,10 @@ engine.io@~6.6.0: engine.io-parser "~5.2.1" ws "~8.17.1" -enhanced-resolve@^5.17.2: - version "5.18.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz#9b5f4c5c076b8787c78fe540392ce76a88855b44" - integrity sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww== +enhanced-resolve@^5.17.3: + version "5.18.4" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz#c22d33055f3952035ce6a144ce092447c525f828" + integrity sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -1718,10 +1718,9 @@ karma-webpack@5.0.1: minimatch "^9.0.3" webpack-merge "^4.1.5" -karma@6.4.4: +"karma@github:Kotlin/karma#6.4.5": version "6.4.4" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.4.tgz#dfa5a426cf5a8b53b43cd54ef0d0d09742351492" - integrity sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w== + resolved "https://codeload.github.com/Kotlin/karma/tar.gz/239a8fc984584f0d96b1dd750e7a5e2c79da93a6" dependencies: "@colors/colors" "1.5.0" body-parser "^1.19.0" @@ -1753,10 +1752,10 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kotlin-web-helpers@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/kotlin-web-helpers/-/kotlin-web-helpers-2.1.0.tgz#6cd4b0f0dc3baea163929c8638155b8d19c55a74" - integrity sha512-NAJhiNB84tnvJ5EQx7iER3GWw7rsTZkX9HVHZpe7E3dDBD/dhTzqgSwNU3MfQjniy2rB04bP24WM9Z32ntUWRg== +kotlin-web-helpers@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/kotlin-web-helpers/-/kotlin-web-helpers-3.0.0.tgz#3ed6b48f694f74bb60a737a9d7e2c0e3b29abdb9" + integrity sha512-kdQO4AJQkUPvpLh9aglkXDRyN+CfXO7pKq+GESEnxooBFkQpytLrqZis3ABvmFN1cGw/ZQ/K38u5sRGW+NfBnw== dependencies: format-util "^1.0.5" @@ -1931,10 +1930,10 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" -mocha@11.7.1: - version "11.7.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.1.tgz#91948fecd624fb4bd154ed260b7e1ad3910d7c7a" - integrity sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A== +mocha@11.7.2: + version "11.7.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.2.tgz#3c0079fe5cc2f8ea86d99124debcc42bb1ab22b5" + integrity sha512-lkqVJPmqqG/w5jmmFtiRvtA2jkDyNVUcefFJKb2uyX4dekk8Okgqop3cgbFiaIvj8uCRJVTP5x9dfxGyXm2jvQ== dependencies: browser-stdout "^1.3.1" chokidar "^4.0.1" @@ -2885,10 +2884,10 @@ webpack-sources@^3.3.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== -webpack@5.100.2: - version "5.100.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.100.2.tgz#e2341facf9f7de1d702147c91bcb65b693adf9e8" - integrity sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw== +webpack@5.101.3: + version "5.101.3" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.101.3.tgz#3633b2375bb29ea4b06ffb1902734d977bc44346" + integrity sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A== dependencies: "@types/eslint-scope" "^3.7.7" "@types/estree" "^1.0.8" @@ -2900,7 +2899,7 @@ webpack@5.100.2: acorn-import-phases "^1.0.3" browserslist "^4.24.0" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.2" + enhanced-resolve "^5.17.3" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" diff --git a/kurrency-compose/build.gradle.kts b/kurrency-compose/build.gradle.kts index 2ccbd85..1757749 100644 --- a/kurrency-compose/build.gradle.kts +++ b/kurrency-compose/build.gradle.kts @@ -1,18 +1,23 @@ @file:OptIn(ExperimentalWasmDsl::class) +import com.android.build.api.dsl.androidLibrary import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { + alias(libs.plugins.androidKotlinMultiplatformLibrary) alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) - alias(libs.plugins.androidLibrary) alias(libs.plugins.maven.publish) } kotlin { - androidTarget { + androidLibrary { + namespace = "org.kimplify.kurrency.compose" + compileSdk = libs.versions.compileSdk.get().toInt() + minSdk = libs.versions.minSdk.get().toInt() + compilations.configureEach { compileTaskProvider.get().compilerOptions { jvmTarget.set(JvmTarget.valueOf(libs.versions.jvmVersion.get())) @@ -61,45 +66,9 @@ kotlin { val androidMain by getting { dependencies { - implementation("androidx.core:core-ktx:1.12.0") + implementation(libs.androidx.core.ktx) } } - - val jvmMain by getting - - val wasmJsMain by getting - val jsMain by getting - - val webMain by creating { - dependsOn(commonMain) - wasmJsMain.dependsOn(this) - jsMain.dependsOn(this) - } - - val iosX64Main by getting - val iosArm64Main by getting - val iosSimulatorArm64Main by getting - - val iosMain by creating { - dependsOn(commonMain) - iosX64Main.dependsOn(this) - iosArm64Main.dependsOn(this) - iosSimulatorArm64Main.dependsOn(this) - } - } -} - -android { - namespace = "org.kimplify.kurrency.compose" - compileSdk = 36 - - defaultConfig { - minSdk = 24 - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 } } diff --git a/kurrency-core/build.gradle.kts b/kurrency-core/build.gradle.kts index dc159e4..bccdfa4 100644 --- a/kurrency-core/build.gradle.kts +++ b/kurrency-core/build.gradle.kts @@ -1,18 +1,31 @@ @file:OptIn(ExperimentalWasmDsl::class) +import com.android.build.api.dsl.androidLibrary import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { + alias(libs.plugins.androidKotlinMultiplatformLibrary) alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) - alias(libs.plugins.androidLibrary) alias(libs.plugins.maven.publish) } kotlin { - androidTarget { + androidLibrary { + namespace = "org.kimplify.kurrency.core" + compileSdk = libs.versions.compileSdk.get().toInt() + minSdk = libs.versions.minSdk.get().toInt() + + withDeviceTest { + instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + execution = "ANDROIDX_TEST_ORCHESTRATOR" + } + withHostTest { + isIncludeAndroidResources = true + } + compilations.configureEach { compileTaskProvider.get().compilerOptions { jvmTarget.set(JvmTarget.valueOf(libs.versions.jvmVersion.get())) @@ -40,17 +53,14 @@ kotlin { iosTarget.binaries.framework { baseName = "Kurrency" isStatic = true - export(libs.cedar.logging) } } sourceSets { - val commonMain by getting { - dependencies { - implementation(compose.runtime) - implementation(compose.foundation) - api(libs.cedar.logging) - } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(libs.cedar.logging) } val commonTest by getting { @@ -61,59 +71,18 @@ kotlin { val androidMain by getting { dependencies { - implementation("androidx.core:core-ktx:1.12.0") - } - } - - val androidInstrumentedTest by getting { - dependencies { - implementation("androidx.test.ext:junit:1.2.1") - implementation("androidx.test:runner:1.6.2") - implementation("androidx.test:rules:1.6.1") - implementation(libs.kotlin.test) + implementation(libs.androidx.core.ktx) } } - val jvmMain by getting - - val wasmJsMain by getting - val jsMain by getting - - val webMain by creating { - dependsOn(commonMain) - wasmJsMain.dependsOn(this) - jsMain.dependsOn(this) + androidInstrumentedTest.dependencies { + implementation(libs.androidx.testExt.junit) + implementation(libs.androidx.runner) + implementation(libs.androidx.rules) + implementation(libs.kotlin.test) } - val iosX64Main by getting - val iosArm64Main by getting - val iosSimulatorArm64Main by getting - - val iosMain by creating { - dependsOn(commonMain) - iosX64Main.dependsOn(this) - iosArm64Main.dependsOn(this) - iosSimulatorArm64Main.dependsOn(this) - } - } -} - -android { - namespace = "org.kimplify.kurrency" - compileSdk = 36 - - defaultConfig { - minSdk = 24 - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - testOptions { - unitTests.isIncludeAndroidResources = false + iosMain.dependencies {} } } @@ -126,7 +95,8 @@ mavenPublishing { pom { name = "Kurrency" - description = "A Kotlin Multiplatform library for currency formatting and handling across Android, iOS, JVM, and Web platforms" + description = + "A Kotlin Multiplatform library for currency formatting and handling across Android, iOS, JVM, and Web platforms" url = "https://github.com/Kimplify/Kurrency" licenses { @@ -154,4 +124,4 @@ mavenPublishing { url = "https://github.com/Kimplify/Kurrency" } } -} \ No newline at end of file +} diff --git a/kurrency-core/src/androidMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt b/kurrency-core/src/androidMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt index 8af8d60..17d41bd 100644 --- a/kurrency-core/src/androidMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt +++ b/kurrency-core/src/androidMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt @@ -2,7 +2,6 @@ package org.kimplify.kurrency import android.icu.text.NumberFormat import android.icu.util.Currency -import org.kimplify.cedar.logging.Cedar import org.kimplify.kurrency.extensions.replaceCommaWithDot import java.math.BigDecimal import java.util.Locale @@ -17,7 +16,7 @@ actual class CurrencyFormatterImpl actual constructor(kurrencyLocale: KurrencyLo val fractionDigits = currency.defaultFractionDigits if (fractionDigits >= 0) fractionDigits else default }.getOrElse { throwable -> - Cedar.tag("Kurrency").w("Failed to get fraction digits for $currencyCode: ${throwable.message}") + KurrencyLog.w { "Failed to get fraction digits for $currencyCode: ${throwable.message}" } default } } @@ -60,7 +59,7 @@ actual class CurrencyFormatterImpl actual constructor(kurrencyLocale: KurrencyLo numberFormat.format(value) }.getOrElse { throwable -> - Cedar.tag("Kurrency").w("Formatting failed for $currencyCode with amount $amount: ${throwable.message}") + KurrencyLog.w { "Formatting failed for $currencyCode with amount $amount: ${throwable.message}" } amount } } diff --git a/kurrency-core/src/androidTest/kotlin/org/kimplify/kurrency/CurrencyStateTestInstrumented.kt b/kurrency-core/src/androidTest/kotlin/org/kimplify/kurrency/CurrencyStateTestInstrumented.kt index fddd27c..49c2b4a 100644 --- a/kurrency-core/src/androidTest/kotlin/org/kimplify/kurrency/CurrencyStateTestInstrumented.kt +++ b/kurrency-core/src/androidTest/kotlin/org/kimplify/kurrency/CurrencyStateTestInstrumented.kt @@ -8,6 +8,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.assertTrue @RunWith(AndroidJUnit4::class) @@ -17,7 +18,9 @@ class CurrencyStateTestInstrumented { fun testCurrencyStateCreation() { val state = CurrencyState("USD", "100.00") - assertEquals("USD", state.currency.code) + assertEquals("USD", state.currencyCode) + assertNotNull(state.currency) + assertEquals("USD", state.currency?.code) assertEquals("100.00", state.amount) } @@ -25,7 +28,9 @@ class CurrencyStateTestInstrumented { fun testCurrencyStateDefaultAmount() { val state = CurrencyState("USD") - assertEquals("USD", state.currency.code) + assertEquals("USD", state.currencyCode) + assertNotNull(state.currency) + assertEquals("USD", state.currency?.code) assertEquals("0.00", state.amount) } @@ -72,7 +77,9 @@ class CurrencyStateTestInstrumented { state.updateCurrency("EUR") - assertEquals("EUR", state.currency.code) + assertEquals("EUR", state.currencyCode) + assertNotNull(state.currency) + assertEquals("EUR", state.currency?.code) assertNotEquals(originalCurrency, state.currency) assertEquals("100.00", state.amount) } @@ -83,7 +90,7 @@ class CurrencyStateTestInstrumented { state.updateAmount("250.50") - assertEquals("USD", state.currency.code) + assertEquals("USD", state.currencyCode) assertEquals("250.50", state.amount) } @@ -93,7 +100,9 @@ class CurrencyStateTestInstrumented { state.updateCurrencyAndAmount("EUR", "500.00") - assertEquals("EUR", state.currency.code) + assertEquals("EUR", state.currencyCode) + assertNotNull(state.currency) + assertEquals("EUR", state.currency?.code) assertEquals("500.00", state.amount) } @@ -140,8 +149,13 @@ class CurrencyStateTestInstrumented { fun testStateWithInvalidCurrency() { val state = CurrencyState("INVALID", "100.00") val formatted = state.formattedAmount + val result = state.formattedAmountResult - assertEquals("100.00", formatted) + assertEquals("INVALID", state.currencyCode) + assertNull(state.currency) + assertTrue(result.isFailure) + assertTrue(result.exceptionOrNull() is KurrencyError.InvalidCurrencyCode) + assertEquals("", formatted) } @Test @@ -173,7 +187,7 @@ class CurrencyStateTestInstrumented { assertEquals("300.00", state.amount) state.updateCurrency("EUR") - assertEquals("EUR", state.currency.code) + assertEquals("EUR", state.currencyCode) } @Test @@ -199,6 +213,7 @@ class CurrencyStateTestInstrumented { currencies.forEach { code -> val state = CurrencyState(code, "100.00") val formatted = state.formattedAmount + assertTrue(state.currencyResult.isSuccess, "Failed for currency: $code") assertNotNull(formatted, "Failed for currency: $code") } } @@ -208,7 +223,7 @@ class CurrencyStateTestInstrumented { val state1 = CurrencyState("USD", "100.00") val state2 = CurrencyState("USD", "100.00") - assertEquals(state1.currency.code, state2.currency.code) + assertEquals(state1.currencyCode, state2.currencyCode) assertEquals(state1.amount, state2.amount) } } diff --git a/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyFormatter.kt b/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyFormatter.kt index f4885a4..ed64daa 100644 --- a/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyFormatter.kt +++ b/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyFormatter.kt @@ -1,6 +1,5 @@ package org.kimplify.kurrency -import org.kimplify.cedar.logging.Cedar import org.kimplify.kurrency.extensions.replaceCommaWithDot expect class CurrencyFormatterImpl(kurrencyLocale: KurrencyLocale = KurrencyLocale.systemLocale()) : CurrencyFormat { @@ -28,7 +27,7 @@ expect fun isValidCurrency(currencyCode: String): Boolean class CurrencyFormatter(private val locale: KurrencyLocale = KurrencyLocale.systemLocale()) : CurrencyFormat { private val impl: CurrencyFormat by lazy { - Cedar.tag("Kurrency").d("Initializing CurrencyFormatter with locale: ${locale.languageTag}") + KurrencyLog.d { "Initializing CurrencyFormatter with locale: ${locale.languageTag}" } CurrencyFormatterImpl(locale) } @@ -74,28 +73,28 @@ class CurrencyFormatter(private val locale: KurrencyLocale = KurrencyLocale.syst ): Result { if (!isValidCurrencyCode(currencyCode)) { val error = KurrencyError.InvalidCurrencyCode(currencyCode) - Cedar.tag("Kurrency").w(error.errorMessage) + KurrencyLog.w { error.errorMessage } return Result.failure(error) } if (!isValidAmount(amount)) { val error = KurrencyError.InvalidAmount(amount) - Cedar.tag("Kurrency").w(error.errorMessage) + KurrencyLog.w { error.errorMessage } return Result.failure(error) } - Cedar.tag("Kurrency").d("Formatting: amount=$amount, currency=$currencyCode") + KurrencyLog.d { "Formatting: amount=$amount, currency=$currencyCode" } return format(amount) .onFailure { throwable -> val error = KurrencyError.FormattingFailure(currencyCode, amount, throwable) - Cedar.tag("Kurrency").e(throwable, error.errorMessage) + KurrencyLog.e(throwable) { error.errorMessage } } } companion object Companion { private const val DEFAULT_FRACTION_DIGITS = 2 private val defaultFormatter: CurrencyFormat by lazy { - Cedar.tag("Kurrency").d("Initializing default CurrencyFormatter") + KurrencyLog.d { "Initializing default CurrencyFormatter" } CurrencyFormatterImpl() } @@ -113,13 +112,13 @@ class CurrencyFormatter(private val locale: KurrencyLocale = KurrencyLocale.syst return runCatching { val normalizedCode = kurrency.code.uppercase() - Cedar.tag("Kurrency").d("Getting fraction digits for: $normalizedCode") + KurrencyLog.d { "Getting fraction digits for: $normalizedCode" } defaultFormatter.getFractionDigitsOrDefault(normalizedCode, DEFAULT_FRACTION_DIGITS) }.fold( onSuccess = { Result.success(it) }, onFailure = { throwable -> val error = KurrencyError.FractionDigitsFailure(currencyCode, throwable) - Cedar.tag("Kurrency").e(throwable, error.errorMessage) + KurrencyLog.e(throwable) { error.errorMessage } Result.failure(error) } ) diff --git a/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyMetadata.kt b/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyMetadata.kt index d5de8e5..7c45e7d 100644 --- a/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyMetadata.kt +++ b/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyMetadata.kt @@ -1,6 +1,5 @@ package org.kimplify.kurrency -import org.kimplify.cedar.logging.Cedar enum class CurrencyMetadata( val code: String, @@ -63,34 +62,33 @@ enum class CurrencyMetadata( companion object { private val codeMap by lazy { - Cedar.tag("Kurrency").d("Initializing CurrencyMetadata map with ${entries.size} currencies") + KurrencyLog.d { "Initializing CurrencyMetadata map with ${entries.size} currencies" } entries.associateBy { it.code.uppercase() } } fun parse(code: String): Result { if (code.isBlank()) { val error = KurrencyError.InvalidCurrencyCode(code) - Cedar.tag("Kurrency").w(error.errorMessage) + KurrencyLog.w { error.errorMessage } return Result.failure(error) } val normalizedCode = code.uppercase().trim() - Cedar.tag("Kurrency").d("Parsing currency code: $normalizedCode") + KurrencyLog.d { "Parsing currency code: $normalizedCode" } return codeMap[normalizedCode]?.let { metadata -> - Cedar.tag("Kurrency").d("Successfully parsed currency: ${metadata.displayName} ${metadata.flag}") + KurrencyLog.d { "Successfully parsed currency: ${metadata.displayName} ${metadata.flag}" } Result.success(metadata) } ?: run { val error = KurrencyError.InvalidCurrencyCode(code) - Cedar.tag("Kurrency").w(error.errorMessage) + KurrencyLog.w { error.errorMessage } Result.failure(error) } } fun getAll(): List { - Cedar.tag("Kurrency").d("Retrieving all ${entries.size} currencies") + KurrencyLog.d { "Retrieving all ${entries.size} currencies" } return entries } } } - diff --git a/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyState.kt b/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyState.kt index b88d5ec..ee736b3 100644 --- a/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyState.kt +++ b/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyState.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import org.kimplify.cedar.logging.Cedar import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -16,17 +15,29 @@ class CurrencyState( initialCurrencyCode: String, initialAmount: String = "0.00" ) { - var currency by mutableStateOf(Kurrency.fromCode(initialCurrencyCode).getOrElse { Kurrency.USD }) + var currencyCode by mutableStateOf(initialCurrencyCode) private set var amount by mutableStateOf(initialAmount) private set + val currencyResult: Result + get() = Kurrency.fromCode(currencyCode) + + val currency: Kurrency? + get() = currencyResult.getOrNull() + val formattedAmountResult: Result - get() = currency.formatAmount(amount) + get() = currencyResult.fold( + onSuccess = { it.formatAmount(amount) }, + onFailure = { Result.failure(it) } + ) val formattedAmountIsoResult: Result - get() = currency.formatAmount(amount, CurrencyStyle.Iso) + get() = currencyResult.fold( + onSuccess = { it.formatAmount(amount, CurrencyStyle.Iso) }, + onFailure = { Result.failure(it) } + ) val formattedAmount: String get() = formattedAmountResult.getOrDefault("") @@ -35,18 +46,18 @@ class CurrencyState( get() = formattedAmountIsoResult.getOrDefault("") fun updateCurrency(currencyCode: String) { - Cedar.tag("Kurrency").d("Updating currency: $currencyCode") - currency = Kurrency.fromCode(currencyCode).getOrElse { Kurrency.USD } + KurrencyLog.d { "Updating currency: $currencyCode" } + this.currencyCode = currencyCode } fun updateAmount(newAmount: String) { - Cedar.tag("Kurrency").d("Updating amount: $newAmount") + KurrencyLog.d { "Updating amount: $newAmount" } amount = newAmount } fun updateCurrencyAndAmount(currencyCode: String, newAmount: String) { - Cedar.tag("Kurrency").d("Updating currency and amount: currency=$currencyCode, amount=$newAmount") - currency = Kurrency.fromCode(currencyCode).getOrElse { Kurrency.USD } + KurrencyLog.d { "Updating currency and amount: currency=$currencyCode, amount=$newAmount" } + this.currencyCode = currencyCode amount = newAmount } } @@ -57,12 +68,15 @@ class FormattedAmountDelegate( private val style: CurrencyStyle = CurrencyStyle.Standard ) : ReadOnlyProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): String { - return state.currency.formatAmount(state.amount, style).getOrDefault("") + return state.currencyResult.fold( + onSuccess = { it.formatAmount(state.amount, style).getOrDefault("") }, + onFailure = { "" } + ) } } @ExperimentalKurrency -fun CurrencyState.formattedAmount(style: CurrencyStyle = CurrencyStyle.Standard) = +fun CurrencyState.formattedAmount(style: CurrencyStyle = CurrencyStyle.Standard) = FormattedAmountDelegate(this, style) @ExperimentalKurrency diff --git a/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/KurrencyLogging.kt b/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/KurrencyLogging.kt new file mode 100644 index 0000000..43d36d0 --- /dev/null +++ b/kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/KurrencyLogging.kt @@ -0,0 +1,64 @@ +package org.kimplify.kurrency + +import org.kimplify.cedar.logging.Cedar + +interface KurrencyLogger { + fun debug(tag: String, message: String) + fun warn(tag: String, message: String) + fun error(tag: String, message: String, throwable: Throwable) +} + +enum class KurrencyLogLevel { + Debug, + Warn, + Error, + None +} + +object KurrencyLogging { + var enabled: Boolean = false + var minLevel: KurrencyLogLevel = KurrencyLogLevel.Debug + var logger: KurrencyLogger = CedarLogger() +} + +class CedarLogger : KurrencyLogger { + override fun debug(tag: String, message: String) { + Cedar.tag(tag).d(message) + } + + override fun warn(tag: String, message: String) { + Cedar.tag(tag).w(message) + } + + override fun error(tag: String, message: String, throwable: Throwable) { + Cedar.tag(tag).e(throwable, message) + } +} + +internal object KurrencyLog { + private const val TAG = "Kurrency" + + inline fun d(message: () -> String) { + log(KurrencyLogLevel.Debug) { it.debug(TAG, message()) } + } + + inline fun w(message: () -> String) { + log(KurrencyLogLevel.Warn) { it.warn(TAG, message()) } + } + + inline fun e(throwable: Throwable, message: () -> String) { + log(KurrencyLogLevel.Error) { it.error(TAG, message(), throwable) } + } + + inline fun log(level: KurrencyLogLevel, block: (KurrencyLogger) -> Unit) { + if (!isEnabled(level)) return + block(KurrencyLogging.logger) + } + + private fun isEnabled(level: KurrencyLogLevel): Boolean { + if (!KurrencyLogging.enabled) return false + val minLevel = KurrencyLogging.minLevel + if (minLevel == KurrencyLogLevel.None) return false + return level.ordinal >= minLevel.ordinal + } +} diff --git a/kurrency-core/src/commonTest/kotlin/org/kimplify/kurrency/CurrencyStateTest.kt b/kurrency-core/src/commonTest/kotlin/org/kimplify/kurrency/CurrencyStateTest.kt index 4e7ccaa..c1e78cb 100644 --- a/kurrency-core/src/commonTest/kotlin/org/kimplify/kurrency/CurrencyStateTest.kt +++ b/kurrency-core/src/commonTest/kotlin/org/kimplify/kurrency/CurrencyStateTest.kt @@ -6,206 +6,221 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.assertTrue class CurrencyStateTest { - + @Test fun testCurrencyStateCreation() { val state = CurrencyState("USD", "100.00") - - assertEquals("USD", state.currency.code) + + assertEquals("USD", state.currencyCode) + assertNotNull(state.currency) + assertEquals("USD", state.currency?.code) assertEquals("100.00", state.amount) } - + @Test fun testCurrencyStateDefaultAmount() { val state = CurrencyState("USD") - - assertEquals("USD", state.currency.code) + + assertEquals("USD", state.currencyCode) + assertNotNull(state.currency) + assertEquals("USD", state.currency?.code) assertEquals("0.00", state.amount) } - + @Test fun testFormattedAmount() { val state = CurrencyState("USD", "1234.56") val formatted = state.formattedAmount - + assertNotNull(formatted) assertTrue(formatted.isNotEmpty()) } - + @Test fun testFormattedAmountIso() { val state = CurrencyState("USD", "1234.56") val formatted = state.formattedAmountIso - + assertNotNull(formatted) assertTrue(formatted.isNotEmpty()) } - + @Test fun testFormattedAmountResult() { val state = CurrencyState("USD", "1234.56") val result = state.formattedAmountResult - + assertTrue(result.isSuccess) assertNotNull(result.getOrNull()) } - + @Test fun testFormattedAmountIsoResult() { val state = CurrencyState("USD", "1234.56") val result = state.formattedAmountIsoResult - + assertTrue(result.isSuccess) assertNotNull(result.getOrNull()) } - + @Test fun testUpdateCurrency() { val state = CurrencyState("USD", "100.00") val originalCurrency = state.currency - + state.updateCurrency("EUR") - - assertEquals("EUR", state.currency.code) + + assertEquals("EUR", state.currencyCode) + assertNotNull(state.currency) + assertEquals("EUR", state.currency?.code) assertNotEquals(originalCurrency, state.currency) assertEquals("100.00", state.amount) } - + @Test fun testUpdateAmount() { val state = CurrencyState("USD", "100.00") - + state.updateAmount("250.50") - - assertEquals("USD", state.currency.code) + + assertEquals("USD", state.currencyCode) assertEquals("250.50", state.amount) } - + @Test fun testUpdateCurrencyAndAmount() { val state = CurrencyState("USD", "100.00") - + state.updateCurrencyAndAmount("EUR", "500.00") - - assertEquals("EUR", state.currency.code) + + assertEquals("EUR", state.currencyCode) + assertNotNull(state.currency) + assertEquals("EUR", state.currency?.code) assertEquals("500.00", state.amount) } - + @Test fun testFormattedAmountAfterUpdate() { val state = CurrencyState("USD", "100.00") val beforeUpdate = state.formattedAmount - + state.updateAmount("200.00") val afterUpdate = state.formattedAmount - + assertNotEquals(beforeUpdate, afterUpdate) } - + @Test fun testFormattedAmountAfterCurrencyUpdate() { val state = CurrencyState("USD", "100.00") val beforeUpdate = state.formattedAmount - + state.updateCurrency("EUR") val afterUpdate = state.formattedAmount - + assertNotEquals(beforeUpdate, afterUpdate) } - + @Test fun testStateWithInvalidAmount() { val state = CurrencyState("USD", "invalid") val formatted = state.formattedAmount - + assertEquals("", formatted) } - + @Test fun testStateWithInvalidAmountResult() { val state = CurrencyState("USD", "invalid") val result = state.formattedAmountResult - + assertTrue(result.isFailure) assertTrue(result.exceptionOrNull() is KurrencyError.InvalidAmount) } - + @Test fun testStateWithInvalidCurrency() { val state = CurrencyState("INVALID", "100.00") val formatted = state.formattedAmount - - assertEquals("100.00", formatted) + val result = state.formattedAmountResult + + assertEquals("INVALID", state.currencyCode) + assertNull(state.currency) + assertTrue(result.isFailure) + assertTrue(result.exceptionOrNull() is KurrencyError.InvalidCurrencyCode) + assertEquals("", formatted) } - + @Test fun testStateWithZeroAmount() { val state = CurrencyState("USD", "0.00") val formatted = state.formattedAmount - + assertNotNull(formatted) assertTrue(formatted.isNotEmpty()) } - + @Test fun testStateWithNegativeAmount() { val state = CurrencyState("USD", "-100.50") val formatted = state.formattedAmount - + assertNotNull(formatted) assertTrue(formatted.isNotEmpty()) } - + @Test fun testMultipleSequentialUpdates() { val state = CurrencyState("USD", "100.00") - + state.updateAmount("200.00") assertEquals("200.00", state.amount) - + state.updateAmount("300.00") assertEquals("300.00", state.amount) - + state.updateCurrency("EUR") - assertEquals("EUR", state.currency.code) + assertEquals("EUR", state.currencyCode) } - + @Test fun testFormattedAmountDelegateCreation() { val state = CurrencyState("USD", "100.00") val delegate = state.formattedAmount() - + assertNotNull(delegate) } - + @Test fun testFormattedAmountDelegateWithStyle() { val state = CurrencyState("USD", "100.00") val delegate = state.formattedAmount(CurrencyStyle.Iso) - + assertNotNull(delegate) } - + @Test fun testStateWithDifferentCurrencies() { val currencies = listOf("USD", "EUR", "GBP", "JPY", "CHF", "CAD", "AUD") - + currencies.forEach { code -> val state = CurrencyState(code, "100.00") val formatted = state.formattedAmount + assertTrue(state.currencyResult.isSuccess, "Failed for currency: $code") assertNotNull(formatted, "Failed for currency: $code") } } - + @Test fun testStateImmutabilityAfterCreation() { val state1 = CurrencyState("USD", "100.00") val state2 = CurrencyState("USD", "100.00") - - assertEquals(state1.currency.code, state2.currency.code) + + assertEquals(state1.currencyCode, state2.currencyCode) assertEquals(state1.amount, state2.amount) } } diff --git a/kurrency-core/src/iosMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt b/kurrency-core/src/iosMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt index 35da985..1e5f965 100644 --- a/kurrency-core/src/iosMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt +++ b/kurrency-core/src/iosMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt @@ -1,6 +1,5 @@ package org.kimplify.kurrency -import org.kimplify.cedar.logging.Cedar import org.kimplify.kurrency.extensions.replaceCommaWithDot import platform.Foundation.NSLocale import platform.Foundation.NSNumber @@ -32,7 +31,7 @@ actual class CurrencyFormatterImpl actual constructor(private val kurrencyLocale val fractionDigits = formatter.maximumFractionDigits.toInt() if (fractionDigits >= 0) fractionDigits else default }.getOrElse { throwable -> - Cedar.tag("Kurrency").w("Failed to get fraction digits for $currencyCode: ${throwable.message}") + KurrencyLog.w { "Failed to get fraction digits for $currencyCode: ${throwable.message}" } default } } @@ -67,7 +66,7 @@ actual class CurrencyFormatterImpl actual constructor(private val kurrencyLocale val numberFormatter = createNumberFormatter(currencyCode, style) numberFormatter.stringFromNumber(value) ?: "" }.getOrElse { throwable -> - Cedar.tag("Kurrency").w("Formatting failed for $currencyCode with amount $amount: ${throwable.message}") + KurrencyLog.w { "Formatting failed for $currencyCode with amount $amount: ${throwable.message}" } amount } } diff --git a/kurrency-core/src/jsMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt b/kurrency-core/src/jsMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt index c2b1d07..478683d 100644 --- a/kurrency-core/src/jsMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt +++ b/kurrency-core/src/jsMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt @@ -1,6 +1,5 @@ package org.kimplify.kurrency -import org.kimplify.cedar.logging.Cedar import org.kimplify.kurrency.extensions.replaceCommaWithDot @JsName("Intl") @@ -78,7 +77,7 @@ actual class CurrencyFormatterImpl actual constructor( val fractionDigits = jsGetMaxFractionDigits(upperCode, locale) if (fractionDigits >= 0) fractionDigits else default }.getOrElse { throwable -> - Cedar.tag("Kurrency").w("Failed to get fraction digits for $currencyCode: ${throwable.message}") + KurrencyLog.w { "Failed to get fraction digits for $currencyCode: ${throwable.message}" } default } } @@ -95,7 +94,7 @@ actual class CurrencyFormatterImpl actual constructor( require(doubleValue.isFinite()) { "Amount must be a finite number" } jsFormatSymbol(normalizedAmount, currencyCode, locale) }.getOrElse { throwable -> - Cedar.tag("Kurrency").w("Formatting failed for $currencyCode with amount $amount: ${throwable.message}") + KurrencyLog.w { "Formatting failed for $currencyCode with amount $amount: ${throwable.message}" } amount } } @@ -112,7 +111,7 @@ actual class CurrencyFormatterImpl actual constructor( require(doubleValue.isFinite()) { "Amount must be a finite number" } jsFormatIso(normalizedAmount, currencyCode, locale) }.getOrElse { throwable -> - Cedar.tag("Kurrency").w("Formatting failed for $currencyCode with amount $amount: ${throwable.message}") + KurrencyLog.w { "Formatting failed for $currencyCode with amount $amount: ${throwable.message}" } amount } } diff --git a/kurrency-core/src/jvmMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt b/kurrency-core/src/jvmMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt index 2c3e224..334ef1e 100644 --- a/kurrency-core/src/jvmMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt +++ b/kurrency-core/src/jvmMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt @@ -1,6 +1,5 @@ package org.kimplify.kurrency -import org.kimplify.cedar.logging.Cedar import org.kimplify.kurrency.extensions.replaceCommaWithDot import java.text.NumberFormat import java.util.Currency @@ -18,7 +17,7 @@ actual class CurrencyFormatterImpl actual constructor( requireNotNull(currency) { "Currency instance is null for code: $currencyCode" } currency.defaultFractionDigits }.getOrElse { throwable -> - Cedar.tag("Kurrency").w("Failed to get fraction digits for $currencyCode: ${throwable.message}") + KurrencyLog.w { "Failed to get fraction digits for $currencyCode: ${throwable.message}" } default } } @@ -62,7 +61,7 @@ actual class CurrencyFormatterImpl actual constructor( numberFormat.format(value) ?: "" } }.getOrElse { throwable -> - Cedar.tag("Kurrency").w("Formatting failed for $currencyCode with amount $amount: ${throwable.message}") + KurrencyLog.w { "Formatting failed for $currencyCode with amount $amount: ${throwable.message}" } amount } } diff --git a/kurrency-core/src/wasmJsMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt b/kurrency-core/src/wasmJsMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt index ae9d506..7f22fd5 100644 --- a/kurrency-core/src/wasmJsMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt +++ b/kurrency-core/src/wasmJsMain/kotlin/org/kimplify/kurrency/CurrencyFormatterImpl.kt @@ -2,7 +2,6 @@ package org.kimplify.kurrency -import org.kimplify.cedar.logging.Cedar import org.kimplify.kurrency.extensions.replaceCommaWithDot @JsFun("function(cur, loc) { return new Intl.NumberFormat(loc || undefined, {style:'currency', currency:cur}).resolvedOptions().maximumFractionDigits; }") @@ -38,8 +37,7 @@ actual class CurrencyFormatterImpl actual constructor(kurrencyLocale: KurrencyLo val fractionDigits = jsGetMaxFractionDigits(upperCode, locale) if (fractionDigits >= 0) fractionDigits else default }.getOrElse { throwable -> - Cedar.tag("Kurrency") - .w("Failed to get fraction digits for $currencyCode: ${throwable.message}") + KurrencyLog.w { "Failed to get fraction digits for $currencyCode: ${throwable.message}" } default } } @@ -56,8 +54,7 @@ actual class CurrencyFormatterImpl actual constructor(kurrencyLocale: KurrencyLo require(doubleValue.isFinite()) { "Amount must be a finite number" } jsFormatSymbol(normalizedAmount, currencyCode, locale) }.getOrElse { throwable -> - Cedar.tag("Kurrency") - .w("Formatting failed for $currencyCode with amount $amount: ${throwable.message}") + KurrencyLog.w { "Formatting failed for $currencyCode with amount $amount: ${throwable.message}" } amount } } @@ -74,8 +71,7 @@ actual class CurrencyFormatterImpl actual constructor(kurrencyLocale: KurrencyLo require(doubleValue.isFinite()) { "Amount must be a finite number" } jsFormatIso(normalizedAmount, currencyCode, locale) }.getOrElse { throwable -> - Cedar.tag("Kurrency") - .w("Formatting failed for $currencyCode with amount $amount: ${throwable.message}") + KurrencyLog.w { "Formatting failed for $currencyCode with amount $amount: ${throwable.message}" } amount } } diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index f38f7c1..2ebb983 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -7,7 +7,6 @@ plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) - alias(libs.plugins.composeHotReload) } kotlin {