From 82b8b57dc16112b859150696199774fcf06655e1 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 18 Mar 2020 17:24:02 -0300 Subject: Factor out code from merchant-terminal into common library --- taler-kotlin-common/build.gradle | 7 +++ .../src/main/java/net/taler/common/Amount.kt | 21 +++++++- .../src/main/java/net/taler/common/AndroidUtils.kt | 48 +++++++++++++---- .../main/java/net/taler/common/CombinedLiveData.kt | 51 ++++++++++++++++++ .../main/java/net/taler/common/ContractTerms.kt | 61 ++++++++++++++++++++++ .../src/main/java/net/taler/common/TalerUtils.kt | 51 ++++++++++++++++++ 6 files changed, 228 insertions(+), 11 deletions(-) create mode 100644 taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt create mode 100644 taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt create mode 100644 taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt (limited to 'taler-kotlin-common') diff --git a/taler-kotlin-common/build.gradle b/taler-kotlin-common/build.gradle index d7c9362..1d45a54 100644 --- a/taler-kotlin-common/build.gradle +++ b/taler-kotlin-common/build.gradle @@ -47,10 +47,17 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core-ktx:1.2.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 + implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2" } diff --git a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt b/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt index 428ddef..0389db1 100644 --- a/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt +++ b/taler-kotlin-common/src/main/java/net/taler/common/Amount.kt @@ -16,13 +16,14 @@ package net.taler.common +import org.json.JSONObject + data class Amount(val currency: String, val amount: String) { companion object { - + private const val FRACTIONAL_BASE = 1e8 private val SIGNED_REGEX = Regex("""([+\-])(\w+):([0-9.]+)""") - @Suppress("unused") fun fromString(strAmount: String): Amount { val components = strAmount.split(":") return Amount(components[0], components[1]) @@ -38,6 +39,22 @@ data class Amount(val currency: String, val amount: String) { // only display as many digits as required to precisely render the balance return Amount(currency, amountStr.removeSuffix(".0")) } + + fun fromJson(jsonAmount: JSONObject): Amount { + val amountCurrency = jsonAmount.getString("currency") + val amountValue = jsonAmount.getString("value") + val amountFraction = jsonAmount.getString("fraction") + val amountIntValue = Integer.parseInt(amountValue) + val amountIntFraction = Integer.parseInt(amountFraction) + return Amount( + amountCurrency, + (amountIntValue + amountIntFraction / FRACTIONAL_BASE).toString() + ) + } + } + + fun isZero(): Boolean { + return amount.toDouble() == 0.0 } override fun toString() = "$amount $currency" diff --git a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt index 2fafdf2..fc04da5 100644 --- a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt +++ b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt @@ -19,35 +19,65 @@ package net.taler.common import android.content.Context import android.content.Context.CONNECTIVITY_SERVICE import android.net.ConnectivityManager -import android.net.NetworkCapabilities -import android.os.Build +import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET +import android.os.Build.VERSION.SDK_INT +import android.text.format.DateUtils +import android.text.format.DateUtils.DAY_IN_MILLIS +import android.text.format.DateUtils.FORMAT_ABBREV_MONTH +import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE +import android.text.format.DateUtils.FORMAT_NO_YEAR +import android.text.format.DateUtils.FORMAT_SHOW_DATE +import android.text.format.DateUtils.FORMAT_SHOW_TIME +import android.text.format.DateUtils.MINUTE_IN_MILLIS import android.view.View +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import androidx.fragment.app.Fragment +import androidx.navigation.NavDirections +import androidx.navigation.fragment.findNavController fun View.fadeIn(endAction: () -> Unit = {}) { - if (visibility == View.VISIBLE) return + if (visibility == VISIBLE) return alpha = 0f - visibility = View.VISIBLE + visibility = VISIBLE animate().alpha(1f).withEndAction { - endAction.invoke() + if (context != null) endAction.invoke() }.start() } fun View.fadeOut(endAction: () -> Unit = {}) { - if (visibility == View.INVISIBLE) return + if (visibility == INVISIBLE) return animate().alpha(0f).withEndAction { - visibility = View.INVISIBLE + if (context == null) return@withEndAction + visibility = INVISIBLE alpha = 1f endAction.invoke() }.start() } +/** + * Use this with 'when' expressions when you need it to handle all possibilities/branches. + */ +val T.exhaustive: T + get() = this + fun Context.isOnline(): Boolean { val cm = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - return if (Build.VERSION.SDK_INT < 29) { + return if (SDK_INT < 29) { @Suppress("DEPRECATION") cm.activeNetworkInfo?.isConnected == true } else { val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + capabilities.hasCapability(NET_CAPABILITY_INTERNET) } } + +fun Fragment.navigate(directions: NavDirections) = findNavController().navigate(directions) + +fun Long.toRelativeTime(context: Context): CharSequence { + val now = System.currentTimeMillis() + return if (now - this > DAY_IN_MILLIS * 2) { + val flags = FORMAT_SHOW_TIME or FORMAT_SHOW_DATE or FORMAT_ABBREV_MONTH or FORMAT_NO_YEAR + DateUtils.formatDateTime(context, this, flags) + } else DateUtils.getRelativeTimeSpanString(this, now, MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE) +} diff --git a/taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt b/taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt new file mode 100644 index 0000000..4e7016b --- /dev/null +++ b/taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.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 + */ + +package net.taler.common + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.Observer + +class CombinedLiveData( + source1: LiveData, + source2: LiveData, + private val combine: (data1: T?, data2: K?) -> S +) : MediatorLiveData() { + + private var data1: T? = null + private var data2: K? = null + + init { + super.addSource(source1) { t -> + data1 = t + value = combine(data1, data2) + } + super.addSource(source2) { k -> + data2 = k + value = combine(data1, data2) + } + } + + override fun addSource(source: LiveData, onChanged: Observer) { + throw UnsupportedOperationException() + } + + override fun removeSource(toRemote: LiveData) { + throw UnsupportedOperationException() + } + +} diff --git a/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt b/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt new file mode 100644 index 0000000..1e70b6f --- /dev/null +++ b/taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt @@ -0,0 +1,61 @@ +/* + * 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 + */ + +package net.taler.common + +import androidx.annotation.RequiresApi +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY +import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL +import com.fasterxml.jackson.annotation.JsonProperty +import net.taler.common.TalerUtils.getLocalizedString + +@JsonInclude(NON_NULL) +abstract class Product { + @get:JsonProperty("product_id") + abstract val productId: String? + abstract val description: String + + @get:JsonProperty("description_i18n") + abstract val descriptionI18n: Map? + abstract val price: String + + @get:JsonProperty("delivery_location") + abstract val location: String? + abstract val image: String? + + @get:JsonIgnore + val localizedDescription: String + @RequiresApi(26) + get() = getLocalizedString(descriptionI18n, description) +} + +data class ContractProduct( + override val productId: String?, + override val description: String, + override val descriptionI18n: Map?, + override val price: String, + override val location: String?, + override val image: String?, + val quantity: Int +) : Product() + +@JsonInclude(NON_EMPTY) +class Timestamp( + @JsonProperty("t_ms") + val ms: Long +) diff --git a/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt b/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt new file mode 100644 index 0000000..cb1622e --- /dev/null +++ b/taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.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 + */ + +package net.taler.common + +import androidx.annotation.RequiresApi +import androidx.core.os.LocaleListCompat +import java.util.* +import kotlin.collections.ArrayList + +object TalerUtils { + + @RequiresApi(26) + fun getLocalizedString(map: Map?, default: String): String { + // just return the default, if it is the only element + if (map == null) return default + // create a priority list of language ranges from system locales + val locales = LocaleListCompat.getDefault() + val priorityList = ArrayList(locales.size()) + for (i in 0 until locales.size()) { + priorityList.add(Locale.LanguageRange(locales[i].toLanguageTag())) + } + // create a list of locales available in the given map + val availableLocales = map.keys.mapNotNull { + if (it == "_") return@mapNotNull null + val list = it.split("_") + when (list.size) { + 1 -> Locale(list[0]) + 2 -> Locale(list[0], list[1]) + 3 -> Locale(list[0], list[1], list[2]) + else -> null + } + } + val match = Locale.lookup(priorityList, availableLocales) + return match?.toString()?.let { map[it] } ?: default + } + +} -- cgit v1.2.3