Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ distinguish between cellular vs WiFi connection.
![badge][badge-android]
![badge][badge-ios]

[currency](currency/README.md): Format currency values.
![badge][badge-android]
![badge][badge-ios]
![badge][badge-js]
![badge][badge-jvm]

[ignore-ios](ignore-ios/README.md): Annotations to ignore iOS tests.
![badge][badge-android]
![badge][badge-ios]
Expand Down Expand Up @@ -54,7 +60,7 @@ language translations, unit/integration tests are welcomed.

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)

Copyright 2021 Appmattus Limited
Copyright 2021-2025 Appmattus Limited

Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
Expand Down
35 changes: 35 additions & 0 deletions currency/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# currency

A Kotlin Multiplatform Mobile library to format currency values.

## Getting started

![badge][badge-android]
![badge][badge-ios]
![badge][badge-js]
[![Maven Central](https://img.shields.io/maven-central/v/com.appmattus.mpu/currency)](https://search.maven.org/search?q=g:com.appmattus.mpu)

Include the following dependency in your *build.gradle.kts* file:

```kotlin
commonMain {
implementation("com.appmattus.mpu:currency:<latest-version>")
}
```

Format a currency value:

```kotlin
Currency.format(value = 1345.23, currencyCode = "GBP", locale = "en-GB")
```

[badge-android]: http://img.shields.io/badge/platform-android-6EDB8D.svg?style=flat
[badge-ios]: http://img.shields.io/badge/platform-ios-CDCDCD.svg?style=flat
[badge-js]: http://img.shields.io/badge/platform-js-F8DB5D.svg?style=flat
[badge-jvm]: http://img.shields.io/badge/platform-jvm-DB413D.svg?style=flat
[badge-linux]: http://img.shields.io/badge/platform-linux-2D3F6C.svg?style=flat
[badge-windows]: http://img.shields.io/badge/platform-windows-4D76CD.svg?style=flat
[badge-mac]: http://img.shields.io/badge/platform-macos-111111.svg?style=flat
[badge-watchos]: http://img.shields.io/badge/platform-watchos-C0C0C0.svg?style=flat
[badge-tvos]: http://img.shields.io/badge/platform-tvos-808080.svg?style=flat
[badge-wasm]: https://img.shields.io/badge/platform-wasm-624FE8.svg?style=flat
85 changes: 85 additions & 0 deletions currency/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2025 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id("com.android.library")
kotlin("multiplatform")
alias(libs.plugins.gradleMavenPublishPlugin)
alias(libs.plugins.dokkaPlugin)
}

kotlin {
androidTarget()

iosX64()
iosArm64()
iosSimulatorArm64()

js {
browser()
nodejs()
}

listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "multiplatformutils-currency"
isStatic = true
}
}

// Apply the default hierarchy again. It'll create, for example, the iosMain source set:
applyDefaultHierarchyTemplate()

sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines)
}
commonTest.dependencies {
implementation(kotlin("test"))
}
androidUnitTest.dependencies {
implementation(libs.robolectric)
}
}

compilerOptions {
jvmToolchain(11)
freeCompilerArgs.add("-Xexpect-actual-classes")
}
}

android {
namespace = "com.appmattus.multiplatformutils.currency"
compileSdk = 35

defaultConfig {
minSdk = 21
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
20 changes: 20 additions & 0 deletions currency/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# Copyright 2025 Appmattus Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

POM_NAME=com.appmattus.mpu:currency
POM_ARTIFACT_ID=currency
POM_DESCRIPTION=Format currencies
POM_INCEPTION_YEAR=2025
52 changes: 52 additions & 0 deletions currency/src/androidMain/kotlin/com/appmattus/currency/Currency.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2025 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.currency

import java.text.NumberFormat
import java.util.Currency
import java.util.Locale

actual object Currency {
actual fun format(
amount: Double,
currencyCode: String,
locale: String,
showFractionDigits: Boolean,
roundingMode: RoundingMode
): String {
return NumberFormat.getCurrencyInstance(Locale.forLanguageTag(locale)).apply {
val currency = Currency.getInstance(currencyCode)
this.currency = currency
this.roundingMode = when (roundingMode) {
RoundingMode.Up -> java.math.RoundingMode.UP
RoundingMode.Down -> java.math.RoundingMode.DOWN
RoundingMode.Ceiling -> java.math.RoundingMode.CEILING
RoundingMode.Floor -> java.math.RoundingMode.FLOOR
RoundingMode.HalfUp -> java.math.RoundingMode.HALF_UP
RoundingMode.HalfDown -> java.math.RoundingMode.HALF_DOWN
RoundingMode.HalfEven -> java.math.RoundingMode.HALF_EVEN
}

// When showing decimal places use value from currency as NumberFormat defaults to device locale
// TND is formatted with 2 dp and not 3 dp and JPY with 2 dp and not 0 dp
(if (!showFractionDigits) 0 else currency.defaultFractionDigits).let { decimalPlaces ->
minimumFractionDigits = decimalPlaces
maximumFractionDigits = decimalPlaces
}
}.format(amount)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2025 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.currency

import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
@RunWith(RobolectricTestRunner::class)
actual abstract class RobolectricTest actual constructor()
37 changes: 37 additions & 0 deletions currency/src/commonMain/kotlin/com/appmattus/currency/Currency.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2025 Appmattus Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.appmattus.currency

expect object Currency {

/**
* Format a currency [amount] taking [locale] into account. Decimals are shown based on [showFractionDigits] and numbers are rounded based
* on [roundingMode].
* @param amount Currency amount to format
* @param currencyCode ISO-4217 alphabetic currency code, i.e. CAD
* @param locale IETF BCP 47 language tag, i.e. en-CA
* @param showFractionDigits `true` to show fraction digits, `false` otherwise
* @param roundingMode [RoundingMode] to use
*/
fun format(
amount: Double,
currencyCode: String,
locale: String,
showFractionDigits: Boolean = true,
roundingMode: RoundingMode = RoundingMode.HalfEven
): String
}
Loading