diff options
51 files changed, 551 insertions, 326 deletions
@@ -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 48f1aec..6dc4426 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 581abbf..01ed15f 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 0391c7c..ff0eec5 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 76f687e..442d232 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 6a7baed..6b73dee 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 0d06c60..641a039 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 c8d9a3b..a4fd35e 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 62a7516..8f7c7c2 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 128f4c1..33e8379 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 9aefa5f..65a12a9 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 ea5a12a..deed81e 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 74ac21f..d159902 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 14d898d..6175852 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 0000000..796b96d --- /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 0000000..bb5af21 --- /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 0000000..d6d6003 --- /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 e69de29..e69de29 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 f1b4245..f1b4245 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 902ddc1..902ddc1 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 0000000..f9b1330 --- /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 b46f306..b46f306 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 fba0d07..fba0d07 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 4e7016b..4e7016b 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 b891ef7..0d5fe5b 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 779247f..779247f 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 11e1e1e..11e1e1e 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 e2a9a55..e2a9a55 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 03a0d6e..03a0d6e 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 444caa4..bb2e78a 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 3c383a8..3c383a8 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 10bdbb9..10bdbb9 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 c916442..c916442 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 a5b1df1..a5b1df1 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 0000000..79a7598 --- /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 49d3e98..c241e31 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 dd083b7..129881d 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 992f93b..84d10c5 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 0000000..962e004 --- /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 8774115..8774115 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 97d9667..e184307 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 0000000..e3a6c17 --- /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 70f30eb..f4f17ea 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 0000000..b114022 --- /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 0000000..6cd9040 --- /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 0000000..8a4091a --- /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 56768f7..c417aa9 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 8cca8dc..1761018 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 3d725d0..ffa2dea 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 ae90b98..a026283 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 4c5b010..d2f8e6c 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( |