summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-07-30 16:40:23 -0300
committerTorsten Grote <t@grobox.de>2020-07-30 16:40:23 -0300
commit8815105bf2462787885214a12af927d484226f21 (patch)
tree597076d6970e336b881d68ca6b48577b007c9730
parente19ba096d57353db6b1f141da4bf170ef2d2d534 (diff)
downloadtaler-android-8815105bf2462787885214a12af927d484226f21.tar.gz
taler-android-8815105bf2462787885214a12af927d484226f21.tar.bz2
taler-android-8815105bf2462787885214a12af927d484226f21.zip
Split out common code into multiplatform Kotlin library
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--.idea/gradle.xml1
-rw-r--r--anastasis-ui/build.gradle2
-rw-r--r--build.gradle4
-rw-r--r--cashier/.gitlab-ci.yml1
-rw-r--r--cashier/build.gradle2
-rw-r--r--cashier/src/main/java/net/taler/cashier/MainViewModel.kt16
-rw-r--r--merchant-lib/.gitlab-ci.yml1
-rw-r--r--merchant-lib/build.gradle2
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/Response.kt3
-rw-r--r--merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt1
-rw-r--r--merchant-terminal/.gitlab-ci.yml1
-rw-r--r--settings.gradle3
-rw-r--r--taler-kotlin-android/.gitignore1
-rw-r--r--taler-kotlin-android/.gitlab-ci.yml12
-rw-r--r--taler-kotlin-android/build.gradle78
-rw-r--r--taler-kotlin-android/consumer-rules.pro (renamed from taler-kotlin-common/consumer-rules.pro)0
-rw-r--r--taler-kotlin-android/proguard-rules.pro (renamed from taler-kotlin-common/proguard-rules.pro)0
-rw-r--r--taler-kotlin-android/src/main/AndroidManifest.xml (renamed from taler-kotlin-common/src/main/AndroidManifest.xml)0
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt51
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt)0
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/ByteArrayUtils.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/ByteArrayUtils.kt)0
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/CombinedLiveData.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt)0
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt)4
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/Event.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/Event.kt)0
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/NfcManager.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/NfcManager.kt)0
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/QrCodeManager.kt)0
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/SignedAmount.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt)0
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/TalerUtils.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt)3
-rw-r--r--taler-kotlin-android/src/main/res/drawable/selectable_background.xml (renamed from taler-kotlin-common/src/main/res/drawable/selectable_background.xml)0
-rw-r--r--taler-kotlin-android/src/main/res/values-night/colors.xml (renamed from taler-kotlin-common/src/main/res/values-night/colors.xml)0
-rw-r--r--taler-kotlin-android/src/main/res/values/colors.xml (renamed from taler-kotlin-common/src/main/res/values/colors.xml)0
-rw-r--r--taler-kotlin-android/src/main/res/values/strings.xml (renamed from taler-kotlin-common/src/main/res/values/strings.xml)0
-rw-r--r--taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt74
-rw-r--r--taler-kotlin-common/.gitlab-ci.yml6
-rw-r--r--taler-kotlin-common/build.gradle130
-rw-r--r--taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/Amount.kt)70
-rw-r--r--taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt81
-rw-r--r--taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt (renamed from taler-kotlin-common/src/main/java/net/taler/common/Version.kt)0
-rw-r--r--taler-kotlin-common/src/commonTest/kotlin/net/taler/common/AmountTest.kt (renamed from taler-kotlin-common/src/test/java/net/taler/common/AmountTest.kt)147
-rw-r--r--taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt26
-rw-r--r--taler-kotlin-common/src/commonTest/kotlin/net/taler/common/VersionTest.kt (renamed from taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt)6
-rw-r--r--taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt23
-rw-r--r--taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt21
-rw-r--r--taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt23
-rw-r--r--wallet/.gitlab-ci.yml1
-rw-r--r--wallet/build.gradle10
-rw-r--r--wallet/src/main/java/net/taler/wallet/MainViewModel.kt5
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt53
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt13
51 files changed, 551 insertions, 326 deletions
diff --git a/.gitignore b/.gitignore
index 7e4952ad..caf9ce6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*.iml
.gradle
/local.properties
+/.idea/artifacts
/.idea/caches
/.idea/libraries
/.idea/misc.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 48f1aec6..6dc44267 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,6 +14,7 @@ include:
- local: 'merchant-lib/.gitlab-ci.yml'
- local: 'merchant-terminal/.gitlab-ci.yml'
- local: 'taler-kotlin-common/.gitlab-ci.yml'
+ - local: 'taler-kotlin-android/.gitlab-ci.yml'
- local: 'wallet/.gitlab-ci.yml'
after_script:
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 581abbf5..01ed15f8 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -14,6 +14,7 @@
<option value="$PROJECT_DIR$/cashier" />
<option value="$PROJECT_DIR$/merchant-lib" />
<option value="$PROJECT_DIR$/merchant-terminal" />
+ <option value="$PROJECT_DIR$/taler-kotlin-android" />
<option value="$PROJECT_DIR$/taler-kotlin-common" />
<option value="$PROJECT_DIR$/wallet" />
</set>
diff --git a/anastasis-ui/build.gradle b/anastasis-ui/build.gradle
index 0391c7ce..ff0eec57 100644
--- a/anastasis-ui/build.gradle
+++ b/anastasis-ui/build.gradle
@@ -51,7 +51,7 @@ android {
}
dependencies {
- implementation project(":taler-kotlin-common")
+ implementation project(":taler-kotlin-android")
implementation 'com.google.android.material:material:1.2.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
diff --git a/build.gradle b/build.gradle
index 76f687ec..442d232d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,7 +24,3 @@ allprojects {
maven { url 'https://jitpack.io' }
}
}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/cashier/.gitlab-ci.yml b/cashier/.gitlab-ci.yml
index 6a7baedf..6b73deeb 100644
--- a/cashier/.gitlab-ci.yml
+++ b/cashier/.gitlab-ci.yml
@@ -6,6 +6,7 @@ cashier_test:
changes:
- cashier/**/*
- taler-kotlin-common/**/*
+ - taler-kotlin-android/**/*
- build.gradle
script: ./gradlew :cashier:check :cashier:assembleRelease
artifacts:
diff --git a/cashier/build.gradle b/cashier/build.gradle
index 0d06c60e..641a0398 100644
--- a/cashier/build.gradle
+++ b/cashier/build.gradle
@@ -54,7 +54,7 @@ android {
}
dependencies {
- implementation project(":taler-kotlin-common")
+ implementation project(":taler-kotlin-android")
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.security:security-crypto:1.0.0-rc02'
implementation 'com.google.android.material:material:1.1.0'
diff --git a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
index c8d9a3b3..a4fd35eb 100644
--- a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
+++ b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
@@ -40,6 +40,7 @@ import net.taler.common.isOnline
private val TAG = MainViewModel::class.java.simpleName
+private const val VERSION_BANK = "0:0:0"
private const val PREF_NAME = "net.taler.cashier.prefs"
private const val PREF_KEY_BANK_URL = "bankUrl"
private const val PREF_KEY_USERNAME = "username"
@@ -86,20 +87,21 @@ class MainViewModel(private val app: Application) : AndroidViewModel(app) {
fun checkAndSaveConfig(config: Config) {
mConfigResult.value = null
viewModelScope.launch(Dispatchers.IO) {
- val url = "${config.bankUrl}/accounts/${config.username}/balance"
+ val url = "${config.bankUrl}/config"
Log.d(TAG, "Checking config: $url")
val result = when (val response = makeJsonGetRequest(url, config)) {
is HttpJsonResult.Success -> {
- val balance = response.json.getString("balance")
+ val version = response.json.getString("version")
+ // TODO check if version is compatible
+ val currency = response.json.getString("currency")
try {
- val amount = SignedAmount.fromJSONString(balance)
- mCurrency.postValue(amount.amount.currency)
- prefs.edit().putString(PREF_KEY_CURRENCY, amount.amount.currency).apply()
+ mCurrency.postValue(currency)
+ prefs.edit().putString(PREF_KEY_CURRENCY, currency).apply()
// save config
saveConfig(config)
ConfigResult.Success
- } catch (e: AmountParserException) {
- ConfigResult.Error(false, "Invalid Amount: $balance")
+ } catch (e: Exception) {
+ ConfigResult.Error(false, "Invalid Config: ${response.json}")
}
}
is HttpJsonResult.Error -> {
diff --git a/merchant-lib/.gitlab-ci.yml b/merchant-lib/.gitlab-ci.yml
index 62a75160..8f7c7c2c 100644
--- a/merchant-lib/.gitlab-ci.yml
+++ b/merchant-lib/.gitlab-ci.yml
@@ -2,6 +2,7 @@ merchant_lib_test:
stage: test
only:
changes:
+ - taler-kotlin-common/**/*
- merchant-lib/**/*
- build.gradle
script: ./gradlew :merchant-lib:check
diff --git a/merchant-lib/build.gradle b/merchant-lib/build.gradle
index 128f4c1d..33e8379b 100644
--- a/merchant-lib/build.gradle
+++ b/merchant-lib/build.gradle
@@ -45,7 +45,7 @@ android {
}
dependencies {
- api project(":taler-kotlin-common")
+ api project(":taler-kotlin-android")
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
index 9aefa5f8..65a12a91 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
@@ -16,7 +16,6 @@
package net.taler.merchantlib
-import android.util.Log
import io.ktor.client.call.receive
import io.ktor.client.features.ClientRequestException
import io.ktor.client.features.ResponseException
@@ -32,7 +31,7 @@ class Response<out T> private constructor(
return try {
success(request())
} catch (e: Throwable) {
- Log.e("merchant-lib", "Error", e)
+ println(e)
failure(e)
}
}
diff --git a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
index ea5a12a1..deed81e1 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -113,6 +113,7 @@ class MerchantApiTest {
val unpaidResponse = CheckPaymentResponse.Unpaid(false, "http://taler.net/foo")
httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId") {
"""{
+ "order_status": "unpaid",
"paid": ${unpaidResponse.paid},
"taler_pay_uri": "${unpaidResponse.talerPayUri}"
}""".trimIndent()
diff --git a/merchant-terminal/.gitlab-ci.yml b/merchant-terminal/.gitlab-ci.yml
index 74ac21f1..d159902c 100644
--- a/merchant-terminal/.gitlab-ci.yml
+++ b/merchant-terminal/.gitlab-ci.yml
@@ -5,6 +5,7 @@ merchant_test:
- merchant-terminal/**/*
- merchant-lib/**/*
- taler-kotlin-common/**/*
+ - taler-kotlin-android/**/*
- build.gradle
script: ./gradlew :merchant-terminal:check :merchant-terminal:assembleRelease
artifacts:
diff --git a/settings.gradle b/settings.gradle
index 14d898dc..61758527 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1,7 @@
include ':cashier', ':merchant-terminal', ':wallet'
include ':taler-kotlin-common'
+include ':taler-kotlin-android'
include ':merchant-lib'
include ':anastasis-ui'
+
+enableFeaturePreview('GRADLE_METADATA')
diff --git a/taler-kotlin-android/.gitignore b/taler-kotlin-android/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/taler-kotlin-android/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/taler-kotlin-android/.gitlab-ci.yml b/taler-kotlin-android/.gitlab-ci.yml
new file mode 100644
index 00000000..bb5af21d
--- /dev/null
+++ b/taler-kotlin-android/.gitlab-ci.yml
@@ -0,0 +1,12 @@
+taler_kotlin_android_test:
+ stage: test
+ only:
+ changes:
+ - taler-kotlin-android/**/*
+ - taler-kotlin-common/**/*
+ - build.gradle
+ script: ./gradlew :taler-kotlin-android:check
+ artifacts:
+ paths:
+ - taler-kotlin-android/build/reports/lint-results.html
+ expire_in: 1 week
diff --git a/taler-kotlin-android/build.gradle b/taler-kotlin-android/build.gradle
new file mode 100644
index 00000000..d6d60037
--- /dev/null
+++ b/taler-kotlin-android/build.gradle
@@ -0,0 +1,78 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+plugins {
+ id 'com.android.library'
+ id 'kotlin-android'
+ id 'kotlin-android-extensions'
+ id 'kotlinx-serialization'
+}
+
+android {
+ compileSdkVersion 29
+ //noinspection GradleDependency
+ buildToolsVersion "$build_tools_version"
+
+ defaultConfig {
+ minSdkVersion 24
+ targetSdkVersion 29
+ versionCode 1
+ versionName "0.1"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ packagingOptions {
+ exclude("META-INF/*.kotlin_module")
+ }
+
+}
+
+dependencies {
+ api project(":taler-kotlin-common")
+
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.core:core-ktx:1.3.0'
+
+ // Navigation
+ implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
+ implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
+
+ // ViewModel and LiveData
+ def lifecycle_version = "2.2.0"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
+
+ // QR codes
+ implementation 'com.google.zxing:core:3.4.0' // needs minSdkVersion 24+
+
+ // JSON parsing and serialization
+ api "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
+ implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2"
+
+ lintChecks 'com.github.thirdegg:lint-rules:0.0.4-alpha'
+
+ testImplementation 'junit:junit:4.13'
+ testImplementation 'org.json:json:20190722'
+}
diff --git a/taler-kotlin-common/consumer-rules.pro b/taler-kotlin-android/consumer-rules.pro
index e69de29b..e69de29b 100644
--- a/taler-kotlin-common/consumer-rules.pro
+++ b/taler-kotlin-android/consumer-rules.pro
diff --git a/taler-kotlin-common/proguard-rules.pro b/taler-kotlin-android/proguard-rules.pro
index f1b42451..f1b42451 100644
--- a/taler-kotlin-common/proguard-rules.pro
+++ b/taler-kotlin-android/proguard-rules.pro
diff --git a/taler-kotlin-common/src/main/AndroidManifest.xml b/taler-kotlin-android/src/main/AndroidManifest.xml
index 902ddc13..902ddc13 100644
--- a/taler-kotlin-common/src/main/AndroidManifest.xml
+++ b/taler-kotlin-android/src/main/AndroidManifest.xml
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt b/taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt
new file mode 100644
index 00000000..f9b13308
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/AmountMixin.kt
@@ -0,0 +1,51 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.DeserializationContext
+import com.fasterxml.jackson.databind.JsonMappingException
+import com.fasterxml.jackson.databind.SerializerProvider
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import com.fasterxml.jackson.databind.annotation.JsonSerialize
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer
+import com.fasterxml.jackson.databind.ser.std.StdSerializer
+
+/**
+ * Used to support Jackson serialization along with KotlinX.
+ */
+@JsonSerialize(using = AmountSerializer::class)
+@JsonDeserialize(using = AmountDeserializer::class)
+abstract class AmountMixin
+
+class AmountSerializer : StdSerializer<Amount>(Amount::class.java) {
+ override fun serialize(value: Amount, gen: JsonGenerator, provider: SerializerProvider) {
+ gen.writeString(value.toJSONString())
+ }
+}
+
+class AmountDeserializer : StdDeserializer<Amount>(Amount::class.java) {
+ override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Amount {
+ val node = p.codec.readValue(p, String::class.java)
+ try {
+ return Amount.fromJSONString(node)
+ } catch (e: AmountParserException) {
+ throw JsonMappingException(p, "Error parsing Amount", e)
+ }
+ }
+}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
index b46f3062..b46f3062 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/ByteArrayUtils.kt b/taler-kotlin-android/src/main/java/net/taler/common/ByteArrayUtils.kt
index fba0d075..fba0d075 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/ByteArrayUtils.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/ByteArrayUtils.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt b/taler-kotlin-android/src/main/java/net/taler/common/CombinedLiveData.kt
index 4e7016bc..4e7016bc 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/CombinedLiveData.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
index b891ef70..0d5fe5bc 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
@@ -18,7 +18,6 @@ package net.taler.common
import androidx.annotation.RequiresApi
import com.fasterxml.jackson.annotation.JsonIgnore
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY
import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
@@ -28,13 +27,14 @@ import kotlinx.serialization.Serializable
import net.taler.common.TalerUtils.getLocalizedString
@Serializable
-@JsonIgnoreProperties(ignoreUnknown = true)
data class ContractTerms(
val summary: String,
@SerialName("summary_i18n")
+ @get:JsonProperty("summary_i18n")
val summaryI18n: Map<String, String>? = null,
val amount: Amount,
@SerialName("fulfillment_url")
+ @get:JsonProperty("fulfillment_url")
val fulfillmentUrl: String,
val products: List<ContractProduct>
)
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Event.kt b/taler-kotlin-android/src/main/java/net/taler/common/Event.kt
index 779247f6..779247f6 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/Event.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/Event.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/NfcManager.kt b/taler-kotlin-android/src/main/java/net/taler/common/NfcManager.kt
index 11e1e1e8..11e1e1e8 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/NfcManager.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/NfcManager.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/QrCodeManager.kt b/taler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt
index e2a9a55f..e2a9a55f 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/QrCodeManager.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt b/taler-kotlin-android/src/main/java/net/taler/common/SignedAmount.kt
index 03a0d6e7..03a0d6e7 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/SignedAmount.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/SignedAmount.kt
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt b/taler-kotlin-android/src/main/java/net/taler/common/TalerUtils.kt
index 444caa46..bb2e78ab 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/TalerUtils.kt
@@ -18,8 +18,7 @@ package net.taler.common
import androidx.annotation.RequiresApi
import androidx.core.os.LocaleListCompat
-import java.util.*
-import kotlin.collections.ArrayList
+import java.util.Locale
object TalerUtils {
diff --git a/taler-kotlin-common/src/main/res/drawable/selectable_background.xml b/taler-kotlin-android/src/main/res/drawable/selectable_background.xml
index 3c383a8c..3c383a8c 100644
--- a/taler-kotlin-common/src/main/res/drawable/selectable_background.xml
+++ b/taler-kotlin-android/src/main/res/drawable/selectable_background.xml
diff --git a/taler-kotlin-common/src/main/res/values-night/colors.xml b/taler-kotlin-android/src/main/res/values-night/colors.xml
index 10bdbb92..10bdbb92 100644
--- a/taler-kotlin-common/src/main/res/values-night/colors.xml
+++ b/taler-kotlin-android/src/main/res/values-night/colors.xml
diff --git a/taler-kotlin-common/src/main/res/values/colors.xml b/taler-kotlin-android/src/main/res/values/colors.xml
index c916442e..c916442e 100644
--- a/taler-kotlin-common/src/main/res/values/colors.xml
+++ b/taler-kotlin-android/src/main/res/values/colors.xml
diff --git a/taler-kotlin-common/src/main/res/values/strings.xml b/taler-kotlin-android/src/main/res/values/strings.xml
index a5b1df15..a5b1df15 100644
--- a/taler-kotlin-common/src/main/res/values/strings.xml
+++ b/taler-kotlin-android/src/main/res/values/strings.xml
diff --git a/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
new file mode 100644
index 00000000..79a75989
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
@@ -0,0 +1,74 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import com.fasterxml.jackson.module.kotlin.readValue
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ContractTermsTest {
+
+ private val mapper = ObjectMapper()
+ .registerModule(KotlinModule())
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .addMixIn(Amount::class.java, AmountMixin::class.java)
+
+ @Test
+ fun test() {
+ val json = """
+ {
+ "amount":"TESTKUDOS:0.5",
+ "extra":{
+ "article_name":"1._The_Free_Software_Definition"
+ },
+ "fulfillment_url":"https://shop.test.taler.net/essay/1._The_Free_Software_Definition",
+ "summary":"Essay: 1. The Free Software Definition",
+ "refund_deadline":{"t_ms":1596128414000},
+ "wire_transfer_deadline":{"t_ms":1596128564000},
+ "products":[],
+ "h_wire":"KV40K023N8EC1F5100TYNS23C4XN68Y1Z3PTJSWFGTMCNYD54KT4S791V2VQ91SZANN86VDAA369M4VEZ0KR6DN71EVRRZA71K681M0",
+ "wire_method":"x-taler-bank",
+ "order_id":"2020.212-01M9VKEAPF76C",
+ "timestamp":{"t_ms":1596128114000},
+ "pay_deadline":{"t_ms":"never"},
+ "max_wire_fee":"TESTKUDOS:1",
+ "max_fee":"TESTKUDOS:1",
+ "wire_fee_amortization":3,
+ "merchant_base_url":"https://backend.test.taler.net/instances/blog/",
+ "merchant":{"name":"Blog","instance":"blog"},
+ "exchanges":[
+ {
+ "url":"https://exchange.test.taler.net/",
+ "master_pub":"DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG"
+ },
+ {
+ "url":"https://exchange.test.taler.net/",
+ "master_pub":"DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG"}
+ ],
+ "auditors":[],
+ "merchant_pub":"8DR9NKSZY1CXFRE47NEYXM0K85C4ZGAYH7Y7VZ22GPNF0BRFNYNG",
+ "nonce":"FK8ZKJRV6VX6YFAG4CDSC6W0DWD084Q09DP81ANF30GRFQYM2KPG"
+ }
+ """.trimIndent()
+ val contractTerms: ContractTerms = mapper.readValue(json)
+ assertEquals("Essay: 1. The Free Software Definition", contractTerms.summary)
+ }
+
+}
diff --git a/taler-kotlin-common/.gitlab-ci.yml b/taler-kotlin-common/.gitlab-ci.yml
index 49d3e987..c241e313 100644
--- a/taler-kotlin-common/.gitlab-ci.yml
+++ b/taler-kotlin-common/.gitlab-ci.yml
@@ -4,8 +4,4 @@ taler_kotlin_common_test:
changes:
- taler-kotlin-common/**/*
- build.gradle
- script: ./gradlew :taler-kotlin-common:check
- artifacts:
- paths:
- - taler-kotlin-common/build/reports/lint-results.html
- expire_in: 1 week
+ script: ./gradlew :taler-kotlin-common:jvmTest
diff --git a/taler-kotlin-common/build.gradle b/taler-kotlin-common/build.gradle
index dd083b73..129881d2 100644
--- a/taler-kotlin-common/build.gradle
+++ b/taler-kotlin-common/build.gradle
@@ -1,72 +1,82 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
plugins {
- id 'com.android.library'
- id 'kotlin-android'
- id 'kotlin-android-extensions'
+ id 'org.jetbrains.kotlin.multiplatform'
id 'kotlinx-serialization'
}
-android {
- compileSdkVersion 29
- //noinspection GradleDependency
- buildToolsVersion "$build_tools_version"
+group 'net.taler'
+version '0.0.1'
- defaultConfig {
- minSdkVersion 24
- targetSdkVersion 29
- versionCode 1
- versionName "0.1"
+apply plugin: 'maven-publish'
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- consumerProguardFiles 'consumer-rules.pro'
+kotlin {
+ jvm()
+ // This is for iPhone simulator
+ // Switch here to iosArm64 (or iosArm32) to build library for iPhone device
+ iosX64("ios") {
+ binaries {
+ framework()
+ }
}
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ linuxX64("linux")
+ js {
+ browser {
+ }
+ nodejs {
+ }
+ }
+ sourceSets {
+ def serialization_version = "0.20.0"
+ commonMain {
+ dependencies {
+ implementation kotlin('stdlib-common')
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
+ }
+ }
+ commonTest {
+ dependencies {
+ implementation kotlin('test-common')
+ implementation kotlin('test-annotations-common')
+ }
+ }
+ jvmMain {
+ dependencies {
+ implementation kotlin('stdlib-jdk8')
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"
+ }
+ }
+ jvmTest {
+ dependencies {
+ implementation kotlin('test')
+ implementation kotlin('test-junit')
+ }
+ }
+ jsMain {
+ dependencies {
+ implementation kotlin('stdlib-js')
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serialization_version"
+ }
+ }
+ jsTest {
+ dependencies {
+ implementation kotlin('test-js')
+ }
+ }
+ nativeMain {
+ dependsOn commonMain
+ dependencies {
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"
+ }
+ }
+ nativeTest {
+ dependsOn commonTest
+ }
+ configure([targets.linux, targets.ios]) {
+ compilations.main.source(sourceSets.nativeMain)
+ compilations.test.source(sourceSets.nativeTest)
}
}
-
}
-dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.core:core-ktx:1.3.0'
-
- // Navigation
- implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
- implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
-
- // ViewModel and LiveData
- def lifecycle_version = "2.2.0"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
-
- // QR codes
- implementation 'com.google.zxing:core:3.4.0' // needs minSdkVersion 24+
-
- // JSON parsing and serialization
- api "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
- implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2"
-
- lintChecks 'com.github.thirdegg:lint-rules:0.0.4-alpha'
-
- testImplementation 'junit:junit:4.13'
- testImplementation 'org.json:json:20190722'
+configurations {
+ compileClasspath
}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
index 992f93bb..84d10c51 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt
+++ b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
@@ -16,23 +16,12 @@
package net.taler.common
-import android.annotation.SuppressLint
-import com.fasterxml.jackson.core.JsonGenerator
-import com.fasterxml.jackson.core.JsonParser
-import com.fasterxml.jackson.databind.DeserializationContext
-import com.fasterxml.jackson.databind.JsonMappingException
-import com.fasterxml.jackson.databind.SerializerProvider
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-import com.fasterxml.jackson.databind.annotation.JsonSerialize
-import com.fasterxml.jackson.databind.deser.std.StdDeserializer
-import com.fasterxml.jackson.databind.ser.std.StdSerializer
import kotlinx.serialization.Decoder
import kotlinx.serialization.Encoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
-import org.json.JSONObject
-import java.lang.Math.floorDiv
+import kotlin.math.floor
import kotlin.math.pow
import kotlin.math.roundToInt
@@ -40,8 +29,6 @@ class AmountParserException(msg: String? = null, cause: Throwable? = null) : Exc
class AmountOverflowException(msg: String? = null, cause: Throwable? = null) : Exception(msg, cause)
@Serializable(with = KotlinXAmountSerializer::class)
-@JsonSerialize(using = AmountSerializer::class)
-@JsonDeserialize(using = AmountDeserializer::class)
data class Amount(
/**
* name of the currency using either a three-character ISO 4217 currency code,
@@ -70,29 +57,21 @@ data class Amount(
private const val FRACTIONAL_BASE: Int = 100000000 // 1e8
- @Suppress("unused")
- private val REGEX = Regex("""^[-_*A-Za-z0-9]{1,12}:([0-9]+)\.?([0-9]+)?$""")
private val REGEX_CURRENCY = Regex("""^[-_*A-Za-z0-9]{1,12}$""")
- private val MAX_VALUE = 2.0.pow(52)
+ val MAX_VALUE = 2.0.pow(52).toLong()
private const val MAX_FRACTION_LENGTH = 8
- private const val MAX_FRACTION = 99_999_999
+ const val MAX_FRACTION = 99_999_999
- @Throws(AmountParserException::class)
- @SuppressLint("CheckedExceptions")
fun zero(currency: String): Amount {
return Amount(checkCurrency(currency), 0, 0)
}
- @Throws(AmountParserException::class)
- @SuppressLint("CheckedExceptions")
fun fromJSONString(str: String): Amount {
val split = str.split(":")
if (split.size != 2) throw AmountParserException("Invalid Amount Format")
return fromString(split[0], split[1])
}
- @Throws(AmountParserException::class)
- @SuppressLint("CheckedExceptions")
fun fromString(currency: String, str: String): Amount {
// value
val valueSplit = str.split(".")
@@ -110,31 +89,23 @@ data class Amount(
return Amount(checkCurrency(currency), value, fraction)
}
- @Throws(AmountParserException::class)
- @SuppressLint("CheckedExceptions")
- fun fromJsonObject(json: JSONObject): Amount {
- val currency = checkCurrency(json.optString("currency"))
- val value = checkValue(json.optString("value").toLongOrNull())
- val fraction = checkFraction(json.optString("fraction").toIntOrNull())
- return Amount(currency, value, fraction)
- }
+ fun min(currency: String): Amount = Amount(currency, 0, 1)
+ fun max(currency: String): Amount = Amount(currency, MAX_VALUE, MAX_FRACTION)
+
- @Throws(AmountParserException::class)
- private fun checkCurrency(currency: String): String {
+ internal fun checkCurrency(currency: String): String {
if (!REGEX_CURRENCY.matches(currency))
throw AmountParserException("Invalid currency: $currency")
return currency
}
- @Throws(AmountParserException::class)
- private fun checkValue(value: Long?): Long {
+ internal fun checkValue(value: Long?): Long {
if (value == null || value > MAX_VALUE)
throw AmountParserException("Value $value greater than $MAX_VALUE")
return value
}
- @Throws(AmountParserException::class)
- private fun checkFraction(fraction: Int?): Int {
+ internal fun checkFraction(fraction: Int?): Int {
if (fraction == null || fraction > MAX_FRACTION)
throw AmountParserException("Fraction $fraction greater than $MAX_FRACTION")
return fraction
@@ -153,25 +124,23 @@ data class Amount(
"$value.$fractionStr"
}
- @Throws(AmountOverflowException::class)
operator fun plus(other: Amount): Amount {
check(currency == other.currency) { "Can only subtract from same currency" }
- val resultValue = value + other.value + floorDiv(fraction + other.fraction, FRACTIONAL_BASE)
+ val resultValue = value + other.value + floor((fraction + other.fraction).toDouble() / FRACTIONAL_BASE).toLong()
if (resultValue > MAX_VALUE)
throw AmountOverflowException()
val resultFraction = (fraction + other.fraction) % FRACTIONAL_BASE
return Amount(currency, resultValue, resultFraction)
}
- @Throws(AmountOverflowException::class)
operator fun times(factor: Int): Amount {
+ // TODO consider replacing with a faster implementation
if (factor == 0) return zero(currency)
var result = this
for (i in 1 until factor) result += this
return result
}
- @Throws(AmountOverflowException::class)
operator fun minus(other: Amount): Amount {
check(currency == other.currency) { "Can only subtract from same currency" }
var resultValue = value
@@ -227,20 +196,3 @@ object KotlinXAmountSerializer: KSerializer<Amount> {
return Amount.fromJSONString(decoder.decodeString())
}
}
-
-class AmountSerializer : StdSerializer<Amount>(Amount::class.java) {
- override fun serialize(value: Amount, gen: JsonGenerator, provider: SerializerProvider) {
- gen.writeString(value.toJSONString())
- }
-}
-
-class AmountDeserializer : StdDeserializer<Amount>(Amount::class.java) {
- override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Amount {
- val node = p.codec.readValue(p, String::class.java)
- try {
- return Amount.fromJSONString(node)
- } catch (e: AmountParserException) {
- throw JsonMappingException(p, "Error parsing Amount", e)
- }
- }
-}
diff --git a/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt
new file mode 100644
index 00000000..962e0042
--- /dev/null
+++ b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt
@@ -0,0 +1,81 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import net.taler.common.Duration.Companion.FOREVER
+import kotlin.math.max
+
+expect fun nowMillis(): Long
+
+@Serializable
+data class Timestamp(
+ @SerialName("t_ms")
+ val ms: Long
+) : Comparable<Timestamp> {
+
+ companion object {
+ const val NEVER: Long = -1 // TODO or UINT64_MAX?
+ fun now(): Timestamp = Timestamp(nowMillis())
+ }
+
+ /**
+ * Returns a copy of this [Timestamp] rounded to seconds.
+ */
+ fun truncateSeconds(): Timestamp {
+ if (ms == NEVER) return Timestamp(ms)
+ return Timestamp((ms / 1000L) * 1000L)
+ }
+
+ operator fun minus(other: Timestamp): Duration = when {
+ ms == NEVER -> Duration(FOREVER)
+ other.ms == NEVER -> throw Error("Invalid argument for timestamp comparision")
+ ms < other.ms -> Duration(0)
+ else -> Duration(ms - other.ms)
+ }
+
+ operator fun minus(other: Duration): Timestamp = when {
+ ms == NEVER -> this
+ other.ms == FOREVER -> Timestamp(0)
+ else -> Timestamp(max(0, ms - other.ms))
+ }
+
+ override fun compareTo(other: Timestamp): Int {
+ return if (ms == NEVER) {
+ if (other.ms == NEVER) 0
+ else 1
+ } else {
+ if (other.ms == NEVER) -1
+ else ms.compareTo(other.ms)
+ }
+ }
+
+}
+
+@Serializable
+data class Duration(
+ /**
+ * Duration in milliseconds.
+ */
+ @SerialName("d_ms")
+ val ms: Long
+) {
+ companion object {
+ const val FOREVER: Long = -1 // TODO or UINT64_MAX?
+ }
+}
diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Version.kt b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt
index 87741153..87741153 100644
--- a/taler-kotlin-common/src/main/java/net/taler/common/Version.kt
+++ b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt
diff --git a/taler-kotlin-common/src/test/java/net/taler/common/AmountTest.kt b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/AmountTest.kt
index 97d9667b..e1843073 100644
--- a/taler-kotlin-common/src/test/java/net/taler/common/AmountTest.kt
+++ b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/AmountTest.kt
@@ -16,20 +16,26 @@
package net.taler.common
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.KotlinModule
-import com.fasterxml.jackson.module.kotlin.readValue
-import org.json.JSONObject
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
-import org.junit.Test
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import kotlin.test.fail
class AmountTest {
+ companion object {
+ fun getRandomAmount() = getRandomAmount(getRandomString(1, Random.nextInt(1, 12)))
+ fun getRandomAmount(currency: String): Amount {
+ val value = Random.nextLong(0, Amount.MAX_VALUE)
+ val fraction = Random.nextInt(0, Amount.MAX_FRACTION)
+ return Amount(currency, value, fraction)
+ }
+ }
+
@Test
- fun `test fromJSONString() works`() {
+ fun testFromJSONString() {
var str = "TESTKUDOS:23.42"
var amount = Amount.fromJSONString(str)
assertEquals(str, amount.toJSONString())
@@ -56,7 +62,7 @@ class AmountTest {
}
@Test
- fun `test fromJSONString() accepts max values, rejects above`() {
+ fun testFromJSONStringAcceptsMaxValuesRejectsAbove() {
val maxValue = 4503599627370496
val str = "TESTKUDOS123:$maxValue.99999999"
val amount = Amount.fromJSONString(str)
@@ -82,35 +88,7 @@ class AmountTest {
}
@Test
- fun `test JSON deserialization()`() {
- val mapper = ObjectMapper().registerModule(KotlinModule())
- var str = "TESTKUDOS:23.42"
- var amount: Amount = mapper.readValue("\"$str\"")
- assertEquals(str, amount.toJSONString())
- assertEquals("TESTKUDOS", amount.currency)
- assertEquals(23, amount.value)
- assertEquals((0.42 * 1e8).toInt(), amount.fraction)
- assertEquals("23.42 TESTKUDOS", amount.toString())
-
- str = "EUR:500000000.00000001"
- amount = mapper.readValue("\"$str\"")
- assertEquals(str, amount.toJSONString())
- assertEquals("EUR", amount.currency)
- assertEquals(500000000, amount.value)
- assertEquals(1, amount.fraction)
- assertEquals("500000000.00000001 EUR", amount.toString())
-
- str = "EUR:1500000000.00000003"
- amount = mapper.readValue("\"$str\"")
- assertEquals(str, amount.toJSONString())
- assertEquals("EUR", amount.currency)
- assertEquals(1500000000, amount.value)
- assertEquals(3, amount.fraction)
- assertEquals("1500000000.00000003 EUR", amount.toString())
- }
-
- @Test
- fun `test fromJSONString() rejections`() {
+ fun testFromJSONStringRejections() {
assertThrows<AmountParserException> {
Amount.fromJSONString("TESTKUDOS:0,5")
}
@@ -132,71 +110,7 @@ class AmountTest {
}
@Test
- fun `test fromJsonObject() works`() {
- val map = mapOf(
- "currency" to "TESTKUDOS",
- "value" to "23",
- "fraction" to "42000000"
- )
-
- val amount = Amount.fromJsonObject(JSONObject(map))
- assertEquals("TESTKUDOS:23.42", amount.toJSONString())
- assertEquals("TESTKUDOS", amount.currency)
- assertEquals(23, amount.value)
- assertEquals(42000000, amount.fraction)
- assertEquals("23.42 TESTKUDOS", amount.toString())
- }
-
- @Test
- fun `test fromJsonObject() accepts max values, rejects above`() {
- val maxValue = 4503599627370496
- val maxFraction = 99999999
- var map = mapOf(
- "currency" to "TESTKUDOS123",
- "value" to "$maxValue",
- "fraction" to "$maxFraction"
- )
-
- val amount = Amount.fromJsonObject(JSONObject(map))
- assertEquals("TESTKUDOS123:$maxValue.$maxFraction", amount.toJSONString())
- assertEquals("TESTKUDOS123", amount.currency)
- assertEquals(maxValue, amount.value)
- assertEquals(maxFraction, amount.fraction)
- assertEquals("$maxValue.$maxFraction TESTKUDOS123", amount.toString())
-
- // longer currency not accepted
- assertThrows<AmountParserException>("longer currency was accepted") {
- map = mapOf(
- "currency" to "TESTKUDOS1234",
- "value" to "$maxValue",
- "fraction" to "$maxFraction"
- )
- Amount.fromJsonObject(JSONObject(map))
- }
-
- // max value + 1 not accepted
- assertThrows<AmountParserException>("max value + 1 was accepted") {
- map = mapOf(
- "currency" to "TESTKUDOS123",
- "value" to "${maxValue + 1}",
- "fraction" to "$maxFraction"
- )
- Amount.fromJsonObject(JSONObject(map))
- }
-
- // max fraction + 1 not accepted
- assertThrows<AmountParserException>("max fraction + 1 was accepted") {
- map = mapOf(
- "currency" to "TESTKUDOS123",
- "value" to "$maxValue",
- "fraction" to "${maxFraction + 1}"
- )
- Amount.fromJsonObject(JSONObject(map))
- }
- }
-
- @Test
- fun `test addition`() {
+ fun testAddition() {
assertEquals(
Amount.fromJSONString("EUR:2"),
Amount.fromJSONString("EUR:1") + Amount.fromJSONString("EUR:1")
@@ -218,7 +132,7 @@ class AmountTest {
}
@Test
- fun `test times`() {
+ fun testTimes() {
assertEquals(
Amount.fromJSONString("EUR:2"),
Amount.fromJSONString("EUR:2") * 1
@@ -231,6 +145,12 @@ class AmountTest {
Amount.fromJSONString("EUR:4.5"),
Amount.fromJSONString("EUR:1.5") * 3
)
+ assertEquals(Amount.fromJSONString("EUR:0"), Amount.fromJSONString("EUR:1.11") * 0)
+ assertEquals(Amount.fromJSONString("EUR:1.11"), Amount.fromJSONString("EUR:1.11") * 1)
+ assertEquals(Amount.fromJSONString("EUR:2.22"), Amount.fromJSONString("EUR:1.11") * 2)
+ assertEquals(Amount.fromJSONString("EUR:3.33"), Amount.fromJSONString("EUR:1.11") * 3)
+ assertEquals(Amount.fromJSONString("EUR:4.44"), Amount.fromJSONString("EUR:1.11") * 4)
+ assertEquals(Amount.fromJSONString("EUR:5.55"), Amount.fromJSONString("EUR:1.11") * 5)
assertEquals(
Amount.fromJSONString("EUR:1500000000.00000003"),
Amount.fromJSONString("EUR:500000000.00000001") * 3
@@ -241,7 +161,7 @@ class AmountTest {
}
@Test
- fun `test subtraction`() {
+ fun testSubtraction() {
assertEquals(
Amount.fromJSONString("EUR:0"),
Amount.fromJSONString("EUR:1") - Amount.fromJSONString("EUR:1")
@@ -263,7 +183,7 @@ class AmountTest {
}
@Test
- fun `test isZero()`() {
+ fun testIsZero() {
assertTrue(Amount.zero("EUR").isZero())
assertTrue(Amount.fromJSONString("EUR:0").isZero())
assertTrue(Amount.fromJSONString("EUR:0.0").isZero())
@@ -276,14 +196,17 @@ class AmountTest {
}
@Test
- fun `test comparision`() {
+ fun testComparision() {
assertTrue(Amount.fromJSONString("EUR:0") <= Amount.fromJSONString("EUR:0"))
assertTrue(Amount.fromJSONString("EUR:0") <= Amount.fromJSONString("EUR:0.00000001"))
assertTrue(Amount.fromJSONString("EUR:0") < Amount.fromJSONString("EUR:0.00000001"))
assertTrue(Amount.fromJSONString("EUR:0") < Amount.fromJSONString("EUR:1"))
- assertTrue(Amount.fromJSONString("EUR:0") == Amount.fromJSONString("EUR:0"))
- assertTrue(Amount.fromJSONString("EUR:42") == Amount.fromJSONString("EUR:42"))
- assertTrue(Amount.fromJSONString("EUR:42.00000001") == Amount.fromJSONString("EUR:42.00000001"))
+ assertEquals(Amount.fromJSONString("EUR:0"), Amount.fromJSONString("EUR:0"))
+ assertEquals(Amount.fromJSONString("EUR:42"), Amount.fromJSONString("EUR:42"))
+ assertEquals(
+ Amount.fromJSONString("EUR:42.00000001"),
+ Amount.fromJSONString("EUR:42.00000001")
+ )
assertTrue(Amount.fromJSONString("EUR:42.00000001") >= Amount.fromJSONString("EUR:42.00000001"))
assertTrue(Amount.fromJSONString("EUR:42.00000002") >= Amount.fromJSONString("EUR:42.00000001"))
assertTrue(Amount.fromJSONString("EUR:42.00000002") > Amount.fromJSONString("EUR:42.00000001"))
diff --git a/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt
new file mode 100644
index 00000000..e3a6c17f
--- /dev/null
+++ b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/TestUtils.kt
@@ -0,0 +1,26 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlin.random.Random
+
+private val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
+fun getRandomString(minLength: Int = 1, maxLength: Int = Random.nextInt(0, 1337)) =
+ (minLength..maxLength)
+ .map { Random.nextInt(0, charPool.size) }
+ .map(charPool::get)
+ .joinToString("")
diff --git a/taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/VersionTest.kt
index 70f30eb2..f4f17eac 100644
--- a/taler-kotlin-common/src/test/java/net/taler/common/VersionTest.kt
+++ b/taler-kotlin-common/src/commonTest/kotlin/net/taler/common/VersionTest.kt
@@ -16,9 +16,9 @@
package net.taler.common
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
-import org.junit.Test
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
class VersionTest {
diff --git a/taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt b/taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt
new file mode 100644
index 00000000..b1140223
--- /dev/null
+++ b/taler-kotlin-common/src/jsMain/kotlin/net/taler/common/Time.kt
@@ -0,0 +1,23 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlin.js.Date
+
+actual fun nowMillis(): Long {
+ return Date().getMilliseconds().toLong()
+}
diff --git a/taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt b/taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt
new file mode 100644
index 00000000..6cd90404
--- /dev/null
+++ b/taler-kotlin-common/src/jvmMain/kotlin/net/taler/common/Time.kt
@@ -0,0 +1,21 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+actual fun nowMillis(): Long {
+ return System.currentTimeMillis()
+}
diff --git a/taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt b/taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt
new file mode 100644
index 00000000..8a4091ab
--- /dev/null
+++ b/taler-kotlin-common/src/nativeMain/kotlin/net/taler/common/Time.kt
@@ -0,0 +1,23 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlin.system.getTimeMillis
+
+actual fun nowMillis(): Long {
+ return getTimeMillis()
+}
diff --git a/wallet/.gitlab-ci.yml b/wallet/.gitlab-ci.yml
index 56768f7c..c417aa92 100644
--- a/wallet/.gitlab-ci.yml
+++ b/wallet/.gitlab-ci.yml
@@ -4,6 +4,7 @@ wallet_test:
changes:
- wallet/**/*
- taler-kotlin-common/**/*
+ - taler-kotlin-android/**/*
- build.gradle
script: ./gradlew :wallet:check :wallet:assembleRelease
artifacts:
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 8cca8dcb..17610188 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -23,7 +23,7 @@ plugins {
id "de.undercouch.download"
}
-def walletCoreVersion = "v0.7.1-dev.14"
+def walletCoreVersion = "v0.7.1-dev.16"
static def versionCodeEpoch() {
return (new Date().getTime() / 1000).toInteger()
@@ -47,7 +47,7 @@ android {
minSdkVersion 24
targetSdkVersion 29
versionCode 6
- versionName "0.7.1.dev.14"
+ versionName "0.7.1.dev.16"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "WALLET_CORE_VERSION", "\"$walletCoreVersion\""
}
@@ -83,6 +83,10 @@ android {
jvmTarget = "1.8"
}
+ packagingOptions {
+ exclude("META-INF/*.kotlin_module")
+ }
+
lintOptions {
abortOnError true
ignoreWarnings false
@@ -93,7 +97,7 @@ android {
}
dependencies {
- implementation project(":taler-kotlin-common")
+ implementation project(":taler-kotlin-android")
implementation project(":anastasis-ui")
implementation 'net.taler:akono:0.1'
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index 3d725d06..ffa2deaa 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -24,10 +24,13 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.viewModelScope
+import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
+import net.taler.common.Amount
+import net.taler.common.AmountMixin
import net.taler.common.Event
import net.taler.common.assertUiThread
import net.taler.common.toEvent
@@ -91,6 +94,8 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {
private val mapper = ObjectMapper()
.registerModule(KotlinModule())
.configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .addMixIn(Amount::class.java, AmountMixin::class.java)
+ .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
val withdrawManager = WithdrawManager(walletBackendApi, mapper)
val paymentManager = PaymentManager(walletBackendApi, mapper)
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
index ae90b98e..a026283b 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
@@ -18,7 +18,6 @@ package net.taler.wallet.exchanges
import net.taler.common.Amount
import net.taler.common.Timestamp
-import org.json.JSONObject
data class CoinFee(
val coin: Amount,
@@ -42,54 +41,4 @@ data class ExchangeFees(
val earliestDepositExpiration: Timestamp,
val coinFees: List<CoinFee>,
val wireFees: List<WireFee>
-) {
- companion object {
- fun fromExchangeWithdrawDetailsJson(json: JSONObject): ExchangeFees {
- val earliestDepositExpiration =
- json.getJSONObject("earliestDepositExpiration").getLong("t_ms")
- val selectedDenoms = json.getJSONObject("selectedDenoms")
- val denoms = selectedDenoms.getJSONArray("selectedDenoms")
- val coinFees = ArrayList<CoinFee>(denoms.length())
- for (i in 0 until denoms.length()) {
- val denom = denoms.getJSONObject(i)
- val d = denom.getJSONObject("denom")
- val coinFee = CoinFee(
- coin = Amount.fromJsonObject(d.getJSONObject("value")),
- quantity = denom.getInt("count"),
- feeDeposit = Amount.fromJsonObject(d.getJSONObject("feeDeposit")),
- feeRefresh = Amount.fromJsonObject(d.getJSONObject("feeRefresh")),
- feeRefund = Amount.fromJsonObject(d.getJSONObject("feeRefund")),
- feeWithdraw = Amount.fromJsonObject(d.getJSONObject("feeWithdraw"))
- )
- coinFees.add(coinFee)
- }
-
- val wireFeesJson = json.getJSONObject("wireFees")
- val feesForType = wireFeesJson.getJSONObject("feesForType")
- val bankFees = feesForType.getJSONArray("x-taler-bank")
- val wireFees = ArrayList<WireFee>(bankFees.length())
- for (i in 0 until bankFees.length()) {
- val fee = bankFees.getJSONObject(i)
- val startStamp =
- fee.getJSONObject("startStamp").getLong("t_ms")
- val endStamp =
- fee.getJSONObject("endStamp").getLong("t_ms")
- val wireFee = WireFee(
- start = Timestamp(startStamp),
- end = Timestamp(endStamp),
- wireFee = Amount.fromJsonObject(fee.getJSONObject("wireFee")),
- closingFee = Amount.fromJsonObject(fee.getJSONObject("closingFee"))
- )
- wireFees.add(wireFee)
- }
-
- return ExchangeFees(
- withdrawFee = Amount.fromJsonObject(json.getJSONObject("withdrawFee")),
- overhead = Amount.fromJsonObject(json.getJSONObject("overhead")),
- earliestDepositExpiration = Timestamp(earliestDepositExpiration),
- coinFees = coinFees,
- wireFees = wireFees
- )
- }
- }
-}
+)
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
index 4c5b0105..d2f8e6cc 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
@@ -16,23 +16,12 @@
package net.taler.wallet.payment
-import com.fasterxml.jackson.annotation.JsonSubTypes
-import com.fasterxml.jackson.annotation.JsonSubTypes.Type
import com.fasterxml.jackson.annotation.JsonTypeInfo
-import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME
import com.fasterxml.jackson.annotation.JsonTypeName
import net.taler.common.ContractTerms
-import net.taler.wallet.payment.PreparePayResponse.AlreadyConfirmedResponse
-import net.taler.wallet.payment.PreparePayResponse.InsufficientBalanceResponse
-import net.taler.wallet.payment.PreparePayResponse.PaymentPossibleResponse
-@JsonTypeInfo(use = NAME, include = PROPERTY, property = "status")
-@JsonSubTypes(
- Type(value = PaymentPossibleResponse::class, name = "payment-possible"),
- Type(value = AlreadyConfirmedResponse::class, name = "already-confirmed"),
- Type(value = InsufficientBalanceResponse::class, name = "insufficient-balance")
-)
+@JsonTypeInfo(use = NAME, property = "status")
sealed class PreparePayResponse(open val proposalId: String) {
@JsonTypeName("payment-possible")
data class PaymentPossibleResponse(