gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-android] branch master updated (e6e58ec -> a229b33)


From: gnunet
Subject: [taler-taler-android] branch master updated (e6e58ec -> a229b33)
Date: Wed, 01 Jun 2022 13:32:21 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository taler-android.

    from e6e58ec  -migrate away from deprecated library (Kotlin synthetics)
     new 29e19d0  -update dependencies
     new 023ea96  -remove multiplatform dependency, library upgrade WIP
     new 1ef7ae7  -fix error info serializer
     new a229b33  use new annotation for polymorphic serialization

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitmodules                                        |   3 -
 anastasis-ui/build.gradle                          |   4 +-
 .../ui/authentication/AuthenticationFragment.kt    |  44 ++--
 .../ui/authentication/SecurityQuestionFragment.kt  |  27 ++-
 .../gnu/anastasis/ui/authentication/SmsFragment.kt |  23 ++-
 .../anastasis/ui/authentication/VideoFragment.kt   |  34 +++-
 .../ui/identity/ChangeLocationFragment.kt          |  16 +-
 build.gradle                                       |  15 +-
 cashier/build.gradle                               |   3 +
 .../java/net/taler/cashier/AboutDialogFragment.kt  |   2 +-
 .../main/java/net/taler/cashier/BalanceFragment.kt |   2 +-
 .../main/java/net/taler/cashier/MainViewModel.kt   |  23 ++-
 .../src/main/java/net/taler/cashier/Response.kt    |   5 +-
 .../main/java/net/taler/cashier/SignedAmount.kt    |   2 +-
 .../java/net/taler/cashier/config/ConfigManager.kt |   7 +-
 .../net/taler/cashier/withdraw/WithdrawManager.kt  |   2 +-
 merchant-lib/build.gradle                          |  16 +-
 .../main/java/net/taler/merchantlib/MerchantApi.kt |  41 ++--
 .../java/net/taler/merchantlib/OrderHistory.kt     |   4 +-
 .../src/main/java/net/taler/merchantlib/Orders.kt  |  10 +-
 .../src/main/java/net/taler/merchantlib/Refunds.kt |   3 +-
 .../main/java/net/taler/merchantlib/Response.kt    |  10 +-
 .../java/net/taler/merchantlib/MerchantApiTest.kt  |   7 +-
 .../java/net/taler/merchantlib/MockHttpClient.kt   |  24 +--
 merchant-terminal/build.gradle                     |   6 +-
 merchant-terminal/src/main/AndroidManifest.xml     |   1 +
 .../java/net/taler/merchantpos/MainActivity.kt     |   4 +-
 .../net/taler/merchantpos/config/ConfigManager.kt  |   7 +-
 .../java/net/taler/merchantpos/config/PosConfig.kt |   2 +-
 .../java/net/taler/merchantpos/order/LiveOrder.kt  |   2 +-
 .../main/java/net/taler/merchantpos/order/Order.kt |   4 +-
 .../taler/merchantpos/order/OrderStateFragment.kt  |   9 +-
 .../taler/merchantpos/payment/PaymentManager.kt    |   2 +-
 .../net/taler/merchantpos/refund/RefundFragment.kt |   4 +-
 .../net/taler/merchantpos/refund/RefundManager.kt  |   2 +-
 .../taler/merchantpos/order/OrderManagerTest.kt    |   2 +-
 multiplatform                                      |   1 -
 taler-kotlin-android/build.gradle                  |  24 +--
 taler-kotlin-android/src/main/AndroidManifest.xml  |   7 +-
 .../src/main/java/net/taler/common/Amount.kt       | 199 +++++++++++++++++++
 .../src/main/java/net/taler/common/AndroidUtils.kt |  12 --
 .../main/java/net/taler/common/ContractTerms.kt    |   2 -
 .../src/main/java/net/taler/common/TalerUri.kt     |  62 ++++++
 .../src/main/java/net/taler/common/Time.kt         | 129 ++++++++++++
 .../src/main/java/net/taler/common/Version.kt      |  70 +++++++
 .../java/net/taler/lib/android/Serialization.kt    |  21 --
 .../src/test/java/net/taler/common/AmountTest.kt   | 221 +++++++++++++++++++++
 .../java/net/taler/common/ContractTermsTest.kt     |   1 -
 .../src/test/java/net/taler/common/TalerUriTest.kt |  65 ++++++
 .../src/test/java/net/taler/common/TestUtils.kt    |  28 ++-
 .../src/test/java/net/taler/common/TimeTest.kt     |  46 +++++
 .../src/test/java/net/taler/common/VersionTest.kt  |  65 ++++++
 wallet/build.gradle                                |  36 ++--
 wallet/src/main/AndroidManifest.xml                |   2 +
 .../net/taler/wallet/backend/WalletBackendApi.kt   |   5 -
 .../net/taler/wallet/backend/WalletResponse.kt     |  10 +-
 .../net/taler/wallet/balances/BalanceAdapter.kt    |   2 +-
 .../net/taler/wallet/exchanges/ExchangeFees.kt     |   5 +-
 .../taler/wallet/exchanges/ExchangeFeesFragment.kt |   2 +-
 .../net/taler/wallet/payment/PaymentManager.kt     |   2 +-
 .../net/taler/wallet/payment/PaymentResponses.kt   |  10 +-
 .../taler/wallet/payment/PromptPaymentFragment.kt  |   6 +-
 .../java/net/taler/wallet/refund/RefundManager.kt  |   2 +-
 .../net/taler/wallet/settings/SettingsFragment.kt  |  46 ++++-
 .../transactions/TransactionDetailFragment.kt      |   2 +-
 .../net/taler/wallet/transactions/Transactions.kt  |   4 +-
 .../wallet/transactions/TransactionsFragment.kt    |   7 +-
 .../wallet/withdraw/ManualWithdrawFragment.kt      |   2 +-
 .../withdraw/ManualWithdrawSuccessFragment.kt      |   2 +-
 .../wallet/withdraw/PromptWithdrawFragment.kt      |   2 +-
 .../net/taler/wallet/withdraw/WithdrawManager.kt   |   2 +-
 .../taler/wallet/payment/PaymentResponsesTest.kt   |   1 -
 72 files changed, 1189 insertions(+), 286 deletions(-)
 delete mode 160000 multiplatform
 create mode 100644 
taler-kotlin-android/src/main/java/net/taler/common/Amount.kt
 create mode 100644 
taler-kotlin-android/src/main/java/net/taler/common/TalerUri.kt
 create mode 100644 taler-kotlin-android/src/main/java/net/taler/common/Time.kt
 create mode 100644 
taler-kotlin-android/src/main/java/net/taler/common/Version.kt
 delete mode 100644 
taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt
 create mode 100644 
taler-kotlin-android/src/test/java/net/taler/common/AmountTest.kt
 create mode 100644 
taler-kotlin-android/src/test/java/net/taler/common/TalerUriTest.kt
 copy cashier/src/main/java/net/taler/cashier/SignedAmount.kt => 
taler-kotlin-android/src/test/java/net/taler/common/TestUtils.kt (54%)
 create mode 100644 
taler-kotlin-android/src/test/java/net/taler/common/TimeTest.kt
 create mode 100644 
taler-kotlin-android/src/test/java/net/taler/common/VersionTest.kt

diff --git a/.gitmodules b/.gitmodules
index 29c8ad2..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "wallet-kotlin"]
-       path = multiplatform
-       url = git://git.taler.net/wallet-kotlin.git
diff --git a/anastasis-ui/build.gradle b/anastasis-ui/build.gradle
index 2275496..4d30ee5 100644
--- a/anastasis-ui/build.gradle
+++ b/anastasis-ui/build.gradle
@@ -72,6 +72,6 @@ dependencies {
 
     testImplementation "junit:junit:$junit_version"
 
-    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
 }
diff --git 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/AuthenticationFragment.kt
 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/AuthenticationFragment.kt
index 59d0410..da947b0 100644
--- 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/AuthenticationFragment.kt
+++ 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/AuthenticationFragment.kt
@@ -29,10 +29,10 @@ import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.FragmentNavigatorExtras
 import androidx.navigation.fragment.findNavController
 import com.google.android.material.card.MaterialCardView
