aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--merchant-terminal/.gitlab-ci.yml4
-rw-r--r--merchant-terminal/build.gradle18
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/Amount.kt48
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt1
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/NfcManager.kt233
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/QrCodeManager.kt42
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt121
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt7
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt45
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt5
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt11
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt6
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt6
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt4
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt1
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt4
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt (renamed from merchant-terminal/src/main/java/net/taler/merchantpos/order/Definitions.kt)101
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt9
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt4
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt5
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt1
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt3
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt12
-rw-r--r--taler-kotlin-common/build.gradle7
-rw-r--r--taler-kotlin-common/src/main/java/net/taler/common/Amount.kt21
-rw-r--r--taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt48
-rw-r--r--taler-kotlin-common/src/main/java/net/taler/common/CombinedLiveData.kt51
-rw-r--r--taler-kotlin-common/src/main/java/net/taler/common/ContractTerms.kt61
-rw-r--r--taler-kotlin-common/src/main/java/net/taler/common/TalerUtils.kt51
29 files changed, 321 insertions, 609 deletions
diff --git a/merchant-terminal/.gitlab-ci.yml b/merchant-terminal/.gitlab-ci.yml
index 467824c..9bd889b 100644
--- a/merchant-terminal/.gitlab-ci.yml
+++ b/merchant-terminal/.gitlab-ci.yml
@@ -2,7 +2,7 @@ merchant_test:
stage: test
only:
changes:
- - "merchant-terminal"
+ - merchant-terminal/**/*
script: ./gradlew :merchant-terminal:lint :merchant-terminal:assembleRelease
artifacts:
paths:
@@ -15,7 +15,7 @@ merchant_deploy_nightly:
refs:
- master
changes:
- - "merchant-terminal"
+ - merchant-terminal/**/*
needs: ["merchant_test"]
script:
# Ensure that key exists
diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle
index 594cab3..dd2527e 100644
--- a/merchant-terminal/build.gradle
+++ b/merchant-terminal/build.gradle
@@ -5,7 +5,9 @@ apply plugin: "androidx.navigation.safeargs.kotlin"
android {
compileSdkVersion 29
- buildToolsVersion "29.0.3"
+ //noinspection GradleDependency
+ buildToolsVersion "$build_tools_version"
+
defaultConfig {
applicationId "net.taler.merchantpos"
minSdkVersion 26
@@ -14,6 +16,7 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
+
buildTypes {
release {
minifyEnabled true
@@ -43,9 +46,8 @@ android {
}
dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.core:core-ktx:1.2.0'
+ implementation project(":taler-kotlin-common")
+
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "androidx.recyclerview:recyclerview:1.1.0"
@@ -55,17 +57,9 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
- // ViewModel and LiveData
- def lifecycle_version = "2.2.0"
- implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
- implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
-
// HTTP Requests
implementation 'com.android.volley:volley:1.1.1'
- // QR codes
- implementation 'com.google.zxing:core:3.4.0'
-
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
// JSON parsing and serialization
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/Amount.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/Amount.kt
deleted file mode 100644
index 17ddd61..0000000
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/Amount.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.merchantpos
-
-import org.json.JSONObject
-
-data class Amount(val currency: String, val amount: String) {
- @Suppress("unused")
- fun isZero(): Boolean {
- return amount.toDouble() == 0.0
- }
-
- companion object {
- private const val FRACTIONAL_BASE = 1e8
-
- @Suppress("unused")
- 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 fromString(strAmount: String): Amount {
- val components = strAmount.split(":")
- return Amount(components[0], components[1])
- }
- }
-}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
index 0c6bdfa..d6e3747 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
@@ -36,6 +36,7 @@ import androidx.navigation.ui.setupWithNavController
import com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_main.*
+import net.taler.common.NfcManager
class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/NfcManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/NfcManager.kt
deleted file mode 100644
index 09c1470..0000000
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/NfcManager.kt
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * 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.merchantpos
-
-import android.app.Activity
-import android.content.Context
-import android.nfc.NfcAdapter
-import android.nfc.NfcAdapter.FLAG_READER_NFC_A
-import android.nfc.NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
-import android.nfc.Tag
-import android.nfc.tech.IsoDep
-import android.util.Log
-import net.taler.merchantpos.Utils.hexStringToByteArray
-import org.json.JSONObject
-import java.io.ByteArrayOutputStream
-import java.net.URL
-import javax.net.ssl.HttpsURLConnection
-
-@Suppress("unused")
-private const val TALER_AID = "A0000002471001"
-
-class NfcManager : NfcAdapter.ReaderCallback {
-
- companion object {
- const val TAG = "taler-merchant"
-
- /**
- * Returns true if NFC is supported and false otherwise.
- */
- fun hasNfc(context: Context): Boolean {
- return getNfcAdapter(context) != null
- }
-
- /**
- * Enables NFC reader mode. Don't forget to call [stop] afterwards.
- */
- fun start(activity: Activity, nfcManager: NfcManager) {
- getNfcAdapter(activity)?.enableReaderMode(activity, nfcManager, nfcManager.flags, null)
- }
-
- /**
- * Disables NFC reader mode. Call after [start].
- */
- fun stop(activity: Activity) {
- getNfcAdapter(activity)?.disableReaderMode(activity)
- }
-
- private fun getNfcAdapter(context: Context): NfcAdapter? {
- return NfcAdapter.getDefaultAdapter(context)
- }
- }
-
- private val flags = FLAG_READER_NFC_A or FLAG_READER_SKIP_NDEF_CHECK
-
- private var tagString: String? = null
- private var currentTag: IsoDep? = null
-
- fun setTagString(tagString: String) {
- this.tagString = tagString
- }
-
- override fun onTagDiscovered(tag: Tag?) {
-
- Log.v(TAG, "tag discovered")
-
- val isoDep = IsoDep.get(tag)
- isoDep.connect()
-
- currentTag = isoDep
-
- isoDep.transceive(apduSelectFile())
-
- val tagString: String? = tagString
- if (tagString != null) {
- isoDep.transceive(apduPutTalerData(1, tagString.toByteArray()))
- }
-
- // FIXME: use better pattern for sleeps in between requests
- // -> start with fast polling, poll more slowly if no requests are coming
-
- while (true) {
- try {
- val reqFrame = isoDep.transceive(apduGetData())
- if (reqFrame.size < 2) {
- Log.v(TAG, "request frame too small")
- break
- }
- val req = ByteArray(reqFrame.size - 2)
- if (req.isEmpty()) {
- continue
- }
- reqFrame.copyInto(req, 0, 0, reqFrame.size - 2)
- val jsonReq = JSONObject(req.toString(Charsets.UTF_8))
- val reqId = jsonReq.getInt("id")
- Log.v(TAG, "got request $jsonReq")
- val jsonInnerReq = jsonReq.getJSONObject("request")
- val method = jsonInnerReq.getString("method")
- val urlStr = jsonInnerReq.getString("url")
- Log.v(TAG, "url '$urlStr'")
- Log.v(TAG, "method '$method'")
- val url = URL(urlStr)
- val conn: HttpsURLConnection = url.openConnection() as HttpsURLConnection
- conn.setRequestProperty("Accept", "application/json")
- conn.connectTimeout = 5000
- conn.doInput = true
- when (method) {
- "get" -> {
- conn.requestMethod = "GET"
- }
- "postJson" -> {
- conn.requestMethod = "POST"
- conn.doOutput = true
- conn.setRequestProperty("Content-Type", "application/json; utf-8")
- val body = jsonInnerReq.getString("body")
- conn.outputStream.write(body.toByteArray(Charsets.UTF_8))
- }
- else -> {
- throw Exception("method not supported")
- }
- }
- Log.v(TAG, "connecting")
- conn.connect()
- Log.v(TAG, "connected")
-
- val statusCode = conn.responseCode
- val tunnelResp = JSONObject()
- tunnelResp.put("id", reqId)
- tunnelResp.put("status", conn.responseCode)
-
- if (statusCode == 200) {
- val stream = conn.inputStream
- val httpResp = stream.buffered().readBytes()
- tunnelResp.put("responseJson", JSONObject(httpResp.toString(Charsets.UTF_8)))
- }
-
- Log.v(TAG, "sending: $tunnelResp")
-
- isoDep.transceive(apduPutTalerData(2, tunnelResp.toString().toByteArray()))
- } catch (e: Exception) {
- Log.v(TAG, "exception during NFC loop: $e")
- break
- }
- }
-
- isoDep.close()
- }
-
- private fun writeApduLength(stream: ByteArrayOutputStream, size: Int) {
- when {
- size == 0 -> {
- // No size field needed!
- }
- size <= 255 -> // One byte size field
- stream.write(size)
- size <= 65535 -> {
- stream.write(0)
- // FIXME: is this supposed to be little or big endian?
- stream.write(size and 0xFF)
- stream.write((size ushr 8) and 0xFF)
- }
- else -> throw Error("payload too big")
- }
- }
-
- private fun apduSelectFile(): ByteArray {
- return hexStringToByteArray("00A4040007A0000002471001")
- }
-
- private fun apduPutData(payload: ByteArray): ByteArray {
- val stream = ByteArrayOutputStream()
-
- // Class
- stream.write(0x00)
-
- // Instruction 0xDA = put data
- stream.write(0xDA)
-
- // Instruction parameters
- // (proprietary encoding)
- stream.write(0x01)
- stream.write(0x00)
-
- writeApduLength(stream, payload.size)
-
- stream.write(payload)
-
- return stream.toByteArray()
- }
-
- private fun apduPutTalerData(talerInst: Int, payload: ByteArray): ByteArray {
- val realPayload = ByteArrayOutputStream()
- realPayload.write(talerInst)
- realPayload.write(payload)
- return apduPutData(realPayload.toByteArray())
- }
-
- private fun apduGetData(): ByteArray {
- val stream = ByteArrayOutputStream()
-
- // Class
- stream.write(0x00)
-
- // Instruction 0xCA = get data
- stream.write(0xCA)
-
- // Instruction parameters
- // (proprietary encoding)
- stream.write(0x01)
- stream.write(0x00)
-
- // Max expected response size, two
- // zero bytes denotes 65536
- stream.write(0x0)
- stream.write(0x0)
-
- return stream.toByteArray()
- }
-
-}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/QrCodeManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/QrCodeManager.kt
deleted file mode 100644
index 595e7ac..0000000
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/QrCodeManager.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.merchantpos
-
-import android.graphics.Bitmap
-import android.graphics.Bitmap.Config.RGB_565
-import android.graphics.Color.BLACK
-import android.graphics.Color.WHITE
-import com.google.zxing.BarcodeFormat.QR_CODE
-import com.google.zxing.qrcode.QRCodeWriter
-
-object QrCodeManager {
-
- fun makeQrCode(text: String, size: Int = 256): Bitmap {
- val qrCodeWriter = QRCodeWriter()
- val bitMatrix = qrCodeWriter.encode(text, QR_CODE, size, size)
- val height = bitMatrix.height
- val width = bitMatrix.width
- val bmp = Bitmap.createBitmap(width, height, RGB_565)
- for (x in 0 until width) {
- for (y in 0 until height) {
- bmp.setPixel(x, y, if (bitMatrix.get(x, y)) BLACK else WHITE)
- }
- }
- return bmp
- }
-
-}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
index a0c30d6..578debf 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/Utils.kt
@@ -16,86 +16,12 @@
package net.taler.merchantpos
-import android.content.Context
-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.text.format.DateUtils.formatDateTime
-import android.text.format.DateUtils.getRelativeTimeSpanString
import android.view.View
-import android.view.View.INVISIBLE
-import android.view.View.VISIBLE
import androidx.annotation.StringRes
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MediatorLiveData
-import androidx.lifecycle.Observer
-import androidx.navigation.NavController
-import androidx.navigation.NavDirections
-import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.BaseTransientBottomBar.ANIMATION_MODE_FADE
import com.google.android.material.snackbar.BaseTransientBottomBar.Duration
import com.google.android.material.snackbar.Snackbar.make
-object Utils {
-
- private const val HEX_CHARS = "0123456789ABCDEF"
-
- fun hexStringToByteArray(data: String): ByteArray {
- val result = ByteArray(data.length / 2)
-
- for (i in data.indices step 2) {
- val firstIndex = HEX_CHARS.indexOf(data[i])
- val secondIndex = HEX_CHARS.indexOf(data[i + 1])
-
- val octet = firstIndex.shl(4).or(secondIndex)
- result[i.shr(1)] = octet.toByte()
- }
- return result
- }
-
-
- private val HEX_CHARS_ARRAY = HEX_CHARS.toCharArray()
-
- @Suppress("unused")
- fun toHex(byteArray: ByteArray): String {
- val result = StringBuffer()
-
- byteArray.forEach {
- val octet = it.toInt()
- val firstIndex = (octet and 0xF0).ushr(4)
- val secondIndex = octet and 0x0F
- result.append(HEX_CHARS_ARRAY[firstIndex])
- result.append(HEX_CHARS_ARRAY[secondIndex])
- }
- return result.toString()
- }
-
-}
-
-fun View.fadeIn(endAction: () -> Unit = {}) {
- if (visibility == VISIBLE) return
- alpha = 0f
- visibility = VISIBLE
- animate().alpha(1f).withEndAction {
- if (context != null) endAction.invoke()
- }.start()
-}
-
-fun View.fadeOut(endAction: () -> Unit = {}) {
- if (visibility == INVISIBLE) return
- animate().alpha(0f).withEndAction {
- if (context == null) return@withEndAction
- visibility = INVISIBLE
- alpha = 1f
- endAction.invoke()
- }.start()
-}
-
fun topSnackbar(view: View, text: CharSequence, @Duration duration: Int) {
make(view, text, duration)
.setAnimationMode(ANIMATION_MODE_FADE)
@@ -106,50 +32,3 @@ fun topSnackbar(view: View, text: CharSequence, @Duration duration: Int) {
fun topSnackbar(view: View, @StringRes resId: Int, @Duration duration: Int) {
topSnackbar(view, view.resources.getText(resId), duration)
}
-
-fun NavDirections.navigate(nav: NavController) = nav.navigate(this)
-
-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
- formatDateTime(context, this, flags)
- } else getRelativeTimeSpanString(this, now, MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE)
-}
-
-class CombinedLiveData<T, K, S>(
- source1: LiveData<T>,
- source2: LiveData<K>,
- private val combine: (data1: T?, data2: K?) -> S
-) : MediatorLiveData<S>() {
-
- 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 <S : Any?> addSource(source: LiveData<S>, onChanged: Observer<in S>) {
- throw UnsupportedOperationException()
- }
-
- override fun <T : Any?> removeSource(toRemote: LiveData<T>) {
- throw UnsupportedOperationException()
- }
-}
-
-/**
- * Use this with 'when' expressions when you need it to handle all possibilities/branches.
- */
-val <T> T.exhaustive: T
- get() = this
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
index c370e33..c0c87dc 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
@@ -23,14 +23,13 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
-import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
+import net.taler.common.navigate
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
import net.taler.merchantpos.config.ConfigFetcherFragmentDirections.Companion.actionConfigFetcherToMerchantSettings
import net.taler.merchantpos.config.ConfigFetcherFragmentDirections.Companion.actionConfigFetcherToOrder
-import net.taler.merchantpos.navigate
class ConfigFetcherFragment : Fragment() {
@@ -52,7 +51,7 @@ class ConfigFetcherFragment : Fragment() {
null -> return@Observer
is ConfigUpdateResult.Error -> onNetworkError(result.msg)
is ConfigUpdateResult.Success -> {
- actionConfigFetcherToOrder().navigate(findNavController())
+ navigate(actionConfigFetcherToOrder())
}
}
})
@@ -60,7 +59,7 @@ class ConfigFetcherFragment : Fragment() {
private fun onNetworkError(msg: String) {
Snackbar.make(view!!, msg, LENGTH_SHORT).show()
- actionConfigFetcherToMerchantSettings().navigate(findNavController())
+ navigate(actionConfigFetcherToMerchantSettings())
}
}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
index 2050e28..8141f0f 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfig.kt
@@ -17,7 +17,13 @@
package net.taler.merchantpos.config
import android.net.Uri
+import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
+import net.taler.common.Amount
+import net.taler.common.ContractProduct
+import net.taler.common.Product
+import net.taler.common.TalerUtils
+import java.util.*
data class Config(
val configUrl: String,
@@ -45,3 +51,42 @@ data class MerchantConfig(
return uriBuilder.toString()
}
}
+
+data class Category(
+ val id: Int,
+ val name: String,
+ @JsonProperty("name_i18n")
+ val nameI18n: Map<String, String>?
+) {
+ var selected: Boolean = false
+ val localizedName: String get() = TalerUtils.getLocalizedString(nameI18n, name)
+}
+
+data class ConfigProduct(
+ @JsonIgnore
+ val id: String = UUID.randomUUID().toString(),
+ override val productId: String?,
+ override val description: String,
+ override val descriptionI18n: Map<String, String>?,
+ override val price: String,
+ override val location: String?,
+ override val image: String?,
+ val categories: List<Int>,
+ @JsonIgnore
+ val quantity: Int = 0
+) : Product() {
+ val priceAsDouble by lazy { Amount.fromString(price).amount.toDouble() }
+
+ fun toContractProduct() = ContractProduct(
+ productId = productId,
+ description = description,
+ descriptionI18n = descriptionI18n,
+ price = price,
+ location = location,
+ image = image,
+ quantity = quantity
+ )
+
+ override fun equals(other: Any?) = other is ConfigProduct && id == other.id
+ override fun hashCode() = id.hashCode()
+}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
index aad1c93..a584af8 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/MerchantConfigFragment.kt
@@ -28,14 +28,13 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
-import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_merchant_config.*
+import net.taler.common.navigate
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
import net.taler.merchantpos.config.MerchantConfigFragmentDirections.Companion.actionSettingsToOrder
-import net.taler.merchantpos.navigate
import net.taler.merchantpos.topSnackbar
/**
@@ -149,7 +148,7 @@ class MerchantConfigFragment : Fragment() {
onResultReceived()
updateView()
topSnackbar(view!!, getString(R.string.config_changed, currency), LENGTH_LONG)
- actionSettingsToOrder().navigate(findNavController())
+ navigate(actionSettingsToOrder())
}
private fun onError(msg: String) {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
index faee226..fc3f93a 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryManager.kt
@@ -24,22 +24,15 @@ import com.android.volley.RequestQueue
import com.android.volley.Response.ErrorListener
import com.android.volley.Response.Listener
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.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
-import net.taler.merchantpos.Amount
+import net.taler.common.Amount
+import net.taler.common.Timestamp
import net.taler.merchantpos.config.ConfigManager
import net.taler.merchantpos.config.MerchantRequest
import org.json.JSONObject
-@JsonInclude(NON_EMPTY)
-class Timestamp(
- @JsonProperty("t_ms")
- val ms: Long
-)
-
data class HistoryItem(
@JsonProperty("order_id")
val orderId: String,
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
index 0c53f71..afa925d 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/history/MerchantHistoryFragment.kt
@@ -35,14 +35,14 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
import kotlinx.android.synthetic.main.fragment_merchant_history.*
+import net.taler.common.exhaustive
+import net.taler.common.navigate
+import net.taler.common.toRelativeTime
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
-import net.taler.merchantpos.exhaustive
import net.taler.merchantpos.history.HistoryItemAdapter.HistoryItemViewHolder
import net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionGlobalMerchantSettings
import net.taler.merchantpos.history.MerchantHistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment
-import net.taler.merchantpos.navigate
-import net.taler.merchantpos.toRelativeTime
import java.util.*
private interface RefundClickListener {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
index 1797cea..aa2489a 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundFragment.kt
@@ -28,15 +28,15 @@ import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_refund.*
+import net.taler.common.fadeIn
+import net.taler.common.fadeOut
+import net.taler.common.navigate
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
-import net.taler.merchantpos.fadeIn
-import net.taler.merchantpos.fadeOut
import net.taler.merchantpos.history.RefundFragmentDirections.Companion.actionRefundFragmentToRefundUriFragment
import net.taler.merchantpos.history.RefundResult.Error
import net.taler.merchantpos.history.RefundResult.PastDeadline
import net.taler.merchantpos.history.RefundResult.Success
-import net.taler.merchantpos.navigate
class RefundFragment : Fragment() {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
index f2bd569..6e5b96d 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/history/RefundUriFragment.kt
@@ -25,9 +25,9 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.fragment_refund_uri.*
+import net.taler.common.NfcManager.Companion.hasNfc
+import net.taler.common.QrCodeManager.makeQrCode
import net.taler.merchantpos.MainViewModel
-import net.taler.merchantpos.NfcManager.Companion.hasNfc
-import net.taler.merchantpos.QrCodeManager.makeQrCode
import net.taler.merchantpos.R
class RefundUriFragment : Fragment() {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
index 34b97c0..e935d4f 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
@@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView.Adapter
import kotlinx.android.synthetic.main.fragment_categories.*
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
+import net.taler.merchantpos.config.Category
import net.taler.merchantpos.order.CategoryAdapter.CategoryViewHolder
interface CategorySelectionListener {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt
index ff6061a..847326b 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt
@@ -20,7 +20,9 @@ import androidx.annotation.UiThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
-import net.taler.merchantpos.CombinedLiveData
+import net.taler.common.CombinedLiveData
+import net.taler.merchantpos.config.Category
+import net.taler.merchantpos.config.ConfigProduct
import net.taler.merchantpos.order.RestartState.DISABLED
import net.taler.merchantpos.order.RestartState.ENABLED
import net.taler.merchantpos.order.RestartState.UNDO
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Definitions.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
index 63eda17..5954e63 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Definitions.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
@@ -16,105 +16,8 @@
package net.taler.merchantpos.order
-import androidx.core.os.LocaleListCompat
-import com.fasterxml.jackson.annotation.JsonIgnore
-import com.fasterxml.jackson.annotation.JsonInclude
-import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
-import com.fasterxml.jackson.annotation.JsonProperty
-import net.taler.merchantpos.Amount
-import java.util.*
-import java.util.Locale.LanguageRange
-import kotlin.collections.ArrayList
-import kotlin.collections.HashMap
-
-data class Category(
- val id: Int,
- val name: String,
- @JsonProperty("name_i18n")
- val nameI18n: Map<String, String>?
-) {
- var selected: Boolean = false
- val localizedName: String get() = getLocalizedString(nameI18n, name)
-}
-
-@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<String, String>?
- abstract val price: String
- @get:JsonProperty("delivery_location")
- abstract val location: String?
- abstract val image: String?
- @get:JsonIgnore
- val localizedDescription: String
- get() = getLocalizedString(descriptionI18n, description)
-}
-
-data class ConfigProduct(
- @JsonIgnore
- val id: String = UUID.randomUUID().toString(),
- override val productId: String?,
- override val description: String,
- override val descriptionI18n: Map<String, String>?,
- override val price: String,
- override val location: String?,
- override val image: String?,
- val categories: List<Int>,
- @JsonIgnore
- val quantity: Int = 0
-) : Product() {
- val priceAsDouble by lazy { Amount.fromString(price).amount.toDouble() }
-
- override fun equals(other: Any?) = other is ConfigProduct && id == other.id
- override fun hashCode() = id.hashCode()
-}
-
-data class ContractProduct(
- override val productId: String?,
- override val description: String,
- override val descriptionI18n: Map<String, String>?,
- override val price: String,
- override val location: String?,
- override val image: String?,
- val quantity: Int
-) : Product() {
- constructor(product: ConfigProduct) : this(
- product.productId,
- product.description,
- product.descriptionI18n,
- product.price,
- product.location,
- product.image,
- product.quantity
- )
-}
-
-private fun getLocalizedString(map: Map<String, String>?, 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<LanguageRange>(locales.size())
- for (i in 0 until locales.size()) {
- priorityList.add(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
-}
+import net.taler.merchantpos.config.Category
+import net.taler.merchantpos.config.ConfigProduct
data class Order(val id: Int, val availableCategories: Map<Int, Category>) {
val products = ArrayList<ConfigProduct>()
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
index 49f7cf2..ad6cd87 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
@@ -23,12 +23,11 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
-import androidx.navigation.fragment.findNavController
import androidx.transition.TransitionManager.beginDelayedTransition
import kotlinx.android.synthetic.main.fragment_order.*
+import net.taler.common.navigate
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
-import net.taler.merchantpos.navigate
import net.taler.merchantpos.order.OrderFragmentDirections.Companion.actionGlobalConfigFetcher
import net.taler.merchantpos.order.OrderFragmentDirections.Companion.actionOrderToMerchantSettings
import net.taler.merchantpos.order.OrderFragmentDirections.Companion.actionOrderToProcessPayment
@@ -65,9 +64,9 @@ class OrderFragment : Fragment() {
override fun onStart() {
super.onStart()
if (!viewModel.configManager.config.isValid()) {
- actionOrderToMerchantSettings().navigate(findNavController())
+ navigate(actionOrderToMerchantSettings())
} else if (viewModel.configManager.merchantConfig?.currency == null) {
- actionGlobalConfigFetcher().navigate(findNavController())
+ navigate(actionGlobalConfigFetcher())
}
}
@@ -108,7 +107,7 @@ class OrderFragment : Fragment() {
completeButton.setOnClickListener {
val order = liveOrder.order.value ?: return@setOnClickListener
paymentManager.createPayment(order)
- actionOrderToProcessPayment().navigate(findNavController())
+ navigate(actionOrderToProcessPayment())
}
}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt
index 48ddc57..a30c264 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderManager.kt
@@ -24,8 +24,10 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations.map
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
-import net.taler.merchantpos.Amount.Companion.fromString
+import net.taler.common.Amount.Companion.fromString
import net.taler.merchantpos.R
+import net.taler.merchantpos.config.Category
+import net.taler.merchantpos.config.ConfigProduct
import net.taler.merchantpos.config.ConfigurationReceiver
import net.taler.merchantpos.order.RestartState.ENABLED
import org.json.JSONObject
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
index 1b70016..a90334b 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
@@ -37,10 +37,11 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import kotlinx.android.synthetic.main.fragment_order_state.*
+import net.taler.common.fadeIn
+import net.taler.common.fadeOut
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
-import net.taler.merchantpos.fadeIn
-import net.taler.merchantpos.fadeOut
+import net.taler.merchantpos.config.ConfigProduct
import net.taler.merchantpos.order.OrderAdapter.OrderLineLookup
import net.taler.merchantpos.order.OrderAdapter.OrderViewHolder
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
index 4704ad0..d4da73f 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
@@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
import kotlinx.android.synthetic.main.fragment_products.*
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
+import net.taler.merchantpos.config.ConfigProduct
import net.taler.merchantpos.order.ProductAdapter.ProductViewHolder
interface ProductSelectionListener {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
index 7f15816..4cfb069 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
@@ -30,7 +30,6 @@ import com.android.volley.VolleyError
import com.fasterxml.jackson.databind.ObjectMapper
import net.taler.merchantpos.config.ConfigManager
import net.taler.merchantpos.config.MerchantRequest
-import net.taler.merchantpos.order.ContractProduct
import net.taler.merchantpos.order.Order
import org.json.JSONArray
import org.json.JSONObject
@@ -103,7 +102,7 @@ class PaymentManager(
}
private fun Order.getProductsJson(): JSONArray {
- val contractProducts = products.map { ContractProduct(it) }
+ val contractProducts = products.map { it.toContractProduct() }
val productsStr = mapper.writeValueAsString(contractProducts)
return JSONArray(productsStr)
}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
index 24f67f1..1d61894 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
@@ -27,13 +27,13 @@ import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
import kotlinx.android.synthetic.main.fragment_process_payment.*
+import net.taler.common.NfcManager.Companion.hasNfc
+import net.taler.common.QrCodeManager.makeQrCode
+import net.taler.common.fadeIn
+import net.taler.common.fadeOut
+import net.taler.common.navigate
import net.taler.merchantpos.MainViewModel
-import net.taler.merchantpos.NfcManager.Companion.hasNfc
-import net.taler.merchantpos.QrCodeManager.makeQrCode
import net.taler.merchantpos.R
-import net.taler.merchantpos.fadeIn
-import net.taler.merchantpos.fadeOut
-import net.taler.merchantpos.navigate
import net.taler.merchantpos.payment.ProcessPaymentFragmentDirections.Companion.actionProcessPaymentToPaymentSuccess
import net.taler.merchantpos.topSnackbar
@@ -69,7 +69,7 @@ class ProcessPaymentFragment : Fragment() {
}
if (payment.paid) {
model.orderManager.onOrderPaid(payment.order.id)
- actionProcessPaymentToPaymentSuccess().navigate(findNavController())
+ navigate(actionProcessPaymentToPaymentSuccess())
return
}
payIntroView.fadeIn()
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> 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 <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.Observer
+
+class CombinedLiveData<T, K, S>(
+ source1: LiveData<T>,
+ source2: LiveData<K>,
+ private val combine: (data1: T?, data2: K?) -> S
+) : MediatorLiveData<S>() {
+
+ 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 <S : Any?> addSource(source: LiveData<S>, onChanged: Observer<in S>) {
+ throw UnsupportedOperationException()
+ }
+
+ override fun <T : Any?> removeSource(toRemote: LiveData<T>) {
+ 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 <http://www.gnu.org/licenses/>
+ */
+
+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<String, String>?
+ 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<String, String>?,
+ 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 <http://www.gnu.org/licenses/>
+ */
+
+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<String, String>?, 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<Locale.LanguageRange>(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
+ }
+
+}