-import kotlinx.android.synthetic.main.fragment_authentication.*
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
 import org.gnu.anastasis.ui.MainViewModel
 import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentAuthenticationBinding
 
 class AuthenticationFragment : Fragment() {
 
@@ -40,6 +40,12 @@ class AuthenticationFragment : Fragment() {
 
     private var price: Amount = Amount.zero("KUDOS")
 
+    private var _binding: FragmentAuthenticationBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
@@ -50,46 +56,46 @@ class AuthenticationFragment : Fragment() {
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        passwordCard.setOnClickListener {
+        binding.passwordCard.setOnClickListener {
             showDialog(
                 
R.id.action_nav_anastasis_authentication_to_securityQuestionFragment,
-                passwordCard,
+                binding.passwordCard,
                 "question_card"
             )
         }
-        postidentCard.setOnClickListener {
+        binding.postidentCard.setOnClickListener {
             toggleCard(
-                postidentCard,
+                binding.postidentCard,
                 Amount.fromJSONString("KUDOS:3.5")
             )
         }
-        smsCard.setOnClickListener {
+        binding.smsCard.setOnClickListener {
             showDialog(
                 R.id.action_nav_anastasis_authentication_to_smsFragment,
-                smsCard,
+                binding.smsCard,
                 "sms_card"
             )
         }
-        videoCard.setOnClickListener {
+        binding.videoCard.setOnClickListener {
             showDialog(
                 R.id.action_nav_anastasis_authentication_to_videoFragment,
-                videoCard,
+                binding.videoCard,
                 "video_card"
             )
         }
 
         viewModel.securityQuestionChecked.observe(viewLifecycleOwner, { 
checked ->
-            passwordCard.isChecked = checked
+            binding.passwordCard.isChecked = checked
             updatePrice(checked, Amount.fromJSONString("KUDOS:0.5"))
             updateNextButtonState()
         })
         viewModel.smsChecked.observe(viewLifecycleOwner, { checked ->
-            smsCard.isChecked = checked
+            binding.smsCard.isChecked = checked
             updatePrice(checked, Amount.fromJSONString("KUDOS:1.0"))
             updateNextButtonState()
         })
         viewModel.videoChecked.observe(viewLifecycleOwner, { checked ->
-            videoCard.isChecked = checked
+            binding.videoCard.isChecked = checked
             updatePrice(checked, Amount.fromJSONString("KUDOS:2.25"))
             updateNextButtonState()
         })
@@ -113,16 +119,16 @@ class AuthenticationFragment : Fragment() {
     private fun updatePrice(add: Boolean, amount: Amount) {
         if (add) price += amount
         else price -= amount
-        recoveryCostView.text = "Recovery cost: $price"
+        binding.recoveryCostView.text = "Recovery cost: $price"
     }
 
     private fun updateNextButtonState() {
         var numChecked = 0
-        numChecked += if (passwordCard.isChecked) 1 else 0
-        numChecked += if (postidentCard.isChecked) 1 else 0
-        numChecked += if (smsCard.isChecked) 1 else 0
-        numChecked += if (videoCard.isChecked) 1 else 0
-        nextAuthButton.isEnabled = numChecked >= 2
+        numChecked += if (binding.passwordCard.isChecked) 1 else 0
+        numChecked += if (binding.postidentCard.isChecked) 1 else 0
+        numChecked += if (binding.smsCard.isChecked) 1 else 0
+        numChecked += if (binding.videoCard.isChecked) 1 else 0
+        binding.nextAuthButton.isEnabled = numChecked >= 2
     }
 
 }
diff --git 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SecurityQuestionFragment.kt
 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SecurityQuestionFragment.kt
index 7353174..0796610 100644
--- 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SecurityQuestionFragment.kt
+++ 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SecurityQuestionFragment.kt
@@ -23,30 +23,35 @@ import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
-import com.google.android.material.transition.MaterialContainerTransform
-import 
com.google.android.material.transition.MaterialContainerTransform.FADE_MODE_CROSS
-import kotlinx.android.synthetic.main.fragment_security_question.*
 import org.gnu.anastasis.ui.MainViewModel
-import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentSecurityQuestionBinding
 
 class SecurityQuestionFragment : Fragment() {
 
     private val viewModel: MainViewModel by activityViewModels()
 
+    private var _binding: FragmentSecurityQuestionBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        sharedElementEnterTransition = MaterialContainerTransform().apply {
-            fadeMode = FADE_MODE_CROSS
-        }
-        return inflater.inflate(R.layout.fragment_security_question, 
container, false).apply {
-            transitionName = "question_card"
-        }
+        _binding = FragmentSecurityQuestionBinding.inflate(inflater, 
container, false)
+        val view = binding.root
+        return view
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        saveQuestionButton.setOnClickListener {
+        binding.saveQuestionButton.setOnClickListener {
             viewModel.securityQuestionChecked.value = true
             findNavController().popBackStack()
         }
diff --git 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SmsFragment.kt 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SmsFragment.kt
index a5d872d..413f472 100644
--- 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SmsFragment.kt
+++ 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SmsFragment.kt
@@ -32,10 +32,11 @@ import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
 import com.google.android.material.transition.MaterialContainerTransform
 import 
com.google.android.material.transition.MaterialContainerTransform.FADE_MODE_CROSS
-import kotlinx.android.synthetic.main.fragment_sms.*
 import org.gnu.anastasis.ui.MainViewModel
 import org.gnu.anastasis.ui.PERMISSION_REQUEST_CODE
 import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentSecurityQuestionBinding
+import org.gnu.anastasis.ui.databinding.FragmentSmsBinding
 
 private const val PERMISSION = Manifest.permission.READ_PHONE_STATE
 
@@ -43,10 +44,17 @@ class SmsFragment : Fragment() {
 
     private val viewModel: MainViewModel by activityViewModels()
 
+    private var _binding: FragmentSmsBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
+        _binding = FragmentSmsBinding.inflate(inflater, container, false)
         sharedElementEnterTransition = MaterialContainerTransform().apply {
             fadeMode = FADE_MODE_CROSS
         }
@@ -55,11 +63,16 @@ class SmsFragment : Fragment() {
         }
     }
 
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        smsView.editText?.setOnFocusChangeListener { _, hasFocus ->
+        binding.smsView.editText?.setOnFocusChangeListener { _, hasFocus ->
             if (hasFocus) checkPerm()
         }
-        saveSmsButton.setOnClickListener {
+        binding.saveSmsButton.setOnClickListener {
             viewModel.smsChecked.value = true
             findNavController().popBackStack()
         }
@@ -98,8 +111,8 @@ class SmsFragment : Fragment() {
     private fun fillPhoneNumber() {
         val telephonyService = 
requireContext().getSystemService<TelephonyManager>()
         telephonyService?.line1Number?.let { phoneNumber ->
-            smsView.editText?.setText(phoneNumber)
-            smsView.editText?.setSelection(phoneNumber.length)
+            binding.smsView.editText?.setText(phoneNumber)
+            binding.smsView.editText?.setSelection(phoneNumber.length)
         }
     }
 
diff --git 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/VideoFragment.kt
 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/VideoFragment.kt
index 6cd80ce..6716f8a 100644
--- 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/VideoFragment.kt
+++ 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/VideoFragment.kt
@@ -35,9 +35,9 @@ import androidx.navigation.fragment.findNavController
 import androidx.transition.TransitionManager.beginDelayedTransition
 import com.google.android.material.transition.MaterialContainerTransform
 import 
com.google.android.material.transition.MaterialContainerTransform.FADE_MODE_CROSS
-import kotlinx.android.synthetic.main.fragment_video.*
 import org.gnu.anastasis.ui.MainViewModel
 import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentVideoBinding
 import java.io.FileDescriptor
 
 private const val REQUEST_IMAGE_CAPTURE = 1
@@ -47,10 +47,17 @@ class VideoFragment : Fragment() {
 
     private val viewModel: MainViewModel by activityViewModels()
 
+    private var _binding: FragmentVideoBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
+        _binding = FragmentVideoBinding.inflate(inflater, container, false)
         sharedElementEnterTransition = MaterialContainerTransform().apply {
             fadeMode = FADE_MODE_CROSS
         }
@@ -59,8 +66,13 @@ class VideoFragment : Fragment() {
         }
     }
 
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        takePhotoButton.setOnClickListener {
+        binding.takePhotoButton.setOnClickListener {
             val pm = requireContext().packageManager
             Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
                 takePictureIntent.resolveActivity(pm)?.also {
@@ -70,7 +82,7 @@ class VideoFragment : Fragment() {
                 }
             }
         }
-        choosePhotoButton.setOnClickListener {
+        binding.choosePhotoButton.setOnClickListener {
             val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
                 addCategory(Intent.CATEGORY_OPENABLE)
                 type = "image/*"
@@ -80,7 +92,7 @@ class VideoFragment : Fragment() {
             )
         }
 
-        saveVideoButton.setOnClickListener {
+        binding.saveVideoButton.setOnClickListener {
             viewModel.videoChecked.value = true
             findNavController().popBackStack()
         }
@@ -99,12 +111,14 @@ class VideoFragment : Fragment() {
     }
 
     private fun showImage(bitmap: Bitmap) {
-        photoView.setImageBitmap(bitmap)
-        beginDelayedTransition(view as ViewGroup)
-        photoView.visibility = VISIBLE
-        takePhotoButton.visibility = GONE
-        choosePhotoButton.visibility = GONE
-        saveVideoButton.isEnabled = true
+        with (binding) {
+            photoView.setImageBitmap(bitmap)
+            beginDelayedTransition(view as ViewGroup)
+            photoView.visibility = VISIBLE
+            takePhotoButton.visibility = GONE
+            choosePhotoButton.visibility = GONE
+            saveVideoButton.isEnabled = true
+        }
     }
 
     private fun getBitmapFromUri(uri: Uri): Bitmap {
diff --git 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/ChangeLocationFragment.kt
 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/ChangeLocationFragment.kt
index 5b68d36..00eec11 100644
--- 
a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/ChangeLocationFragment.kt
+++ 
b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/ChangeLocationFragment.kt
@@ -23,14 +23,20 @@ import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_change_location.*
 import org.gnu.anastasis.ui.MainViewModel
 import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentChangeLocationBinding
 
 class ChangeLocationFragment : Fragment() {
 
     private val viewModel: MainViewModel by activityViewModels()
 
+    private var _binding: FragmentChangeLocationBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
@@ -40,16 +46,16 @@ class ChangeLocationFragment : Fragment() {
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        switzerlandView.setOnClickListener {
+        binding.switzerlandView.setOnClickListener {
             changeCountry(LOCATIONS[0])
         }
-        germanyView.setOnClickListener {
+        binding.germanyView.setOnClickListener {
             changeCountry(LOCATIONS[1])
         }
-        usaView.setOnClickListener {
+        binding.usaView.setOnClickListener {
             changeCountry(LOCATIONS[2])
         }
-        indiaView.setOnClickListener {
+        binding.indiaView.setOnClickListener {
             changeCountry(LOCATIONS[3])
         }
     }
diff --git a/build.gradle b/build.gradle
index 7c7f3a4..3179daa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,10 @@
 buildscript {
-    ext.kotlin_version = '1.5.31'
-    ext.ktor_version = "1.6.3"
-    ext.nav_version = '2.4.1'
-    ext.material_version = "1.4.0"
-    ext.lifecycle_version = "2.4.0"
-    ext.constraintlayout_version = "2.1.2"
+    ext.kotlin_version = '1.6.10'
+    ext.ktor_version = '2.0.1'
+    ext.nav_version = '2.4.2'
+    ext.material_version = '1.6.0'
+    ext.lifecycle_version = '2.4.1'
+    ext.constraintlayout_version = '2.1.3'
     ext.junit_version = "4.13.2"
     // check https://android-rebuilds.beuc.net/ for availability of free build 
tools
     ext.build_tools_version = "30.0.3"
@@ -15,7 +15,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:7.2.0'
+        classpath 'com.android.tools.build:gradle:7.2.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
         classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
         classpath 
"androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
@@ -25,7 +25,6 @@ buildscript {
 allprojects {
     repositories {
         google()
-        jcenter()
         mavenCentral()
         maven { url 'https://jitpack.io' }
     }
diff --git a/cashier/build.gradle b/cashier/build.gradle
index 1c9c2b6..ea59f23 100644
--- a/cashier/build.gradle
+++ b/cashier/build.gradle
@@ -85,6 +85,9 @@ dependencies {
     implementation "io.ktor:ktor-client:$ktor_version"
     implementation "io.ktor:ktor-client-okhttp:$ktor_version"
     implementation "io.ktor:ktor-client-serialization-jvm:$ktor_version"
+    implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
+    implementation "io.ktor:ktor-client-content-negotiation:$ktor_version"
+    implementation "io.ktor:ktor-server-call-logging:$ktor_version"
 
     testImplementation "junit:junit:$junit_version"
 
diff --git a/cashier/src/main/java/net/taler/cashier/AboutDialogFragment.kt 
b/cashier/src/main/java/net/taler/cashier/AboutDialogFragment.kt
index cdea792..3da49d2 100644
--- a/cashier/src/main/java/net/taler/cashier/AboutDialogFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/AboutDialogFragment.kt
@@ -30,7 +30,7 @@ import net.taler.cashier.BuildConfig.VERSION_NAME
 import net.taler.cashier.config.VERSION_BANK
 import net.taler.cashier.databinding.FragmentAboutDialogBinding
 import net.taler.cashier.databinding.FragmentBalanceBinding
-import net.taler.lib.common.Version
+import net.taler.common.Version
 
 class AboutDialogFragment : DialogFragment() {
 
diff --git a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt 
b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
index 002301c..fa9600b 100644
--- a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
@@ -32,10 +32,10 @@ import 
net.taler.cashier.BalanceFragmentDirections.Companion.actionBalanceFragme
 import net.taler.cashier.databinding.FragmentBalanceBinding
 import net.taler.cashier.withdraw.LastTransaction
 import net.taler.cashier.withdraw.WithdrawStatus
+import net.taler.common.Amount
 import net.taler.common.exhaustive
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
-import net.taler.lib.common.Amount
 
 sealed class BalanceResult {
     class Error(val msg: String) : BalanceResult()
diff --git a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt 
b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
index 253c7d5..2196e78 100644
--- a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
+++ b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
@@ -24,17 +24,17 @@ import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.viewModelScope
 import io.ktor.client.HttpClient
 import io.ktor.client.engine.okhttp.OkHttp
-import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.json.serializer.KotlinxSerializer
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.serialization.kotlinx.json.json
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.serialization.json.Json
 import net.taler.cashier.HttpHelper.makeJsonGetRequest
 import net.taler.cashier.config.ConfigManager
 import net.taler.cashier.withdraw.WithdrawManager
+import net.taler.common.Amount
+import net.taler.common.AmountParserException
 import net.taler.common.isOnline
-import net.taler.lib.common.Amount
-import net.taler.lib.common.AmountParserException
 
 private val TAG = MainViewModel::class.java.simpleName
 
@@ -46,12 +46,12 @@ class MainViewModel(private val app: Application) : 
AndroidViewModel(app) {
                 retryOnConnectionFailure(true)
             }
         }
-        install(JsonFeature) {
-            serializer = KotlinxSerializer(
-                Json {
-                    ignoreUnknownKeys = true
-                }
-            )
+        expectSuccess = true
+        install(ContentNegotiation) {
+            json(Json {
+                encodeDefaults = false
+                ignoreUnknownKeys = true
+            })
         }
     }
     val configManager = ConfigManager(app, viewModelScope, httpClient)
@@ -77,7 +77,8 @@ class MainViewModel(private val app: Application) : 
AndroidViewModel(app) {
                         "debit" -> false
                         else -> throw AmountParserException("Unexpected 
credit_debit_indicator: $creditDebitIndicator")
                     }
-                    BalanceResult.Success(SignedAmount(positive, 
Amount.fromJSONString(balanceAmount)))
+                    BalanceResult.Success(SignedAmount(positive,
+                        Amount.fromJSONString(balanceAmount)))
                 } catch (e: Exception) {
                     Log.e(TAG, "Error parsing balance", e)
                     BalanceResult.Error("Invalid 
amount:\n${response.json.toString(2)}")
diff --git a/cashier/src/main/java/net/taler/cashier/Response.kt 
b/cashier/src/main/java/net/taler/cashier/Response.kt
index 6a72604..89b7b33 100644
--- a/cashier/src/main/java/net/taler/cashier/Response.kt
+++ b/cashier/src/main/java/net/taler/cashier/Response.kt
@@ -18,8 +18,9 @@ package net.taler.cashier
 
 import android.content.Context
 import android.util.Log
+import io.ktor.client.call.body
 import io.ktor.client.call.receive
-import io.ktor.client.features.ResponseException
+import io.ktor.client.plugins.ResponseException
 import io.ktor.http.HttpStatusCode
 import kotlinx.serialization.Serializable
 import net.taler.common.isOnline
@@ -47,7 +48,7 @@ class Response<out T> private constructor(
             val response = e.response
             return try {
                 Log.e("TEST", "TRY RECEIVE $response")
-                val error: Error = response.receive()
+                val error: Error = response.body()
                 "Error ${error.code}: ${error.hint}"
             } catch (ex: Exception) {
                 "Status code: ${response.status.value}"
diff --git a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt 
b/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
index e79acfd..4f624ae 100644
--- a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
+++ b/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
@@ -16,7 +16,7 @@
 
 package net.taler.cashier
 
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
 
 data class SignedAmount(
     val positive: Boolean,
diff --git a/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt 
b/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt
index 0718963..2fde37d 100644
--- a/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt
+++ b/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt
@@ -29,6 +29,7 @@ import 
androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionSc
 import androidx.security.crypto.MasterKeys
 import androidx.security.crypto.MasterKeys.AES256_GCM_SPEC
 import io.ktor.client.HttpClient
+import io.ktor.client.call.body
 import io.ktor.client.request.get
 import io.ktor.client.request.header
 import io.ktor.http.HttpHeaders.Authorization
@@ -39,8 +40,8 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import net.taler.cashier.Response
 import net.taler.cashier.Response.Companion.response
+import net.taler.common.Version
 import net.taler.common.getIncompatibleStringOrNull
-import net.taler.lib.common.Version
 
 val VERSION_BANK = Version(0, 0, 0)
 private const val PREF_NAME = "net.taler.cashier.prefs"
@@ -116,7 +117,7 @@ class ConfigManager(
         val url = "${config.bankUrl}/config"
         Log.d(TAG, "Checking config: $url")
         val configResponse = response {
-            httpClient.get(url) as ConfigResponse
+            httpClient.get(url).body<ConfigResponse>()
         }
         if (configResponse.isFailure) {
             configResponse
@@ -126,7 +127,7 @@ class ConfigManager(
             val balanceResponse = response {
                 val authUrl = "${config.bankUrl}/accounts/${config.username}"
                 Log.d(TAG, "Checking auth: $authUrl")
-                httpClient.get<Unit>(authUrl) {
+                httpClient.get(authUrl) {
                     header(Authorization, config.basicAuth)
                 }
             }
diff --git 
a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt 
b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
index 5d34bba..360bded 100644
--- a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
@@ -34,9 +34,9 @@ import net.taler.cashier.HttpJsonResult.Error
 import net.taler.cashier.HttpJsonResult.Success
 import net.taler.cashier.MainViewModel
 import net.taler.cashier.R
+import net.taler.common.Amount
 import net.taler.common.QrCodeManager.makeQrCode
 import net.taler.common.isOnline
-import net.taler.lib.common.Amount
 import org.json.JSONObject
 import java.util.concurrent.TimeUnit.MINUTES
 import java.util.concurrent.TimeUnit.SECONDS
diff --git a/merchant-lib/build.gradle b/merchant-lib/build.gradle
index d388b27..42dc3ec 100644
--- a/merchant-lib/build.gradle
+++ b/merchant-lib/build.gradle
@@ -21,13 +21,13 @@ plugins {
 }
 
 android {
-    compileSdkVersion 30
+    compileSdkVersion 32
     //noinspection GradleDependency
     buildToolsVersion "$build_tools_version"
 
     defaultConfig {
         minSdkVersion 21
-        targetSdkVersion 30
+        targetSdkVersion 32
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles "consumer-rules.pro"
@@ -57,9 +57,17 @@ dependencies {
     api "io.ktor:ktor-client:$ktor_version"
     api "io.ktor:ktor-client-okhttp:$ktor_version"
     api "io.ktor:ktor-client-serialization-jvm:$ktor_version"
+    api "io.ktor:ktor-client-content-negotiation:$ktor_version"
+    implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
+    implementation "io.ktor:ktor-server-call-logging:$ktor_version"
 
     testImplementation "junit:junit:$junit_version"
     testImplementation "io.ktor:ktor-client-mock-jvm:$ktor_version"
-    testImplementation "io.ktor:ktor-client-logging-jvm:$ktor_version"
-    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1'
+    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'
 }
+
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+    kotlinOptions {
+        freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
+    }
+}
\ No newline at end of file
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
index 0d22f91..c02907b 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
@@ -17,21 +17,24 @@
 package net.taler.merchantlib
 
 import io.ktor.client.HttpClient
+import io.ktor.client.call.body
 import io.ktor.client.engine.okhttp.OkHttp
-import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.json.serializer.KotlinxSerializer
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
 import io.ktor.client.request.delete
 import io.ktor.client.request.get
 import io.ktor.client.request.header
 import io.ktor.client.request.post
+import io.ktor.client.request.setBody
 import io.ktor.http.ContentType.Application.Json
 import io.ktor.http.HttpHeaders.Authorization
 import io.ktor.http.contentType
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
-import kotlinx.serialization.json.Json
 import net.taler.merchantlib.Response.Companion.response
+import io.ktor.serialization.kotlinx.json.*
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
 
 class MerchantApi(
     private val httpClient: HttpClient = getDefaultHttpClient(),
@@ -40,7 +43,7 @@ class MerchantApi(
 
     suspend fun getConfig(baseUrl: String): Response<ConfigResponse> = 
withContext(ioDispatcher) {
         response {
-            httpClient.get("$baseUrl/config") as ConfigResponse
+            httpClient.get("$baseUrl/config").body()
         }
     }
 
@@ -52,8 +55,8 @@ class MerchantApi(
             httpClient.post(merchantConfig.urlFor("private/orders")) {
                 header(Authorization, "ApiKey ${merchantConfig.apiKey}")
                 contentType(Json)
-                body = orderRequest
-            } as PostOrderResponse
+                setBody(orderRequest)
+            }.body()
         }
     }
 
@@ -64,7 +67,7 @@ class MerchantApi(
         response {
             httpClient.get(merchantConfig.urlFor("private/orders/$orderId")) {
                 header(Authorization, "ApiKey ${merchantConfig.apiKey}")
-            } as CheckPaymentResponse
+            }.body()
         }
     }
 
@@ -75,7 +78,7 @@ class MerchantApi(
         response {
             
httpClient.delete(merchantConfig.urlFor("private/orders/$orderId")) {
                 header(Authorization, "ApiKey ${merchantConfig.apiKey}")
-            } as Unit
+            }.body()
         }
     }
 
@@ -84,7 +87,7 @@ class MerchantApi(
             response {
                 httpClient.get(merchantConfig.urlFor("private/orders")) {
                     header(Authorization, "ApiKey ${merchantConfig.apiKey}")
-                } as OrderHistory
+                }.body()
             }
         }
 
@@ -97,27 +100,23 @@ class MerchantApi(
             
httpClient.post(merchantConfig.urlFor("private/orders/$orderId/refund")) {
                 header(Authorization, "ApiKey ${merchantConfig.apiKey}")
                 contentType(Json)
-                body = request
-            } as RefundResponse
+                setBody(request)
+            }.body()
         }
     }
 }
 
 fun getDefaultHttpClient(): HttpClient = HttpClient(OkHttp) {
+    expectSuccess = true
     engine {
         config {
             retryOnConnectionFailure(true)
         }
     }
-    install(JsonFeature) {
-        serializer = getSerializer()
+    install(ContentNegotiation) {
+        json(Json {
+            encodeDefaults = false
+            ignoreUnknownKeys = true
+        })
     }
 }
-
-fun getSerializer() = KotlinxSerializer(
-    Json {
-        encodeDefaults = false
-        ignoreUnknownKeys = true
-        classDiscriminator = "order_status"
-    }
-)
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
index dfd989b..b1ff5b1 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
@@ -18,8 +18,8 @@ package net.taler.merchantlib
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
+import net.taler.common.Amount
+import net.taler.common.Timestamp
 
 @Serializable
 data class OrderHistory(
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
index 166ca3c..391abf5 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
@@ -16,11 +16,12 @@
 
 package net.taler.merchantlib
 
+import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonClassDiscriminator
 import net.taler.common.ContractTerms
-import net.taler.lib.android.CustomClassDiscriminator
-import net.taler.lib.common.Duration
+import net.taler.common.Duration
 
 @Serializable
 data class PostOrderRequest(
@@ -36,9 +37,10 @@ data class PostOrderResponse(
     val orderId: String
 )
 
+@OptIn(ExperimentalSerializationApi::class)
 @Serializable
-sealed class CheckPaymentResponse: CustomClassDiscriminator {
-    override val discriminator: String = "order_status"
+@JsonClassDiscriminator("order_status")
+sealed class CheckPaymentResponse {
     abstract val paid: Boolean
 
     @Serializable
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt 
b/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt
index b78b571..58073fa 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt
@@ -18,8 +18,7 @@ package net.taler.merchantlib
 
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import net.taler.lib.common.Amount
-
+import net.taler.common.Amount
 @Serializable
 data class RefundRequest(
     /**
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 6fd0e37..b7ba1ac 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
@@ -16,10 +16,10 @@
 
 package net.taler.merchantlib
 
-import io.ktor.client.call.receive
-import io.ktor.client.features.ClientRequestException
-import io.ktor.client.features.ResponseException
-import io.ktor.client.features.ServerResponseException
+import io.ktor.client.call.body
+import io.ktor.client.plugins.ClientRequestException
+import io.ktor.client.plugins.ResponseException
+import io.ktor.client.plugins.ServerResponseException
 import kotlinx.serialization.Serializable
 
 class Response<out T> private constructor(
@@ -72,7 +72,7 @@ class Response<out T> private constructor(
     private suspend fun getExceptionString(e: ResponseException): String {
         val response = e.response
         return try {
-            val error: Error = response.receive()
+            val error: Error = response.body()
             "Error ${error.code}: ${error.hint}"
         } catch (ex: Exception) {
             "Status code: ${response.status.value}"
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 6abacfd..a89e2d3 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -20,10 +20,11 @@ import io.ktor.http.HttpStatusCode.Companion.NotFound
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import net.taler.common.Amount
 import net.taler.common.ContractProduct
 import net.taler.common.ContractTerms
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
+import net.taler.common.Timestamp
 import net.taler.merchantlib.MockHttpClient.giveJsonResponse
 import net.taler.merchantlib.MockHttpClient.httpClient
 import org.junit.Assert.assertEquals
@@ -33,7 +34,7 @@ import org.junit.Test
 @ExperimentalCoroutinesApi
 class MerchantApiTest {
 
-    private val api = MerchantApi(httpClient, TestCoroutineDispatcher())
+    private val api = MerchantApi(httpClient, UnconfinedTestDispatcher())
     private val merchantConfig = MerchantConfig(
         baseUrl = "http://example.net/instances/testInstance";,
         apiKey = "apiKeyFooBar"
diff --git a/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt 
b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
index c8e6f22..32c7ee1 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
@@ -20,11 +20,7 @@ import io.ktor.client.HttpClient
 import io.ktor.client.engine.mock.MockEngine
 import io.ktor.client.engine.mock.MockEngineConfig
 import io.ktor.client.engine.mock.respond
-import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.logging.LogLevel
-import io.ktor.client.features.logging.Logger
-import io.ktor.client.features.logging.Logging
-import io.ktor.client.features.logging.SIMPLE
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
 import io.ktor.http.ContentType.Application.Json
 import io.ktor.http.HttpStatusCode
 import io.ktor.http.Url
@@ -32,29 +28,31 @@ import io.ktor.http.content.TextContent
 import io.ktor.http.fullPath
 import io.ktor.http.headersOf
 import io.ktor.http.hostWithPort
+import io.ktor.serialization.kotlinx.json.json
+import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.Json.Default.parseToJsonElement
 import org.junit.Assert.assertEquals
 
 object MockHttpClient {
 
     val httpClient = HttpClient(MockEngine) {
-        install(JsonFeature) {
-            serializer = getSerializer()
-        }
-        install(Logging) {
-            logger = Logger.SIMPLE
-            level = LogLevel.ALL
-        }
         engine {
             addHandler { error("No response handler set") }
         }
+        expectSuccess = true
+        install(ContentNegotiation) {
+            json(Json {
+                encodeDefaults = false
+                ignoreUnknownKeys = true
+            })
+        }
     }
 
     fun HttpClient.giveJsonResponse(
         url: String,
         expectedBody: String? = null,
         statusCode: HttpStatusCode = HttpStatusCode.OK,
-        jsonProducer: () -> String
+        jsonProducer: () -> String,
     ) {
         val httpConfig = engineConfig as MockEngineConfig
         httpConfig.requestHandlers.removeAt(0)
diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle
index 19b0136..00fe568 100644
--- a/merchant-terminal/build.gradle
+++ b/merchant-terminal/build.gradle
@@ -6,14 +6,14 @@ plugins {
 }
 
 android {
-    compileSdkVersion 31
+    compileSdkVersion 32
     //noinspection GradleDependency
     buildToolsVersion "$build_tools_version"
 
     defaultConfig {
         applicationId "net.taler.merchantpos"
         minSdkVersion 21
-        targetSdkVersion 30
+        targetSdkVersion 32
         versionCode 2
         versionName "0.1"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -78,5 +78,5 @@ dependencies {
     implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
 
     testImplementation 'androidx.test.ext:junit:1.1.3'
-    testImplementation 'org.robolectric:robolectric:4.4'
+    testImplementation 'org.robolectric:robolectric:4.8.1'
 }
diff --git a/merchant-terminal/src/main/AndroidManifest.xml 
b/merchant-terminal/src/main/AndroidManifest.xml
index eb7940f..f7b4929 100644
--- a/merchant-terminal/src/main/AndroidManifest.xml
+++ b/merchant-terminal/src/main/AndroidManifest.xml
@@ -39,6 +39,7 @@
         <activity
             android:name=".MainActivity"
             android:label="@string/app_name"
+            android:exported="true"
             android:screenOrientation="landscape"
             android:theme="@style/AppTheme.NoActionBar"
             tools:ignore="LockedOrientationActivity">
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 47da74e..29f5da4 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
@@ -55,11 +55,11 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
         ui = ActivityMainBinding.inflate(layoutInflater)
         setContentView(ui.root)
 
-        model.paymentManager.payment.observe(this, { payment ->
+        model.paymentManager.payment.observe(this) { payment ->
             payment?.talerPayUri?.let {
                 nfcManager.setTagString(it)
             }
-        })
+        }
 
         val navHostFragment =
             supportFragmentManager.findFragmentById(R.id.navHostFragment) as 
NavHostFragment
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
index 165bb8e..4327f4e 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
@@ -26,7 +26,8 @@ import androidx.annotation.WorkerThread
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import io.ktor.client.HttpClient
-import io.ktor.client.features.ClientRequestException
+import io.ktor.client.call.body
+import io.ktor.client.plugins.ClientRequestException
 import io.ktor.client.request.get
 import io.ktor.client.request.header
 import io.ktor.http.HttpHeaders.Authorization
@@ -34,8 +35,8 @@ import io.ktor.http.HttpStatusCode.Companion.Unauthorized
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import net.taler.common.Version
 import net.taler.common.getIncompatibleStringOrNull
-import net.taler.lib.common.Version
 import net.taler.merchantlib.ConfigResponse
 import net.taler.merchantlib.MerchantApi
 import net.taler.merchantlib.MerchantConfig
@@ -105,7 +106,7 @@ class ConfigManager(
                     val credentials = "${config.username}:${config.password}"
                     val auth = ("Basic 
${encodeToString(credentials.toByteArray(), NO_WRAP)}")
                     header(Authorization, auth)
-                }
+                }.body()
                 val merchantConfig = posConfig.merchantConfig
                 // get config from merchant backend API
                 
api.getConfig(merchantConfig.baseUrl).handleSuspend(::onNetworkError) {
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
index 971f92c..1f1ab74 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
@@ -19,10 +19,10 @@ package net.taler.merchantpos.config
 import android.os.Build.VERSION.SDK_INT
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import net.taler.common.Amount
 import net.taler.common.ContractProduct
 import net.taler.common.Product
 import net.taler.common.TalerUtils
-import net.taler.lib.common.Amount
 import net.taler.merchantlib.MerchantConfig
 import java.util.UUID
 
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 f48c1db..ad9af74 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,8 +20,8 @@ import androidx.annotation.UiThread
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.Transformations
+import net.taler.common.Amount
 import net.taler.common.CombinedLiveData
-import net.taler.lib.common.Amount
 import net.taler.merchantpos.config.Category
 import net.taler.merchantpos.config.ConfigProduct
 import net.taler.merchantpos.order.RestartState.DISABLED
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
index 0bea20c..9860dbd 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
@@ -16,10 +16,10 @@
 
 package net.taler.merchantpos.order
 
+import net.taler.common.Amount
 import net.taler.common.ContractTerms
+import net.taler.common.Timestamp
 import net.taler.common.now
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
 import net.taler.merchantpos.config.Category
 import net.taler.merchantpos.config.ConfigProduct
 import java.net.URLEncoder
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 d86f504..c4a5228 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
@@ -26,6 +26,7 @@ import androidx.recyclerview.selection.SelectionPredicates
 import androidx.recyclerview.selection.SelectionTracker
 import androidx.recyclerview.selection.StorageStrategy
 import androidx.recyclerview.widget.LinearLayoutManager
+import net.taler.common.Amount
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.merchantpos.MainViewModel
@@ -82,11 +83,11 @@ class OrderStateFragment : Fragment() {
                 liveOrder.selectOrderLine(item)
             }
         })
-        liveOrder.order.observe(viewLifecycleOwner, { order ->
+        liveOrder.order.observe(viewLifecycleOwner) { order ->
             if (order == null) return@observe
             onOrderChanged(order, tracker)
-        })
-        liveOrder.orderTotal.observe(viewLifecycleOwner, { orderTotal ->
+        }
+        liveOrder.orderTotal.observe(viewLifecycleOwner) { orderTotal: Amount 
->
             if (orderTotal.isZero()) {
                 ui.totalView.fadeOut()
                 ui.totalView.text = null
@@ -94,7 +95,7 @@ class OrderStateFragment : Fragment() {
                 ui.totalView.text = getString(R.string.order_total, orderTotal)
                 ui.totalView.fadeIn()
             }
-        })
+        }
     }
 
     override fun onSaveInstanceState(outState: Bundle) {
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 98161db..d703a31 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
@@ -26,8 +26,8 @@ import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
+import net.taler.common.Duration
 import net.taler.common.assertUiThread
-import net.taler.lib.common.Duration
 import net.taler.merchantlib.CheckPaymentResponse
 import net.taler.merchantlib.MerchantApi
 import net.taler.merchantlib.PostOrderRequest
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
index bb98dbd..03c786f 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
@@ -24,12 +24,12 @@ import androidx.annotation.StringRes
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
+import net.taler.common.Amount
+import net.taler.common.AmountParserException
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.common.navigate
 import net.taler.common.showError
-import net.taler.lib.common.Amount
-import net.taler.lib.common.AmountParserException
 import net.taler.merchantlib.OrderHistoryEntry
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt
index 8b3efca..849dbaa 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt
@@ -21,8 +21,8 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
+import net.taler.common.Amount
 import net.taler.common.assertUiThread
-import net.taler.lib.common.Amount
 import net.taler.merchantlib.MerchantApi
 import net.taler.merchantlib.OrderHistoryEntry
 import net.taler.merchantlib.RefundRequest
diff --git 
a/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
 
b/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
index ca48b6e..bb8dcb7 100644
--- 
a/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
+++ 
b/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
@@ -20,7 +20,7 @@ import android.app.Application
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import kotlinx.coroutines.runBlocking
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
 import net.taler.merchantlib.MerchantConfig
 import net.taler.merchantpos.R
 import net.taler.merchantpos.config.Category
diff --git a/multiplatform b/multiplatform
deleted file mode 160000
index cb92566..0000000
--- a/multiplatform
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit cb92566166884efdaad5e93ef25e2de1f3034616
diff --git a/taler-kotlin-android/build.gradle 
b/taler-kotlin-android/build.gradle
index e45a3a1..5fdb545 100644
--- a/taler-kotlin-android/build.gradle
+++ b/taler-kotlin-android/build.gradle
@@ -21,13 +21,12 @@ plugins {
 }
 
 android {
-    compileSdkVersion 31
-    //noinspection GradleDependency
+    compileSdkVersion 32
     buildToolsVersion "$build_tools_version"
 
     defaultConfig {
         minSdkVersion 21
-        targetSdkVersion 30
+        targetSdkVersion 32
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles 'consumer-rules.pro'
@@ -58,10 +57,8 @@ android {
 }
 
 dependencies {
-    api project(":multiplatform:common")
-
-    implementation 'androidx.appcompat:appcompat:1.3.1'
-    implementation 'androidx.core:core-ktx:1.6.0'
+    implementation 'androidx.appcompat:appcompat:1.4.1'
+    implementation 'androidx.core:core-ktx:1.7.0'
     implementation 
"androidx.constraintlayout:constraintlayout:$constraintlayout_version"
 
     // Navigation
@@ -72,20 +69,13 @@ dependencies {
     implementation 
"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
 
     // QR codes
-    implementation 'com.google.zxing:core:3.4.0'  // needs minSdkVersion 24+ 
or desugar
-
-    // Logcat viewer
-    implementation('com.github.pedrovgs:lynx:1.1.0') {
-        exclude group: 'com.android.support'
-        exclude group: 'com.squareup'
-    }
-    implementation 'com.github.pedrovgs:renderers:4.0.0'
+    implementation 'com.google.zxing:core:3.5.0'  // needs minSdkVersion 24+ 
or desugar
 
     // JSON parsing and serialization
-    api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2"
+    api 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3'
 
     lintPublish 'com.github.thirdegg:lint-rules:0.0.6-beta'
 
     testImplementation "junit:junit:$junit_version"
-    testImplementation 'org.json:json:20200518'
+    testImplementation 'org.json:json:20220320'
 }
diff --git a/taler-kotlin-android/src/main/AndroidManifest.xml 
b/taler-kotlin-android/src/main/AndroidManifest.xml
index c1f6146..5533913 100644
--- a/taler-kotlin-android/src/main/AndroidManifest.xml
+++ b/taler-kotlin-android/src/main/AndroidManifest.xml
@@ -15,12 +15,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android";
-    xmlns:tools="http://schemas.android.com/tools";>
-
-    <uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
+    package="net.taler.common">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.NFC" />
-
 </manifest>
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt
new file mode 100644
index 0000000..a36c0ab
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt
@@ -0,0 +1,199 @@
+/*
+ * 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.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Serializer
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlin.math.floor
+import kotlin.math.pow
+import kotlin.math.roundToInt
+
+public class AmountParserException(msg: String? = null, cause: Throwable? = 
null) : Exception(msg, cause)
+public class AmountOverflowException(msg: String? = null, cause: Throwable? = 
null) : Exception(msg, cause)
+
+@Serializable(with = KotlinXAmountSerializer::class)
+public data class Amount(
+    /**
+     * name of the currency using either a three-character ISO 4217 currency 
code,
+     * or a regional currency identifier starting with a "*" followed by at 
most 10 characters.
+     * ISO 4217 exponents in the name are not supported,
+     * although the "fraction" is corresponds to an ISO 4217 exponent of 6.
+     */
+    val currency: String,
+
+    /**
+     * The integer part may be at most 2^52.
+     * Note that "1" here would correspond to 1 EUR or 1 USD, depending on 
currency, not 1 cent.
+     */
+    val value: Long,
+
+    /**
+     * Unsigned 32 bit fractional value to be added to value representing
+     * an additional currency fraction, in units of one hundred millionth 
(1e-8)
+     * of the base currency value.  For example, a fraction
+     * of 50_000_000 would correspond to 50 cents.
+     */
+    val fraction: Int
+) : Comparable<Amount> {
+
+    public companion object {
+
+        private const val FRACTIONAL_BASE: Int = 100000000 // 1e8
+
+        private val REGEX_CURRENCY = Regex("""^[-_*A-Za-z0-9]{1,12}$""")
+        public val MAX_VALUE: Long = 2.0.pow(52).toLong()
+        private const val MAX_FRACTION_LENGTH = 8
+        public const val MAX_FRACTION: Int = 99_999_999
+
+        public fun zero(currency: String): Amount {
+            return Amount(checkCurrency(currency), 0, 0)
+        }
+
+        public fun fromJSONString(str: String): Amount {
+            val split = str.split(":")
+            if (split.size != 2) throw AmountParserException("Invalid Amount 
Format")
+            return fromString(split[0], split[1])
+        }
+
+        public fun fromString(currency: String, str: String): Amount {
+            // value
+            val valueSplit = str.split(".")
+            val value = checkValue(valueSplit[0].toLongOrNull())
+            // fraction
+            val fraction: Int = if (valueSplit.size > 1) {
+                val fractionStr = valueSplit[1]
+                if (fractionStr.length > MAX_FRACTION_LENGTH)
+                    throw AmountParserException("Fraction $fractionStr too 
long")
+                val fraction = "0.$fractionStr".toDoubleOrNull()
+                    ?.times(FRACTIONAL_BASE)
+                    ?.roundToInt()
+                checkFraction(fraction)
+            } else 0
+            return Amount(checkCurrency(currency), value, fraction)
+        }
+
+        public fun min(currency: String): Amount = Amount(currency, 0, 1)
+        public fun max(currency: String): Amount = Amount(currency, MAX_VALUE, 
MAX_FRACTION)
+
+
+        internal fun checkCurrency(currency: String): String {
+            if (!REGEX_CURRENCY.matches(currency))
+                throw AmountParserException("Invalid currency: $currency")
+            return currency
+        }
+
+        internal fun checkValue(value: Long?): Long {
+            if (value == null || value > MAX_VALUE)
+                throw AmountParserException("Value $value greater than 
$MAX_VALUE")
+            return value
+        }
+
+        internal fun checkFraction(fraction: Int?): Int {
+            if (fraction == null || fraction > MAX_FRACTION)
+                throw AmountParserException("Fraction $fraction greater than 
$MAX_FRACTION")
+            return fraction
+        }
+
+    }
+
+    public val amountStr: String
+        get() = if (fraction == 0) "$value" else {
+            var f = fraction
+            var fractionStr = ""
+            while (f > 0) {
+                fractionStr += f / (FRACTIONAL_BASE / 10)
+                f = (f * 10) % FRACTIONAL_BASE
+            }
+            "$value.$fractionStr"
+        }
+
+    public operator fun plus(other: Amount): Amount {
+        check(currency == other.currency) { "Can only subtract from same 
currency" }
+        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)
+    }
+
+    public 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
+    }
+
+    public operator fun minus(other: Amount): Amount {
+        check(currency == other.currency) { "Can only subtract from same 
currency" }
+        var resultValue = value
+        var resultFraction = fraction
+        if (resultFraction < other.fraction) {
+            if (resultValue < 1L)
+                throw AmountOverflowException()
+            resultValue--
+            resultFraction += FRACTIONAL_BASE
+        }
+        check(resultFraction >= other.fraction)
+        resultFraction -= other.fraction
+        if (resultValue < other.value)
+            throw AmountOverflowException()
+        resultValue -= other.value
+        return Amount(currency, resultValue, resultFraction)
+    }
+
+    public fun isZero(): Boolean {
+        return value == 0L && fraction == 0
+    }
+
+    public fun toJSONString(): String {
+        return "$currency:$amountStr"
+    }
+
+    override fun toString(): String {
+        return "$amountStr $currency"
+    }
+
+    override fun compareTo(other: Amount): Int {
+        check(currency == other.currency) { "Can only compare amounts with the 
same currency" }
+        when {
+            value == other.value -> {
+                if (fraction < other.fraction) return -1
+                if (fraction > other.fraction) return 1
+                return 0
+            }
+            value < other.value -> return -1
+            else -> return 1
+        }
+    }
+
+}
+
+@Suppress("EXPERIMENTAL_API_USAGE")
+@Serializer(forClass = Amount::class)
+internal object KotlinXAmountSerializer : KSerializer<Amount> {
+    override fun serialize(encoder: Encoder, value: Amount) {
+        encoder.encodeString(value.toJSONString())
+    }
+
+    override fun deserialize(decoder: Decoder): Amount {
+        return Amount.fromJSONString(decoder.decodeString())
+    }
+}
diff --git 
a/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
index 4ac2e73..5b57320 100644
--- a/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
@@ -46,10 +46,7 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentActivity
 import androidx.navigation.NavDirections
 import androidx.navigation.fragment.findNavController
-import com.github.pedrovgs.lynx.LynxActivity
-import com.github.pedrovgs.lynx.LynxConfig
 import net.taler.lib.android.ErrorBottomSheet
-import net.taler.lib.common.Version
 
 fun View.fadeIn(endAction: () -> Unit = {}) {
     if (visibility == VISIBLE && alpha == 1f) return
@@ -96,15 +93,6 @@ fun Context.isOnline(): Boolean {
     }
 }
 
-fun Context.showLogViewer(logFilter: String? = null) {
-    val lynxActivityIntent = LynxActivity.getIntent(this, LynxConfig().apply {
-        maxNumberOfTracesToShow = 1500 // higher numbers seem to break share 
functionality
-        samplingRate = Int.MAX_VALUE // no updates please
-        logFilter?.let { filter = it }
-    })
-    startActivity(lynxActivityIntent)
-}
-
 fun FragmentActivity.showError(mainText: String, detailText: String = "") = 
ErrorBottomSheet
         .newInstance(mainText, detailText)
         .show(supportFragmentManager, "ERROR_BOTTOM_SHEET")
diff --git 
a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
index fb30692..910cc36 100644
--- a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
@@ -20,8 +20,6 @@ import android.os.Build
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import net.taler.common.TalerUtils.getLocalizedString
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
 
 @Serializable
 data class ContractTerms(
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/TalerUri.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/TalerUri.kt
new file mode 100644
index 0000000..a1b7225
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/TalerUri.kt
@@ -0,0 +1,62 @@
+/*
+ * 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 java.util.Locale
+
+public object TalerUri {
+
+    private const val SCHEME = "taler://"
+    private const val SCHEME_INSECURE = "taler+http://";
+    private const val AUTHORITY_PAY = "pay"
+    private const val AUTHORITY_WITHDRAW = "withdraw"
+    private const val AUTHORITY_REFUND = "refund"
+    private const val AUTHORITY_TIP = "tip"
+
+    public data class WithdrawUriResult(
+        val bankIntegrationApiBaseUrl: String,
+        val withdrawalOperationId: String
+    )
+
+    /**
+     * Parses a withdraw URI and returns a bank status URL or null if the URI 
was invalid.
+     */
+    public fun parseWithdrawUri(uri: String): WithdrawUriResult? {
+        val (resultScheme, prefix) = when {
+            uri.startsWith(SCHEME, ignoreCase = true) -> {
+                Pair("https://";, "${SCHEME}${AUTHORITY_WITHDRAW}/")
+            }
+            uri.startsWith(SCHEME_INSECURE, ignoreCase = true) -> {
+                Pair("http://";, "${SCHEME_INSECURE}${AUTHORITY_WITHDRAW}/")
+            }
+            else -> return null
+        }
+        if (!uri.startsWith(prefix)) return null
+        val parts = uri.let {
+            (if (it.endsWith("/")) it.dropLast(1) else 
it).substring(prefix.length).split('/')
+        }
+        if (parts.size < 2) return null
+        val host = parts[0].lowercase(Locale.ROOT)
+        val pathSegments = parts.slice(1 until parts.size - 
1).joinToString("/")
+        val withdrawId = parts.last()
+        if (withdrawId.isBlank()) return null
+        val url = "${resultScheme}${host}/${pathSegments}"
+
+        return WithdrawUriResult(url, withdrawId)
+    }
+
+}
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Time.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/Time.kt
new file mode 100644
index 0000000..61bbce8
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/Time.kt
@@ -0,0 +1,129 @@
+/*
+ * 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 kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.JsonTransformingSerializer
+import kotlinx.serialization.json.contentOrNull
+import kotlinx.serialization.json.jsonPrimitive
+import kotlinx.serialization.json.longOrNull
+import kotlin.math.max
+
+@Serializable
+data class Timestamp(
+    @SerialName("t_ms")
+    @Serializable(NeverSerializer::class)
+    val old_ms: Long? = null,
+    @SerialName("t_s")
+    @Serializable(NeverSerializer::class)
+    private val s: Long? = null,
+) : Comparable<Timestamp> {
+
+    constructor(ms: Long) : this(ms, null)
+
+    companion object {
+        private const val NEVER: Long = -1
+        fun now(): Timestamp = Timestamp(System.currentTimeMillis())
+        fun never(): Timestamp = Timestamp(NEVER)
+    }
+
+    val ms: Long = if (s != null) {
+        s * 1000L
+    } else if (old_ms !== null) {
+        old_ms
+    } else  {
+        throw Exception("timestamp didn't have t_s or t_ms")
+    }
+
+
+    /**
+     * 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(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 == Duration.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(
+    @SerialName("d_ms")
+    @Serializable(ForeverSerializer::class) val old_ms: Long? = null,
+    @SerialName("d_s")
+    @Serializable(ForeverSerializer::class)
+    private val s: Long? = null,
+) {
+    val ms: Long = if (s != null) {
+        s * 1000L
+    } else if (old_ms !== null) {
+        old_ms
+    } else  {
+        throw Exception("duration didn't have d_s or d_ms")
+    }
+
+    constructor(ms: Long) : this(ms, null)
+
+    companion object {
+        internal const val FOREVER: Long = -1
+        fun forever(): Duration = Duration(FOREVER)
+    }
+}
+
+internal abstract class MinusOneSerializer(private val keyword: String) :
+    JsonTransformingSerializer<Long>(Long.serializer()) {
+
+    override fun transformDeserialize(element: JsonElement): JsonElement {
+        return if (element.jsonPrimitive.contentOrNull == keyword) return 
JsonPrimitive(-1)
+        else super.transformDeserialize(element)
+    }
+
+    override fun transformSerialize(element: JsonElement): JsonElement {
+        return if (element.jsonPrimitive.longOrNull == -1L) return 
JsonPrimitive(keyword)
+        else element
+    }
+}
+
+internal object NeverSerializer : MinusOneSerializer("never")
+internal object ForeverSerializer : MinusOneSerializer("forever")
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Version.kt 
b/taler-kotlin-android/src/main/java/net/taler/common/Version.kt
new file mode 100644
index 0000000..f46913e
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/Version.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.math.sign
+
+/**
+ * Semantic versioning, but libtool-style.
+ * See 
https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
+ */
+public data class Version(
+    val current: Int,
+    val revision: Int,
+    val age: Int
+) {
+    public companion object {
+        public fun parse(v: String): Version? {
+            val elements = v.split(":")
+            if (elements.size != 3) return null
+            val (currentStr, revisionStr, ageStr) = elements
+            val current = currentStr.toIntOrNull()
+            val revision = revisionStr.toIntOrNull()
+            val age = ageStr.toIntOrNull()
+            if (current == null || revision == null || age == null) return null
+            return Version(current, revision, age)
+        }
+    }
+
+    /**
+     * Compare two libtool-style versions.
+     *
+     * Returns a [VersionMatchResult] or null if the given version was null.
+     */
+    public fun compare(other: Version?): VersionMatchResult? {
+        if (other == null) return null
+        val compatible = current - age <= other.current &&
+                current >= other.current - other.age
+        val currentCmp = sign((current - other.current).toDouble()).toInt()
+        return VersionMatchResult(compatible, currentCmp)
+    }
+
+    /**
+     * Result of comparing two libtool versions.
+     */
+    public data class VersionMatchResult(
+        /**
+         * Is the first version compatible with the second?
+         */
+        val compatible: Boolean,
+        /**
+         * Is the first version older (-1), newer (+1) or identical (0)?
+         */
+        val currentCmp: Int
+    )
+
+}
diff --git 
a/taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt 
b/taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt
deleted file mode 100644
index 7eb4480..0000000
--- a/taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt
+++ /dev/null
@@ -1,21 +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.lib.android
-
-interface CustomClassDiscriminator {
-    val discriminator: String
-}
diff --git a/taler-kotlin-android/src/test/java/net/taler/common/AmountTest.kt 
b/taler-kotlin-android/src/test/java/net/taler/common/AmountTest.kt
new file mode 100644
index 0000000..3343b52
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/AmountTest.kt
@@ -0,0 +1,221 @@
+/*
+ * 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
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.assertFalse
+import org.junit.Test
+
+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 testFromJSONString() {
+        var str = "TESTKUDOS:23.42"
+        var amount = Amount.fromJSONString(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 = Amount.fromJSONString(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 = Amount.fromJSONString(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 testFromJSONStringAcceptsMaxValuesRejectsAbove() {
+        val maxValue = 4503599627370496
+        val str = "TESTKUDOS123:$maxValue.99999999"
+        val amount = Amount.fromJSONString(str)
+        assertEquals(str, amount.toJSONString())
+        assertEquals("TESTKUDOS123", amount.currency)
+        assertEquals(maxValue, amount.value)
+        assertEquals("$maxValue.99999999 TESTKUDOS123", amount.toString())
+
+        // longer currency not accepted
+        assertThrows<AmountParserException>("longer currency was accepted") {
+            Amount.fromJSONString("TESTKUDOS1234:$maxValue.99999999")
+        }
+
+        // max value + 1 not accepted
+        assertThrows<AmountParserException>("max value + 1 was accepted") {
+            Amount.fromJSONString("TESTKUDOS123:${maxValue + 1}.99999999")
+        }
+
+        // max fraction + 1 not accepted
+        assertThrows<AmountParserException>("max fraction + 1 was accepted") {
+            Amount.fromJSONString("TESTKUDOS123:$maxValue.999999990")
+        }
+    }
+
+    @Test
+    fun testFromJSONStringRejections() {
+        assertThrows<AmountParserException> {
+            Amount.fromJSONString("TESTKUDOS:0,5")
+        }
+        assertThrows<AmountParserException> {
+            Amount.fromJSONString("+TESTKUDOS:0.5")
+        }
+        assertThrows<AmountParserException> {
+            Amount.fromJSONString("0.5")
+        }
+        assertThrows<AmountParserException> {
+            Amount.fromJSONString(":0.5")
+        }
+        assertThrows<AmountParserException> {
+            Amount.fromJSONString("EUR::0.5")
+        }
+        assertThrows<AmountParserException> {
+            Amount.fromJSONString("EUR:.5")
+        }
+    }
+
+    @Test
+    fun testAddition() {
+        assertEquals(
+            Amount.fromJSONString("EUR:2"),
+            Amount.fromJSONString("EUR:1") + Amount.fromJSONString("EUR:1")
+        )
+        assertEquals(
+            Amount.fromJSONString("EUR:3"),
+            Amount.fromJSONString("EUR:1.5") + Amount.fromJSONString("EUR:1.5")
+        )
+        assertEquals(
+            Amount.fromJSONString("EUR:500000000.00000002"),
+            Amount.fromJSONString("EUR:500000000.00000001") + 
Amount.fromJSONString("EUR:0.00000001")
+        )
+        assertThrows<AmountOverflowException>("addition didn't overflow") {
+            Amount.fromJSONString("EUR:4503599627370496.99999999") + 
Amount.fromJSONString("EUR:0.00000001")
+        }
+        assertThrows<AmountOverflowException>("addition didn't overflow") {
+            Amount.fromJSONString("EUR:4000000000000000") + 
Amount.fromJSONString("EUR:4000000000000000")
+        }
+    }
+
+    @Test
+    fun testTimes() {
+        assertEquals(
+            Amount.fromJSONString("EUR:2"),
+            Amount.fromJSONString("EUR:2") * 1
+        )
+        assertEquals(
+            Amount.fromJSONString("EUR:2"),
+            Amount.fromJSONString("EUR:1") * 2
+        )
+        assertEquals(
+            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
+        )
+        assertThrows<AmountOverflowException>("times didn't overflow") {
+            Amount.fromJSONString("EUR:4000000000000000") * 2
+        }
+    }
+
+    @Test
+    fun testSubtraction() {
+        assertEquals(
+            Amount.fromJSONString("EUR:0"),
+            Amount.fromJSONString("EUR:1") - Amount.fromJSONString("EUR:1")
+        )
+        assertEquals(
+            Amount.fromJSONString("EUR:1.5"),
+            Amount.fromJSONString("EUR:3") - Amount.fromJSONString("EUR:1.5")
+        )
+        assertEquals(
+            Amount.fromJSONString("EUR:500000000.00000001"),
+            Amount.fromJSONString("EUR:500000000.00000002") - 
Amount.fromJSONString("EUR:0.00000001")
+        )
+        assertThrows<AmountOverflowException>("subtraction didn't underflow") {
+            Amount.fromJSONString("EUR:23.42") - 
Amount.fromJSONString("EUR:42.23")
+        }
+        assertThrows<AmountOverflowException>("subtraction didn't underflow") {
+            Amount.fromJSONString("EUR:0.5") - 
Amount.fromJSONString("EUR:0.50000001")
+        }
+    }
+
+    @Test
+    fun testIsZero() {
+        assertTrue(Amount.zero("EUR").isZero())
+        assertTrue(Amount.fromJSONString("EUR:0").isZero())
+        assertTrue(Amount.fromJSONString("EUR:0.0").isZero())
+        assertTrue(Amount.fromJSONString("EUR:0.00000").isZero())
+        assertTrue((Amount.fromJSONString("EUR:1.001") - 
Amount.fromJSONString("EUR:1.001")).isZero())
+
+        assertFalse(Amount.fromJSONString("EUR:0.00000001").isZero())
+        assertFalse(Amount.fromJSONString("EUR:1.0").isZero())
+        assertFalse(Amount.fromJSONString("EUR:0001.0").isZero())
+    }
+
+    @Test
+    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"))
+        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"))
+        assertTrue(Amount.fromJSONString("EUR:0.00000002") > 
Amount.fromJSONString("EUR:0.00000001"))
+        assertTrue(Amount.fromJSONString("EUR:0.00000001") > 
Amount.fromJSONString("EUR:0"))
+        assertTrue(Amount.fromJSONString("EUR:2") > 
Amount.fromJSONString("EUR:1"))
+
+        assertThrows<IllegalStateException>("could compare amounts with 
different currencies") {
+            Amount.fromJSONString("EUR:0.5") < 
Amount.fromJSONString("USD:0.50000001")
+        }
+    }
+
+}
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
index 3a2cdb4..f7b83a9 100644
--- a/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
+++ b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
@@ -18,7 +18,6 @@ package net.taler.common
 
 import kotlinx.serialization.decodeFromString
 import kotlinx.serialization.json.Json
-import net.taler.lib.common.Timestamp
 import org.junit.Assert.assertEquals
 import org.junit.Test
 
diff --git 
a/taler-kotlin-android/src/test/java/net/taler/common/TalerUriTest.kt 
b/taler-kotlin-android/src/test/java/net/taler/common/TalerUriTest.kt
new file mode 100644
index 0000000..128f707
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/TalerUriTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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 net.taler.common.TalerUri.parseWithdrawUri
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+class TalerUriTest {
+
+    @Test
+    fun testParseWithdrawUri() {
+        // correct parsing
+        var uri = "taler://withdraw/bank.example.com/12345"
+        var expected = TalerUri.WithdrawUriResult("https://bank.example.com/";, 
"12345")
+        assertEquals(expected, parseWithdrawUri(uri))
+
+        // correct parsing with insecure http
+        uri = "taler+http://withdraw/bank.example.org/foo";
+        expected = TalerUri.WithdrawUriResult("http://bank.example.org/";, 
"foo")
+        assertEquals(expected, parseWithdrawUri(uri))
+
+        // correct parsing with long path
+        uri = "taler://withdraw/bank.example.com/foo/bar/23/42/1337/1234567890"
+        expected =
+            
TalerUri.WithdrawUriResult("https://bank.example.com/foo/bar/23/42/1337";, 
"1234567890")
+        assertEquals(expected, parseWithdrawUri(uri))
+
+        // rejects incorrect scheme
+        uri = "talerx://withdraw/bank.example.com/12345"
+        assertNull(parseWithdrawUri(uri))
+
+        // rejects incorrect authority
+        uri = "taler://withdrawx/bank.example.com/12345"
+        assertNull(parseWithdrawUri(uri))
+
+        // rejects incorrect authority with insecure http
+        uri = "taler+http://withdrawx/bank.example.com/12345";
+        assertNull(parseWithdrawUri(uri))
+
+        // rejects empty withdrawalId
+        uri = "taler://withdraw/bank.example.com//"
+        assertNull(parseWithdrawUri(uri))
+
+        // rejects empty path and withdrawalId
+        uri = "taler://withdraw/bank.example.com////"
+        assertNull(parseWithdrawUri(uri))
+    }
+
+}
diff --git a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt 
b/taler-kotlin-android/src/test/java/net/taler/common/TestUtils.kt
similarity index 54%
copy from cashier/src/main/java/net/taler/cashier/SignedAmount.kt
copy to taler-kotlin-android/src/test/java/net/taler/common/TestUtils.kt
index e79acfd..b0f191d 100644
--- a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
+++ b/taler-kotlin-android/src/test/java/net/taler/common/TestUtils.kt
@@ -14,17 +14,27 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.cashier
+package net.taler.common
 
-import net.taler.lib.common.Amount
+import kotlin.random.Random
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
 
-data class SignedAmount(
-    val positive: Boolean,
-    val amount: Amount
-) {
+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("")
 
-    override fun toString(): String {
-        return if (positive) "$amount" else "-$amount"
+inline fun <reified T : Throwable> assertThrows(
+    msg: String? = null,
+    function: () -> Any
+) {
+    try {
+        function.invoke()
+        fail(msg)
+    } catch (e: Exception) {
+        assertTrue(e is T)
     }
-
 }
diff --git a/taler-kotlin-android/src/test/java/net/taler/common/TimeTest.kt 
b/taler-kotlin-android/src/test/java/net/taler/common/TimeTest.kt
new file mode 100644
index 0000000..beda621
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/TimeTest.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.json.Json.Default.decodeFromString
+import kotlinx.serialization.json.Json.Default.encodeToString
+import kotlin.random.Random
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+// TODO test other functionality of Timestamp and Duration
+class TimeTest {
+
+    @Test
+    fun testSerialize() {
+        for (i in 0 until 42) {
+            val t = Random.nextLong()
+            assertEquals("""{"t_ms":$t}""", 
encodeToString(Timestamp.serializer(), Timestamp(t)))
+        }
+        assertEquals("""{"t_ms":"never"}""", 
encodeToString(Timestamp.serializer(), Timestamp.never()))
+    }
+
+    @Test
+    fun testDeserialize() {
+        for (i in 0 until 42) {
+            val t = Random.nextLong()
+            assertEquals(Timestamp(t), 
decodeFromString(Timestamp.serializer(), """{ "t_ms": $t }"""))
+        }
+        assertEquals(Timestamp.never(), 
decodeFromString(Timestamp.serializer(), """{ "t_ms": "never" }"""))
+    }
+
+}
diff --git a/taler-kotlin-android/src/test/java/net/taler/common/VersionTest.kt 
b/taler-kotlin-android/src/test/java/net/taler/common/VersionTest.kt
new file mode 100644
index 0000000..d8d7149
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/VersionTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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 org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+
+class VersionTest {
+
+    @Test
+    fun testParse() {
+        assertNull(Version.parse(""))
+        assertNull(Version.parse("foo"))
+        assertNull(Version.parse("foo:bar:foo"))
+        assertNull(Version.parse("0:0:0:"))
+        assertNull(Version.parse("0:0:"))
+        assertEquals(Version(0, 0, 0), Version.parse("0:0:0"))
+        assertEquals(Version(1, 2, 3), Version.parse("1:2:3"))
+        assertEquals(Version(1337, 42, 23), Version.parse("1337:42:23"))
+    }
+
+    @Test
+    fun testComparision() {
+        assertEquals(
+            Version.VersionMatchResult(true, 0),
+            Version.parse("0:0:0")!!.compare(Version.parse("0:0:0"))
+        )
+        assertEquals(
+            Version.VersionMatchResult(true, -1),
+            Version.parse("0:0:0")!!.compare(Version.parse("1:0:1"))
+        )
+        assertEquals(
+            Version.VersionMatchResult(true, -1),
+            Version.parse("0:0:0")!!.compare(Version.parse("1:5:1"))
+        )
+        assertEquals(
+            Version.VersionMatchResult(false, -1),
+            Version.parse("0:0:0")!!.compare(Version.parse("1:5:0"))
+        )
+        assertEquals(
+            Version.VersionMatchResult(false, 1),
+            Version.parse("1:0:0")!!.compare(Version.parse("0:5:0"))
+        )
+        assertEquals(
+            Version.VersionMatchResult(true, 0),
+            Version.parse("1:0:1")!!.compare(Version.parse("1:5:1"))
+        )
+    }
+
+}
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 2aaea01..faebdde 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -23,8 +23,8 @@ plugins {
     id "de.undercouch.download"
 }
 
-def walletCoreVersion = "v0.9.0-dev.10"
-def walletCoreSha256 = 
"3c9aa9afeb6d61091599553719ffd1010be7817b7368c2d0ace5f77d8089d513"
+def walletCoreVersion = "v0.9.0-dev.11"
+def walletCoreSha256 = 
"0ee5eae7d0afd6f251f7d33975758055d06926ccb67ae9099d589a79d34be39d"
 
 static def versionCodeEpoch() {
     return (new Date().getTime() / 1000).toInteger()
@@ -39,14 +39,13 @@ def gitCommit = { ->
 }
 
 android {
-    compileSdkVersion 31
-    //noinspection GradleDependency
+    compileSdkVersion 32
     buildToolsVersion "$build_tools_version"
 
     defaultConfig {
         applicationId "net.taler.wallet"
         minSdkVersion 21
-        targetSdkVersion 30
+        targetSdkVersion 32
         versionCode 11
         versionName walletCoreVersion
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -92,7 +91,7 @@ android {
     }
 
     composeOptions {
-        kotlinCompilerExtensionVersion '1.0.5'
+        kotlinCompilerExtensionVersion '1.1.1'
     }
 
     buildFeatures {
@@ -124,18 +123,18 @@ dependencies {
 
     implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
 
-    implementation 'androidx.preference:preference-ktx:1.1.1'
+    implementation 'androidx.preference:preference-ktx:1.2.0'
     implementation "com.google.android.material:material:$material_version"
     implementation 
"androidx.constraintlayout:constraintlayout:$constraintlayout_version"
 
     // Compose
     implementation 'androidx.activity:activity-compose:1.4.0'
-    implementation 'androidx.compose.material:material:1.0.5'
-    implementation 'androidx.compose.animation:animation:1.0.5'
-    implementation 'androidx.compose.ui:ui-tooling:1.0.5'
-    implementation 'androidx.compose.material:material-icons-extended:1.0.5'
+    implementation 'androidx.compose.material:material:1.1.1'
+    implementation 'androidx.compose.animation:animation:1.1.1'
+    implementation 'androidx.compose.ui:ui-tooling:1.1.1'
+    implementation 'androidx.compose.material:material-icons-extended:1.1.1'
     implementation 
"androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
-    implementation "com.google.android.material:compose-theme-adapter:1.1.1"
+    implementation 'com.google.android.material:compose-theme-adapter:1.1.9'
 
     // Lists and Selection
     implementation "androidx.recyclerview:recyclerview:1.2.1"
@@ -149,7 +148,7 @@ dependencies {
     implementation 
"androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
 
     // QR codes
-    implementation 'com.journeyapps:zxing-android-embedded:4.0.2@aar'
+    implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
     // needed to support zxing library in taler-kotlin-android on API < 24
     coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
 
@@ -157,13 +156,13 @@ dependencies {
     implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
 
     // Markdown rendering
-    final def markwon_version = '4.6.0'
+    final def markwon_version = '4.6.2'
     implementation "io.noties.markwon:core:$markwon_version"
     implementation "io.noties.markwon:ext-tables:$markwon_version"
     implementation "io.noties.markwon:recycler:$markwon_version"
 
     testImplementation "junit:junit:$junit_version"
-    testImplementation 'org.json:json:20200518'
+    testImplementation 'org.json:json:20220320'
     androidTestImplementation 'androidx.test:runner:1.4.0'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
 }
@@ -189,7 +188,14 @@ task verifyWalletLibrary(type: Verify, dependsOn: 
downloadWalletLibrary) {
     algorithm 'SHA-256'
     checksum walletCoreSha256
 }
+
 tasks.withType(MergeResources) {
     inputs.dir walletLibraryDir
     dependsOn verifyWalletLibrary
 }
+
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+    kotlinOptions {
+        freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
+    }
+}
\ No newline at end of file
diff --git a/wallet/src/main/AndroidManifest.xml 
b/wallet/src/main/AndroidManifest.xml
index 285908a..61191b6 100644
--- a/wallet/src/main/AndroidManifest.xml
+++ b/wallet/src/main/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" 
/>
     <uses-permission android:name="android.permission.NFC" />
 
     <uses-feature
@@ -43,6 +44,7 @@
 
         <activity
             android:name=".MainActivity"
+            android:exported="true"
             android:label="@string/app_name"
             android:theme="@style/AppTheme.NoActionBar">
             <intent-filter>
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index a2b48b6..76aceb2 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -30,7 +30,6 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.json.Json
-import net.taler.lib.android.CustomClassDiscriminator
 import net.taler.wallet.backend.WalletBackendService.Companion.MSG_COMMAND
 import net.taler.wallet.backend.WalletBackendService.Companion.MSG_NOTIFY
 import net.taler.wallet.backend.WalletBackendService.Companion.MSG_REPLY
@@ -42,7 +41,6 @@ import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.atomic.AtomicInteger
 import kotlin.coroutines.resume
 import kotlin.coroutines.suspendCoroutine
-import kotlin.reflect.full.companionObjectInstance
 
 class WalletBackendApi(
     private val app: Application,
@@ -152,9 +150,6 @@ class WalletBackendApi(
         suspendCoroutine { cont ->
             val json = Json {
                 ignoreUnknownKeys = true
-                (T::class.companionObjectInstance as? 
CustomClassDiscriminator)?.let {
-                    classDiscriminator = it.discriminator
-                }
             }
             sendRequest(operation, args?.invoke(JSONObject())) { isError, 
message ->
                 val response = try {
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
index ab1ac80..e52fd4f 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
@@ -32,13 +32,13 @@ sealed class WalletResponse<T> {
     @Serializable
     @SerialName("response")
     data class Success<T>(
-        val result: T
+        val result: T,
     ) : WalletResponse<T>()
 
     @Serializable
     @SerialName("error")
     data class Error<T>(
-        val error: TalerErrorInfo
+        val error: TalerErrorInfo,
     ) : WalletResponse<T>()
 
     fun onSuccess(block: (result: T) -> Unit): WalletResponse<T> {
@@ -59,15 +59,15 @@ data class TalerErrorInfo(
     val code: Int,
 
     // English description of the error code.
-    val hint: String?,
+    val hint: String? = null,
 
     // English diagnostic message that can give details
     // for the instance of the error.
-    val message: String?,
+    val message: String? = null,
 
     // Error details
     @Serializable(JSONObjectDeserializer::class)
-    val details: JSONObject?
+    val details: JSONObject? = null,
 ) {
     val userFacingMsg: String
         get() {
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt 
b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
index 09ae353..24ee1a1 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
@@ -25,7 +25,7 @@ import android.widget.TextView
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView.Adapter
 import kotlinx.serialization.Serializable
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
 import net.taler.wallet.R
 import net.taler.wallet.balances.BalanceAdapter.BalanceViewHolder
 
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 1da9b49..7824d1e 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
@@ -16,8 +16,9 @@
 
 package net.taler.wallet.exchanges
 
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
+import net.taler.common.Amount
+import net.taler.common.Timestamp
+
 
 data class CoinFee(
     val coin: Amount,
diff --git 
a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt
index 1ea32dd..d8242f3 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt
@@ -27,9 +27,9 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.recyclerview.widget.RecyclerView.Adapter
 import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import net.taler.common.Amount
 import net.taler.common.toRelativeTime
 import net.taler.common.toShortDate
-import net.taler.lib.common.Amount
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 import net.taler.wallet.databinding.FragmentExchangeFeesBinding
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
index 34a8023..ae091df 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -22,8 +22,8 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
+import net.taler.common.Amount
 import net.taler.common.ContractTerms
-import net.taler.lib.common.Amount
 import net.taler.wallet.TAG
 import net.taler.wallet.backend.WalletBackendApi
 import net.taler.wallet.backend.TalerErrorInfo
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 4b908b5..7e03472 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
@@ -16,18 +16,18 @@
 
 package net.taler.wallet.payment
 
+import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonClassDiscriminator
+import net.taler.common.Amount
 import net.taler.common.ContractTerms
-import net.taler.lib.android.CustomClassDiscriminator
-import net.taler.lib.common.Amount
 import net.taler.wallet.backend.TalerErrorInfo
 
+@OptIn(ExperimentalSerializationApi::class)
 @Serializable
+@JsonClassDiscriminator("status")
 sealed class PreparePayResponse {
-    companion object : CustomClassDiscriminator {
-        override val discriminator: String = "status"
-    }
 
     @Serializable
     @SerialName("payment-possible")
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
index 700e158..d5c3eaf 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
@@ -28,10 +28,10 @@ import androidx.navigation.fragment.findNavController
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.snackbar.Snackbar
 import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
+import net.taler.common.Amount
 import net.taler.common.ContractTerms
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
-import net.taler.lib.common.Amount
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 import net.taler.wallet.databinding.FragmentPromptPaymentBinding
@@ -49,7 +49,7 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
 
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
-        savedInstanceState: Bundle?
+        savedInstanceState: Bundle?,
     ): View {
         ui = FragmentPromptPaymentBinding.inflate(inflater, container, false)
         return ui.root
@@ -131,7 +131,7 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
         }
     }
 
-    private fun showOrder(contractTerms: ContractTerms, amount:Amount, 
totalFees: Amount? = null) {
+    private fun showOrder(contractTerms: ContractTerms, amount: Amount, 
totalFees: Amount? = null) {
         ui.details.orderView.text = contractTerms.summary
         adapter.setItems(contractTerms.products)
         ui.details.productsList.fadeIn()
diff --git a/wallet/src/main/java/net/taler/wallet/refund/RefundManager.kt 
b/wallet/src/main/java/net/taler/wallet/refund/RefundManager.kt
index 9c292aa..f3c41e8 100644
--- a/wallet/src/main/java/net/taler/wallet/refund/RefundManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/refund/RefundManager.kt
@@ -21,7 +21,7 @@ import androidx.lifecycle.MutableLiveData
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.serialization.Serializable
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
 import net.taler.wallet.backend.WalletBackendApi
 
 sealed class RefundStatus {
diff --git a/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
index bd6ce1a..1a17d78 100644
--- a/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
@@ -18,6 +18,8 @@ package net.taler.wallet.settings
 
 import android.os.Bundle
 import android.view.View
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.AlertDialog
 import androidx.fragment.app.activityViewModels
 import androidx.preference.Preference
@@ -26,7 +28,6 @@ import androidx.preference.SwitchPreferenceCompat
 import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_SHORT
 import com.google.android.material.snackbar.Snackbar
 import net.taler.common.showError
-import net.taler.common.showLogViewer
 import net.taler.common.toRelativeTime
 import net.taler.wallet.BuildConfig.FLAVOR
 import net.taler.wallet.BuildConfig.VERSION_CODE
@@ -64,6 +65,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
         )
     }
 
+    val createDocumentActivity =
+        registerForActivityResult(ActivityResultContracts.CreateDocument()) { 
uri ->
+
+        }
+
     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: 
String?) {
         setPreferencesFromResource(R.xml.settings_main, rootKey)
         prefBackup = findPreference("pref_backup")!!
@@ -80,12 +86,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        model.lastBackup.observe(viewLifecycleOwner, {
+        model.lastBackup.observe(viewLifecycleOwner) {
             val time = it.toRelativeTime(requireContext())
             prefBackup.summary = getString(R.string.backup_last, time)
-        })
+        }
 
-        model.devMode.observe(viewLifecycleOwner, { enabled ->
+        model.devMode.observe(viewLifecycleOwner) { enabled ->
             prefDevMode.isChecked = enabled
             if (enabled) {
                 prefVersionApp.summary = "$VERSION_NAME ($FLAVOR 
$VERSION_CODE)"
@@ -94,13 +100,13 @@ class SettingsFragment : PreferenceFragmentCompat() {
                 model.merchantVersion?.let { prefVersionMerchant.summary = it }
             }
             devPrefs.forEach { it.isVisible = enabled }
-        })
+        }
         prefDevMode.setOnPreferenceChangeListener { _, newValue ->
             model.devMode.value = newValue as Boolean
             true
         }
 
-        withdrawManager.testWithdrawalStatus.observe(viewLifecycleOwner, { 
status ->
+        withdrawManager.testWithdrawalStatus.observe(viewLifecycleOwner) { 
status ->
             if (status == null) return@observe
             val loading = status is WithdrawTestStatus.Withdrawing
             prefWithdrawTest.isEnabled = !loading
@@ -109,14 +115,37 @@ class SettingsFragment : PreferenceFragmentCompat() {
                 requireActivity().showError(R.string.withdraw_error_test, 
status.message)
             }
             withdrawManager.testWithdrawalStatus.value = null
-        })
+        }
         prefWithdrawTest.setOnPreferenceClickListener {
             withdrawManager.withdrawTestkudos()
             true
         }
 
         prefLogcat.setOnPreferenceClickListener {
-            requireContext().showLogViewer("taler-wallet")
+            val toast =
+                Toast.makeText(requireActivity(), "Log export currently 
unavailable", Toast.LENGTH_LONG)
+            toast.show()
+
+//            val myPid = android.os.Process.myPid()
+//            val proc = Runtime.getRuntime()
+//                .exec(arrayOf("logcat", "-d", "--pid=$myPid", "*:V"))
+//            val bytes = proc.inputStream.readBytes()
+//            val f = File(requireActivity().getExternalFilesDir(null),
+//                "taler-wallet-log-${System.currentTimeMillis()}.txt")
+//            f.writeBytes(bytes)
+//            val toast = Toast.makeText(requireActivity(), "Saved to 
${f.absolutePath}", Toast.LENGTH_LONG)
+//            toast.show()
+//            val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+//                addCategory(Intent.CATEGORY_OPENABLE)
+//                type = "application/pdf"
+//                putExtra(Intent.EXTRA_TITLE, "invoice.pdf")
+//
+//                // Optionally, specify a URI for the directory that should 
be opened in
+//                // the system file picker before your app creates the 
document.
+//                putExtra(DocumentsContract.EXTRA_INITIAL_URI, 
pickerInitialUri)
+//            }
+//            startActivityForResult(intent, CREATE_FILE)
+//            ActivityResultContracts.CreateDocument
             true
         }
 
@@ -138,5 +167,4 @@ class SettingsFragment : PreferenceFragmentCompat() {
             }
             .show()
     }
-
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
index 866b363..128ca04 100644
--- 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
+++ 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
@@ -25,8 +25,8 @@ import android.view.MenuItem
 import android.widget.TextView
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import net.taler.common.Amount
 import net.taler.common.startActivitySafe
-import net.taler.lib.common.Amount
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
index 50181c5..cca370e 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -23,10 +23,10 @@ import androidx.annotation.StringRes
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.Transient
+import net.taler.common.Amount
 import net.taler.common.ContractMerchant
 import net.taler.common.ContractProduct
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
+import net.taler.common.Timestamp
 import net.taler.wallet.R
 import net.taler.wallet.backend.TalerErrorInfo
 import net.taler.wallet.cleanExchange
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
index 90510e6..e63d451 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
@@ -36,6 +36,7 @@ import androidx.recyclerview.selection.SelectionTracker
 import androidx.recyclerview.selection.StorageStrategy
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
+import net.taler.common.Amount
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.wallet.MainViewModel
@@ -112,12 +113,12 @@ class TransactionsFragment : Fragment(), 
OnTransactionClickListener, ActionMode.
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
-        model.balances.observe(viewLifecycleOwner, { balances ->
-            balances.find { it.currency == currency }?.available?.let { amount 
->
+        model.balances.observe(viewLifecycleOwner) { balances ->
+            balances.find { it.currency == currency }?.available?.let { 
amount: Amount ->
                 requireActivity().title =
                     getString(R.string.transactions_detail_title_balance, 
amount)
             }
-        })
+        }
     }
 
     override fun onSaveInstanceState(outState: Bundle) {
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
index e78ff44..0cb39d2 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
@@ -24,8 +24,8 @@ import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
+import net.taler.common.Amount
 import net.taler.common.hideKeyboard
-import net.taler.lib.common.Amount
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 import net.taler.wallet.databinding.FragmentManualWithdrawBinding
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt
 
b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt
index 4ea3e73..cdacde6 100644
--- 
a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt
+++ 
b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt
@@ -60,8 +60,8 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
 import com.google.android.material.composethemeadapter.MdcTheme
+import net.taler.common.Amount
 import net.taler.common.startActivitySafe
-import net.taler.lib.common.Amount
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
index 08cbc2e..dfae920 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -25,10 +25,10 @@ import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
 import com.google.android.material.snackbar.Snackbar
 import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
+import net.taler.common.Amount
 import net.taler.common.EventObserver
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
-import net.taler.lib.common.Amount
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 import net.taler.wallet.cleanExchange
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
index fbb5c18..d96f421 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -24,9 +24,9 @@ import androidx.lifecycle.MutableLiveData
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.serialization.Serializable
+import net.taler.common.Amount
 import net.taler.common.Event
 import net.taler.common.toEvent
-import net.taler.lib.common.Amount
 import net.taler.wallet.TAG
 import net.taler.wallet.backend.TalerErrorInfo
 import net.taler.wallet.backend.WalletBackendApi
diff --git 
a/wallet/src/test/java/net/taler/wallet/payment/PaymentResponsesTest.kt 
b/wallet/src/test/java/net/taler/wallet/payment/PaymentResponsesTest.kt
index fbdc07d..da2378e 100644
--- a/wallet/src/test/java/net/taler/wallet/payment/PaymentResponsesTest.kt
+++ b/wallet/src/test/java/net/taler/wallet/payment/PaymentResponsesTest.kt
@@ -23,7 +23,6 @@ class PaymentResponsesTest {
 
     private val json = Json {
         ignoreUnknownKeys = true
-        classDiscriminator = PreparePayResponse.discriminator
     }
 
     @Test

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]